@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 }>
| Parameter | Description |
|---|
signer | The signer that pays fees and signs the transaction. |
data | Content, MIME type, and optional cluster ID for the new Spore. |
to | Lock script of the initial owner. Defaults to the signer’s recommended lock. |
clusterMode | How 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. |
tx | Optional 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
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);
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);