Skip to main content
@ckb-ccc/spore implements the Spore Protocol — CKB’s standard for fully on-chain digital objects (DOBs). Spore cells store content entirely on-chain and encode ownership through CKB lock scripts, making them censorship-resistant and permanent. The package exports functions for managing both Spores (individual NFTs) and Clusters (named collections that group Spores together).

Installation

npm install @ckb-ccc/spore
import { spore } from "@ckb-ccc/shell"; // Node.js
// or
import { ccc } from "@ckb-ccc/connector-react"; // React (via ccc.spore)
If you install @ckb-ccc/spore directly, import from it:
import { createSpore, transferSpore, meltSpore } from "@ckb-ccc/spore";

SporeDataView

Every Spore cell holds a SporeDataView payload that describes its content:
type SporeDataView = {
  contentType: string;   // MIME type, e.g. "image/png" or "text/plain"
  content: Uint8Array;   // Raw bytes of the content
  clusterId?: ccc.Hex;   // Optional cluster this Spore belongs to
};

Spore functions

createSpore

Create a new Spore cell and return the signed transaction along with the new Spore ID.
async function createSpore(params: {
  signer: ccc.Signer;
  data: SporeDataView;
  to?: ccc.ScriptLike;
  clusterMode?: "lockProxy" | "clusterCell" | "skip";
  tx?: ccc.TransactionLike;
  scriptInfo?: SporeScriptInfoLike;
  scriptInfoHash?: ccc.HexLike;
}): Promise<{ tx: ccc.Transaction; id: ccc.Hex }>
ParameterDescription
signerThe signer that pays fees and signs the transaction.
dataContent, MIME type, and optional cluster ID for the new Spore.
toLock script of the initial owner. Defaults to the signer’s recommended lock.
clusterModeHow to handle the cluster cell when data.clusterId is set. Use "clusterCell" to include the cluster cell directly, "lockProxy" to use a proxy lock, or "skip" to omit cluster handling.
txOptional transaction skeleton to extend.

transferSpore

Move an existing Spore to a new owner.
async function transferSpore(params: {
  signer: ccc.Signer;
  id: ccc.HexLike;
  to: ccc.ScriptLike;
  tx?: ccc.TransactionLike;
  scripts?: SporeScriptInfoLike[];
  scriptInfoHash?: ccc.HexLike;
}): Promise<{ tx: ccc.Transaction }>

meltSpore

Destroy a Spore and reclaim its CKB capacity.
async function meltSpore(params: {
  signer: ccc.Signer;
  id: ccc.HexLike;
  tx?: ccc.TransactionLike;
  scripts?: SporeScriptInfoLike[];
  scriptInfoHash?: ccc.HexLike;
}): Promise<{ tx: ccc.Transaction }>

findSpore

Look up a single Spore by its ID. Returns undefined if not found.
async function findSpore(
  client: ccc.Client,
  id: ccc.HexLike,
  scripts?: SporeScriptInfoLike[],
): Promise<{ cell: ccc.Cell; spore: ccc.Cell; sporeData: SporeDataView; scriptInfo: SporeScriptInfo } | undefined>

assertSpore

Like findSpore, but throws if the Spore does not exist.
async function assertSpore(
  client: ccc.Client,
  args: ccc.HexLike,
  scripts?: SporeScriptInfoLike[],
): Promise<{ cell: ccc.Cell; scriptInfo: SporeScriptInfo }>

findSpores

Async generator that streams all Spores matching an optional lock script and/or cluster ID.
async function* findSpores(params: {
  client: ccc.Client;
  lock?: ccc.ScriptLike;
  clusterId?: ccc.HexLike;
  scriptInfos?: SporeScriptInfoLike[];
  limit?: number;
  order?: "asc" | "desc";
}): AsyncGenerator<{ cell: ccc.Cell; spore: ccc.Cell; sporeData: SporeDataView; scriptInfo: SporeScriptInfo }>

findSporesBySigner

Async generator that streams all Spores owned by a signer, with optional cluster filter.
async function* findSporesBySigner(params: {
  signer: ccc.Signer;
  order?: "asc" | "desc";
  limit?: number;
  clusterId?: ccc.HexLike;
  scriptInfos?: SporeScriptInfoLike[];
}): AsyncGenerator<{ cell: ccc.Cell; spore: ccc.Cell; sporeData: SporeDataView; scriptInfo: SporeScriptInfo }>
Pass clusterId: "" to findSpores or findSporesBySigner to return only public Spores — those not belonging to any cluster.

Cluster functions

Clusters group Spores into named collections. They are singleton cells that carry a name and description.

createSporeCluster

async function createSporeCluster(params: {
  signer: ccc.Signer;
  data: ClusterDataView;      // { name: string; description: string }
  to?: ccc.ScriptLike;
  tx?: ccc.TransactionLike;
  scriptInfo?: SporeScriptInfoLike;
  scriptInfoHash?: ccc.HexLike;
}): Promise<{ tx: ccc.Transaction; id: ccc.Hex }>

transferSporeCluster

async function transferSporeCluster(params: {
  signer: ccc.Signer;
  id: ccc.HexLike;
  to: ccc.ScriptLike;
  tx?: ccc.TransactionLike;
  scripts?: SporeScriptInfoLike[];
  scriptInfoHash?: ccc.HexLike;
}): Promise<{ tx: ccc.Transaction }>

findCluster / assertCluster

async function findCluster(
  client: ccc.Client,
  id: ccc.HexLike,
  scripts?: SporeScriptInfoLike[],
): Promise<{ cell: ccc.Cell; cluster: ccc.Cell; clusterData: ClusterDataView; scriptInfo: SporeScriptInfo } | undefined>

async function assertCluster(
  client: ccc.Client,
  args: ccc.HexLike,
  scripts?: SporeScriptInfoLike[],
): Promise<{ cell: ccc.Cell; scriptInfo: SporeScriptInfo }>

findSporeClustersBySigner / findSporeClusters

Async generators for enumerating clusters, equivalent to their Spore counterparts.

Example: create and transfer a Spore

1

Create a Spore

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

const client = new ccc.ClientPublicTestnet();
const signer = new ccc.SignerCkbPrivateKey(client, "0x<private-key>");

const { tx, id } = await createSpore({
  signer,
  data: {
    contentType: "text/plain",
    content: new TextEncoder().encode("Hello, Spore!"),
  },
});

// Complete capacity and fee, then send
await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);
console.log("Spore created:", id, "TX:", txHash);
2

Transfer the Spore

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

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

const { tx: transferTx } = await transferSpore({
  signer,
  id,
  to: newOwner,
});

await transferTx.completeInputsByCapacity(signer);
await transferTx.completeFeeBy(signer);
const transferHash = await signer.sendTransaction(transferTx);
console.log("Transferred:", transferHash);