关于 walrus-ts-sdk
walrus-ts-sdk 是使用官方 `@mysten/walrus` TypeScript SDK 读写 Walrus Blob 和文件的技能,介绍直接访问存储节点、上传中继及可恢复的分步写入流程。
主要功能
- 01读写 Blob 与文件
- 02处理 Quilt 中的文件
- 03通过上传中继支持浏览器上传
- 04恢复中断的写入流程
- 05配置 Sui 与 Walrus 客户端
- 06使用前检查 Blob 可用性
适用场景
- 01在 TypeScript 应用中接入 Walrus 存储
- 02通过浏览器钱包流程上传
- 03恢复中断的 Blob 上传
- 04依赖存储数据前检查认证状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mystenlabs/ walrus-skills --skill walrus-ts-sdk安装后在 Claude Code 中输入/walrus-ts-sdk确认已加载。
附带文件evals/evals.json
详细介绍
Walrus TypeScript SDK
来源限制: 本技能中的所有信息均来自 Walrus TypeScript SDK 文档、 官方示例 和 Walrus 文档。 扩展本技能时,只能使用这些来源。
@mysten/walrus 包是 Walrus 的官方 TypeScript SDK,由 Mysten Labs 维护。它可以直接与 Walrus 存储节点交互,也可以通过上传中继交互。不使用中继时,写入一个 blob 大约需要 2,200 次请求,读取一个 blob 大约需要 335 次,因此对于许多应用,SDK 文档建议改用发布器和聚合器;该 SDK 适用于需要直接与 Walrus 交互,或需要由用户自行支付存储费用的应用。
存储在 Walrus 上的所有 blob 都是公开的。存储任何机密内容之前,请先在客户端加密(参见 walrus-data-security)。
本技能中的所有模式均源自:
- https://sdk.mystenlabs.com/walrus
- https://github.com/MystenLabs/ts-sdks/tree/main/packages/walrus/examples
- https://docs.wal.app/docs/walrus-client/verifying-availability
如果不确定某个 SDK API 的用法,请先获取相关页面。完整的方法列表见 WalrusClient TypeDocs。
相关技能
| 主题 | 技能 | 适用场景 |
|---|---|---|
| CLI 操作 | walrus-cli | 使用 walrus 二进制文件而非 SDK |
| HTTP API | walrus-http-api | 使用任何语言调用 REST 端点 |
| 机密性 | walrus-data-security | 上传前加密 blob |
| Blob 管理 | walrus-blob-lifecycle | 延长、删除或共享 blob;验证可用性 |
| Quilts | walrus-quilts | 将许多小 blob 批量合并为一个单元 |
| Move 集成 | walrus-move-integration | 在 Move 合约中封装 Walrus blob |
技能内容
核心概念
- 客户端设置。 创建 Sui 客户端,并通过 Walrus 扩展它:
new SuiGrpcClient({ network, baseUrl }).$extend(walrus())。扩展通过client.walrus访问。新代码不要使用已弃用的 JSON-RPCSuiClient。 - 两种 API 层级。
WalrusFileAPI(getFiles、writeFiles)可处理以普通 blob 和 quilt 形式存储的数据,是推荐的入口。底层的readBlob/writeBlobAPI 则用于单个Uint8Arrayblob。 - 工作流。
writeFilesFlow和writeBlobFlow会将写入拆分为encode、register、upload、certify几个步骤。浏览器钱包(每次签名都需要单独的用户交互)和需要支持崩溃恢复的上传场景应使用它们(run()会生成可持久化的步骤;将其作为resume传回即可继续)。 - 签名者。 写入操作需要一个
Signer,其地址需持有 SUI,用于支付注册和认证交易,并持有 WAL,用于支付存储费用和写入费用。读取操作不需要签名者。 - 上传中继。 浏览器或移动客户端无法建立足够的连接来访问所有分片。
uploadRelay选项会将 blob 发送到一个负责分发碎片的中继;客户端仍需在 Sui 上签名并支付费用。中继可能要求支付小费。 - Blob ID 与对象 ID。
blobId(URL 安全的 base64,由内容派生)用于读取。blobObject.id(0x...)是用于生命周期操作和验证可用性的 Sui 对象。 - 社区 SDK。 对于非 TypeScript 语言,可使用社区 SDK:Go、PHP 和 Python。它们通过 HTTP 与聚合器和发布器通信。
安装和设置
npm install --save @mysten/walrus @mysten/sui
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { walrus } from '@mysten/walrus';
const client = new SuiGrpcClient({
network: 'testnet',
baseUrl: 'https://fullnode.testnet.sui.io:443',
}).$extend(walrus());
SDK 为每个网络打包了程序包 ID 和对象 ID。若要指定其他部署,请将 packageConfig: { systemObjectId, stakingPoolId } 传给 walrus({...})。若要自定义存储节点请求(超时、重试、日志记录),请传入 storageNodeClientOptions: { fetch, timeout, onError }。
使用 WalrusFile API 读取
getFiles 接受 blob ID 和 quilt ID,并返回 WalrusFile 对象。请分批读取,以便客户端高效地从一个 quilt 加载多个文件:
const [file1, file2] = await client.walrus.getFiles({ ids: [anyBlobId, orQuiltId] });
const bytes = await file1.bytes(); // Uint8Array
const text = await file1.text(); // utf-8 string
const json = await file2.json(); // parsed JSON
// Files stored in a quilt also carry an identifier and tags
const identifier: string | null = await file1.getIdentifier();
const tags: Record<string, string> = await file1.getTags();
有了 blob ID 后,你还可以获取 WalrusBlob;如果它是 quilt,还可以按标识符、标签或 quilt ID 列出其中的文件:
const blob = await client.walrus.getBlob({ blobId });
const files = await blob.files();
const [readme] = await blob.files({ identifiers: ['README.md'] });
const textFiles = await blob.files({ tags: [{ 'content-type': 'text/plain' }] });
使用 WalrusFile API 写入
从 Uint8Array、Blob 或字符串创建文件,然后写入。一次 writeFiles 调用中的所有文件都会写入同一个 quilt,因此尽可能一次写入多个文件;对于单个文件,quilt 编码的效率较低。
import { WalrusFile } from '@mysten/walrus';
const file1 = WalrusFile.from({ contents: new Uint8Array([1, 2, 3]), identifier: 'file1.bin' });
const file2 = WalrusFile.from({
contents: new TextEncoder().encode('Hello from the TS SDK!!!\n'),
identifier: 'README.md',
tags: { 'content-type': 'text/plain' },
});
const results = await client.walrus.writeFiles({
files: [file1, file2],
epochs: 3,
deletable: true,
signer: keypair,
});
// results: { id, blobId, blobObject }[]
原始 blob:readBlob 和 writeBlob
const data = await client.walrus.readBlob({ blobId }); // Uint8Array
const { blobId } = await client.walrus.writeBlob({
blob: new TextEncoder().encode('Hello from the TS SDK!!!\n'),
deletable: false,
epochs: 3,
signer: keypair,
});
writeBlob 和 writeFiles 支持 onStep 和 resume,可实现崩溃后恢复上传:持久化 onStep 返回的每个步骤,并将保存的步骤作为 resume 传入,以便在崩溃后继续。
工作流:浏览器钱包和可恢复上传
浏览器钱包会打开弹出窗口以进行签名;如果弹出窗口不是直接响应用户操作而打开,可能会被拦截。writeFilesFlow 会拆分写入流程,让注册和认证交易可以分别通过各自的按钮进行签名:
const flow = client.walrus.writeFilesFlow({
files: [WalrusFile.from({ contents: new Uint8Array(fileData), identifier: 'my-file.txt' })],
});
await flow.encode();
// Button 1
async function handleRegister() {
const registerTx = flow.register({ epochs: 3, owner: currentAccount.address, deletable: true });
const result = await signAndExecuteTransaction({ transaction: registerTx });
if (result.$kind === 'FailedTransaction') {
throw new Error(`Registration failed: ${result.FailedTransaction.status.error?.message}`);
}
await flow.upload({ digest: result.Transaction.digest });
}
// Button 2
async function handleCertify() {
const certifyTx = flow.certify();
const result = await signAndExecuteTransaction({ transaction: certifyTx });
if (result.$kind === 'FailedTransaction') {
throw new Error(`Certification failed: ${result.FailedTransaction.status.error?.message}`);
}
const files = await flow.listFiles();
}
如果没有单独的用户交互,请将整个流程作为异步迭代器运行,并持久化每个步骤:
const flow = client.walrus.writeBlobFlow({ blob });
for await (const step of flow.run({ signer, epochs: 3, deletable: true })) {
await db.save(fileId, step); // a WriteBlobStep, persisted for crash recovery
}
// Resume: completed steps are skipped and only missing slivers are uploaded
const resumed = client.walrus.writeBlobFlow({ blob, resume: await db.load(fileId) });
executeRegister({ signer, epochs, deletable, owner }) 和 executeCertify({ signer }) 会代你进行签名,并返回类型化的步骤结果。
浏览器应用的上传中继
const client = new SuiGrpcClient({
network: 'testnet',
baseUrl: 'https://fullnode.testnet.sui.io:443',
}).$extend(
walrus({
uploadRelay: {
host: 'https://upload-relay.testnet.walrus.space',
sendTip: { max: 1_000 }, // maximum tip in MIST; the client reads the required tip itself
},
}),
);
host为必填项。主网:https://upload-relay.mainnet.walrus.space。测试网:https://upload-relay.testnet.walrus.space。- 中继会在
<host>/v1/tip-config发布其小费配置。小费类型为const(每个 blob 固定)或linear(由base加上perEncodedKib组成)。使用sendTip: { max }时,客户端最多支付该上限;若要手动设置小费,请传入sendTip: { address, kind: { const: 105 } }或kind: { linear: { base: 105, perEncodedKib: 10 } }。 - 如果所需小费超过
max,上传将失败并提示Tip amount exceeds the maximum allowed tip。请调高max。
依赖 blob 前先进行验证
写入结果并不能证明 blob 已持久存储。请读取 Blob 对象,并检查它已认证、仍在存储期限内且不可删除:
const blob = await walrusClient.getBlobObject(blobObjectId); // blobObject.id from the write result
const durable =
blob.certified_epoch != null && // null until certified
currentEpoch < blob.storage.end_epoch && // read the epoch from system state or `walrus info`
!blob.deletable;
if (!durable) throw new Error('Blob is not durably available yet; do not depend on it');
Sui 对象上的 blob_id 字段是 u256,而不是聚合器 URL 中使用的 base64 blob ID;请通过对象 ID 读取,而不是将该字段拼接到 URL 中。完整的可用性和完整性检查请参阅 walrus-blob-lifecycle。
错误处理
因 epoch 变更导致的过期缓存数据错误会继承 RetryableWalrusClientError。重置客户端并重试:
import { RetryableWalrusClientError } from '@mysten/walrus';
if (error instanceof RetryableWalrusClientError) {
client.walrus.reset();
// retry the operation
}
readBlob 等高级方法已会自动重试这些情况,并容忍部分节点失败。若要查看 SDK 吸收的各个存储节点错误,请传入 storageNodeClientOptions: { onError: (error) => console.log(error) }。
WASM 加载:Vite 和 Next.js
SDK 需要 WASM 绑定来编码和解码 blob。Node.js、Bun 和部分打包工具无需配置。如果打包工具无法定位二进制文件,请将客户端指向该文件:
// Vite
import walrusWasmUrl from '@mysten/walrus-wasm/web/walrus_wasm_bg.wasm?url';
const client = new SuiGrpcClient({ network: 'testnet', baseUrl: 'https://fullnode.testnet.sui.io:443' })
.$extend(walrus({ wasmUrl: walrusWasmUrl }));
// Or load from a CDN / self-host
walrus({ wasmUrl: 'https://unpkg.com/@mysten/walrus-wasm@latest/web/walrus_wasm_bg.wasm' });
// Next.js API routes (webpack and Turbopack alike)
const nextConfig: NextConfig = {
serverExternalPackages: ['@mysten/walrus', '@mysten/walrus-wasm'],
};
如果不使用 serverExternalPackages,将 WASM 加载器打包进服务器路由可能会导致其无法正确定位二进制文件;Turbopack 会将其显示为 /ROOT/... 之类的虚拟路径。如果无法使用它,请像 Vite 示例那样将 wasmUrl 指向 Web 构建产物。
已知的获取限制
- Node.js 默认的
connectTimeout为 10 秒,有些节点响应较慢;请在storageNodeClientOptions中通过自定义fetch提高超时时间(例如使用undici的Agent({ connectTimeout }))。 - 在 Bun 中,
abort信号会阻止请求返回响应,但 Promise 仍会等待操作完成后才拒绝。
规则
- 扩展
SuiGrpcClient。new SuiGrpcClient({ network, baseUrl }).$extend(walrus())。JSON-RPC 客户端已弃用。 - 使用 SDK 提供的方法名。 对文件和 quilts 使用
getFiles/writeFiles,对原始 blob 使用readBlob/writeBlob,对分步写入使用writeFilesFlow/writeBlobFlow。没有storeBlob。 - 写入时始终传入
epochs、deletable和signer。 signer 需要有 SUI 来支付 gas,有 WAL 来支付存储费用和写入费用。 - 在浏览器中使用上传中继。 设置
uploadRelay.host,并将sendTip.max设为足以满足中继/v1/tip-config要求的值。 - 依赖 blob 前先验证。 检查
Blob对象中的certified_epoch、storage.end_epoch和deletable。 - 遇到
RetryableWalrusClientError时重置。 调用client.walrus.reset()并重试;这不保证成功,但能清除跨 epoch 变化而过时的状态。 - 上传敏感数据前先加密。 所有 Walrus blob 都是公开的。
常见错误
- 调用
storeBlob。 该方法不存在。请使用writeBlob(原始字节)或writeFiles(文件,以 quilt 形式存储)。 - 将原始字符串或对象作为 blob 传入。
writeBlob接受Uint8Array;WalrusFile.from接受Uint8Array、Blob或字符串。使用new TextEncoder().encode(JSON.stringify(obj))转换 JSON。 - 在 Vite 中使用
optimizeDeps.exclude。 文档规定的修复方法是使用wasmUrl选项,并通过?url导入@mysten/walrus-wasm/web/walrus_wasm_bg.wasm;对于 Next.js 路由,则使用serverExternalPackages。 - 在浏览器中通过一次点击签署 register 和 certify。 第二个弹窗会被拦截。请使用
writeFilesFlow,并让用户对每笔交易分别进行一次交互。 - 使用
writeFiles写入许多单独的文件。 每次调用都会生成一个 quilt;请将多个文件合并到一次调用中。 - 将写入结果视为持久性保证。 已注册的 blob 不等于已认证的 blob。使用
getBlobObject重新读取Blob对象,并检查certified_epoch。 Tip amount exceeds the maximum allowed tip。 提高sendTip.max;检查中继的/v1/tip-config。- 未调用
reset()就重试。 epoch 变化后,客户端缓存的委员会信息会过时;重试RetryableWalrusClientError前先重置。 - 硬编码系统对象 ID。 SDK 会按网络打包这些 ID;仅在自定义部署时使用
packageConfig。