指南

Spore 协议

使用 Spore 协议 SDK 创建和管理链上数码物(DOB)。

在 GitHub 上编辑

直接在 CKB 区块链上创建永久可拥有的链上数码物(Digital Object,简称 DOB)。Spore 将任意内容(图片、文本、JSON 等)编码存入 Cell——所有权由 Lock 脚本强制执行,转移是原子性的链上操作,不依赖任何市场或索引器。

完成本指南后你将能够

  • 创建 Spore——将内容(文本、图片、JSON……)永久存入链上
  • 转移与销毁 Spore——变更所有权,或销毁并取回 CKB
  • 用 Cluster 组织 Spore——将相关 Spore 归入命名集合
  • 查询 Spore——遍历持有的 Spore,并按 Cluster 过滤

以下示例均假设你已有一个已连接的 signer。请先阅读连接钱包(浏览器端)或 Node.js 后端(服务端)。

核心概念

  • Spore——包含任意内容的 Cell,附带赋予其唯一 ID 的 Type 脚本。内容永久存储,所有权可转移。
  • Cluster——可选的集合 Cell,包含名称和描述。任意 Spore 均可通过 Cluster ID 来确定其归属。
  • 销毁(Melt)——销毁 Spore Cell 并取回其锁定的 CKB 容量,操作不可逆。

安装

npm install @ckb-ccc/spore
import { createSpore, transferSpore, meltSpore } from "@ckb-ccc/spore";
import { createSporeCluster, transferSporeCluster } from "@ckb-ccc/spore";

创建 Spore

createSpore 将内容编码存入新的 Spore Cell。提供 MIME 内容类型和原始内容字节,链上数据打包由 CCC 处理:

import { ccc } from "@ckb-ccc/ccc";
import { createSpore } from "@ckb-ccc/spore";

const { tx, id } = await createSpore({
  signer,
  data: {
    contentType: "text/plain",
    content: new TextEncoder().encode("Hello, Spore!"),
  },
  // 可选:将 Spore 发送至其他地址
  // to: recipientLockScript,
});

// 填充容量并支付手续费
await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);

const txHash = await signer.sendTransaction(tx);
console.log("Spore ID:", id);              // "0x..." — 唯一的 Type 脚本 args,请妥善保存!
console.log("Transaction hash:", txHash);  // "0x..." — 32 字节交易哈希

返回的 id 即为 sporeId——Spore Type 脚本的 args 字段。请立即保存,后续转移和销毁操作均需用到。

在 Cluster 中创建 Spore

如需将 Spore 归入某个 Cluster,在 data 中传入 clusterId 并设置 clusterMode

const { tx, id } = await createSpore({
  signer,
  data: {
    contentType: "image/png",
    content: pngBytes,
    clusterId: "0xabc123...", // createSporeCluster 返回的 id
  },
  clusterMode: "lockProxy", // 或 "clusterCell"
});
clusterMode行为
"lockProxy"在输入和输出中各放入一个与 Cluster 同 Lock 的代理 Cell,成本较低
"clusterCell"将 Cluster Cell 本身放入输入和输出,直接证明所有权,成本较高
"skip"完全跳过 Cluster 逻辑,仅在自行处理 Cluster 的输入输出时使用

转移 Spore

适用场景:变更已有 Spore 的所有权(如出售、赠送或迁移至其他地址)。

import { transferSpore } from "@ckb-ccc/spore";

const { script: newOwner } = await ccc.Address.fromString(
  recipientAddress,
  signer.client,
);

const { tx } = await transferSpore({
  signer,
  id: sporeId,   // "0x..."
  to: newOwner,
});

await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);

销毁 Spore

适用场景:永久销毁 Spore 并取回其锁定的 CKB 容量:

import { meltSpore } from "@ckb-ccc/spore";

const { tx } = await meltSpore({
  signer,
  id: sporeId,
});

await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);

销毁操作不可逆。Spore 的内容将从链上移除,CKB 随之释放,无法撤销。

Cluster

Cluster 是可选的命名集合。先创建 Cluster,再在创建 Spore 时引用其 ID。

创建 Cluster

import { createSporeCluster } from "@ckb-ccc/spore";

const { tx, id: clusterId } = await createSporeCluster({
  signer,
  data: {
    name: "My Collection",
    description: "A collection of on-chain art.",
  },
});

await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);
console.log("Cluster ID:", clusterId); // "0x..." — 创建 Spore 时作为 clusterId 传入

转移 Cluster

import { transferSporeCluster } from "@ckb-ccc/spore";

const { script: newOwner } = await ccc.Address.fromString(
  recipientAddress,
  signer.client,
);

const { tx } = await transferSporeCluster({
  signer,
  id: clusterId,
  to: newOwner,
});

await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);
await signer.sendTransaction(tx);

查询 Spore 和 Cluster

所有查询函数均返回异步生成器(AsyncGenerator)——按需流式返回结果,处理大型集合时无需将全部数据加载到内存。

import { findSporesBySigner, findSporeClusters } from "@ckb-ccc/spore";

// 遍历 signer 持有的所有 Spore
for await (const { spore, sporeData } of findSporesBySigner({ signer })) {
  console.log(spore.outPoint, sporeData.contentType);
}

// 过滤:仅返回指定 Cluster 中的 Spore
for await (const { sporeData } of findSporesBySigner({
  signer,
  clusterId: "0xabc123...",
})) {
  console.log(sporeData);
}

// 过滤:仅返回公开 Spore(不属于任何 Cluster)
for await (const { spore } of findSporesBySigner({
  signer,
  clusterId: "", // 空字符串 = 仅公开 Spore
})) {
  console.log(spore.outPoint);
}

API 参考

函数说明
createSpore(params)创建新的 Spore Cell
transferSpore(params)将 Spore 转移至新所有者
meltSpore(params)销毁 Spore 并取回 CKB
findSpore(client, id)按 ID 查询单个 Spore
findSpores(params)按 Lock 和/或 Cluster 查询 Spore
findSporesBySigner(params)查询 signer 持有的 Spore
createSporeCluster(params)创建新的 Cluster Cell
transferSporeCluster(params)将 Cluster 转移至新所有者
findCluster(client, id)按 ID 查询单个 Cluster
findSporeClusters(params)按 Lock 查询 Cluster
findSporeClustersBySigner(params)查询 signer 持有的 Cluster

常见问题

createSpore 报"not enough capacity"错误 Spore Cell 将内容存储在链上,所需 CKB 容量远超普通转账。文本 Spore 约需 200 CKB 以上,图片 Spore 可能需要数千 CKB。请确保 signer 余额充足。

创建后 Spore ID 丢失 createSpore 返回的 id 是 Spore Cell 的 Type 脚本 args,由首个输入和输出索引通过确定性的方式计算得出,应在创建后立即保存。也可通过 findSporesBySigner 找回。

clusterMode 该选哪个?

  • "lockProxy"(推荐)——成本较低,通过与 Cluster 同 Lock 的代理 Cell 证明集合归属。
  • "clusterCell"——成本较高,将实际 Cluster Cell 纳入交易,适用于 Cluster 的 Lock 与 signer 不同的情况。
  • "skip"——仅在自行处理 Cluster 输入输出时使用。

下一步

  • 组装交易——了解每个 Spore 操作通用的声明 → 填充 → 手续费 → 发送模式。
  • UDT 代币——在 CKB 上发行同质化代币。
  • Node.js 后端——从服务端脚本批量铸造 Spore。

最后更新于

目录