mcpField
W
walrus-ts-sdk

walrus-ts-sdk

github.com/mystenlabs开发344 次安装1 周前更新
代码仓库

关于 walrus-ts-sdk

walrus-ts-sdk 是使用官方 `@mysten/walrus` TypeScript SDK 读写 Walrus Blob 和文件的技能,介绍直接访问存储节点、上传中继及可恢复的分步写入流程。

主要功能

  1. 01读写 Blob 与文件
  2. 02处理 Quilt 中的文件
  3. 03通过上传中继支持浏览器上传
  4. 04恢复中断的写入流程
  5. 05配置 Sui 与 Walrus 客户端
  6. 06使用前检查 Blob 可用性

适用场景

  1. 01在 TypeScript 应用中接入 Walrus 存储
  2. 02通过浏览器钱包流程上传
  3. 03恢复中断的 Blob 上传
  4. 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)。

本技能中的所有模式均源自:

如果不确定某个 SDK API 的用法,请先获取相关页面。完整的方法列表见 WalrusClient TypeDocs。


相关技能

主题技能适用场景
CLI 操作walrus-cli使用 walrus 二进制文件而非 SDK
HTTP APIwalrus-http-api使用任何语言调用 REST 端点
机密性walrus-data-security上传前加密 blob
Blob 管理walrus-blob-lifecycle延长、删除或共享 blob;验证可用性
Quiltswalrus-quilts将许多小 blob 批量合并为一个单元
Move 集成walrus-move-integration在 Move 合约中封装 Walrus blob

技能内容

核心概念
  • 客户端设置。 创建 Sui 客户端,并通过 Walrus 扩展它:new SuiGrpcClient({ network, baseUrl }).$extend(walrus())。扩展通过 client.walrus 访问。新代码不要使用已弃用的 JSON-RPC SuiClient。
  • 两种 API 层级。 WalrusFile API(getFiles、writeFiles)可处理以普通 blob 和 quilt 形式存储的数据,是推荐的入口。底层的 readBlob / writeBlob API 则用于单个 Uint8Array blob。
  • 工作流。 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 仍会等待操作完成后才拒绝。
规则
  1. 扩展 SuiGrpcClient。 new SuiGrpcClient({ network, baseUrl }).$extend(walrus())。JSON-RPC 客户端已弃用。
  2. 使用 SDK 提供的方法名。 对文件和 quilts 使用 getFiles / writeFiles,对原始 blob 使用 readBlob / writeBlob,对分步写入使用 writeFilesFlow / writeBlobFlow。没有 storeBlob。
  3. 写入时始终传入 epochs、deletable 和 signer。 signer 需要有 SUI 来支付 gas,有 WAL 来支付存储费用和写入费用。
  4. 在浏览器中使用上传中继。 设置 uploadRelay.host,并将 sendTip.max 设为足以满足中继 /v1/tip-config 要求的值。
  5. 依赖 blob 前先验证。 检查 Blob 对象中的 certified_epoch、storage.end_epoch 和 deletable。
  6. 遇到 RetryableWalrusClientError 时重置。 调用 client.walrus.reset() 并重试;这不保证成功,但能清除跨 epoch 变化而过时的状态。
  7. 上传敏感数据前先加密。 所有 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。

评论

支持 Markdown