Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ckb-devrel/ccc/llms.txt

Use this file to discover all available pages before exploring further.

Spore Protocol is an on-chain standard for creating permanent, ownable Digital Objects (DOBs) on CKB. A Spore encodes arbitrary content (images, text, JSON, etc.) directly on-chain and ties ownership to a CKB lock script. Because the data lives in a cell, ownership transfer is an atomic on-chain operation — no marketplace or indexer is required to verify it. Clusters are optional collections that group related Spores. A Cluster has a name and description; any Spore can reference a Cluster ID to declare membership.

Installation

npm install @ckb-ccc/spore
The package name is @ckb-ccc/spore (version 1.5.17).
import { createSpore, transferSpore, meltSpore } from "@ckb-ccc/spore";
import { createSporeCluster, transferSporeCluster } from "@ckb-ccc/spore";

Create a Spore

createSpore encodes your content and produces a transaction with a new Spore cell. The data field follows the Spore data format: a content type string and raw content bytes.
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!"),
  },
  // Optional: send the Spore to a different address
  // to: recipientLockScript,
});

// Balance capacity and pay fee
await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);

const txHash = await signer.sendTransaction(tx);
console.log("Spore created:", id);        // the sporeId (type args)
console.log("Transaction hash:", txHash);
The returned id is the sporeId — the args field of the Spore’s type script. Store it; you will need it for transfers and melts.

Spore inside a Cluster

If you want the Spore to belong to a Cluster, include clusterId in the data and set clusterMode:
const { tx, id } = await createSpore({
  signer,
  data: {
    contentType: "image/png",
    content: pngBytes,
    clusterId: "0xabc123...", // id returned by createSporeCluster
  },
  clusterMode: "lockProxy", // or "clusterCell"
});
clusterModeBehaviour
"lockProxy"Puts a cell with the Cluster’s lock in both inputs and outputs — lower cost
"clusterCell"Puts the Cluster cell itself in inputs and outputs — proves ownership directly
"skip"Skips cluster logic entirely — use only if you handle it yourself

Transfer a 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);

Melt a Spore

Melting destroys the Spore and reclaims its locked CKB capacity back to the signer:
import { meltSpore } from "@ckb-ccc/spore";

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

await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);
Melting is irreversible. The Spore’s content is removed from the chain and its CKB is released. There is no undo.

Clusters

Create a 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);

Transfer a 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);

Query Spores and Clusters

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

// Iterate all Spores owned by the signer
for await (const { spore, sporeData } of findSporesBySigner({ signer })) {
  console.log(spore.outPoint, sporeData.contentType);
}

// Find Spores in a specific Cluster
for await (const { sporeData } of findSporesBySigner({
  signer,
  clusterId: "0xabc123...",
})) {
  console.log(sporeData);
}

// Query all public Spores (no cluster) by lock
for await (const { spore } of findSporesBySigner({
  signer,
  clusterId: "", // empty string = public spores only
})) {
  console.log(spore.outPoint);
}

API reference

FunctionDescription
createSpore(params)Create a new Spore cell
transferSpore(params)Transfer a Spore to a new owner
meltSpore(params)Destroy a Spore and reclaim CKB
findSpore(client, id)Look up a single Spore by ID
findSpores(params)Query Spores by lock and/or cluster
findSporesBySigner(params)Query Spores owned by a signer
createSporeCluster(params)Create a new Cluster cell
transferSporeCluster(params)Transfer a Cluster to a new owner
findCluster(client, id)Look up a single Cluster by ID
findSporeClusters(params)Query Clusters by lock
findSporeClustersBySigner(params)Query Clusters owned by a signer

Build docs developers (and LLMs) love