Skip to main content

Create a Digital Object Using Spore Protocol

⏰ Estimated Time: 5 - 10 min
πŸ”§ What You Will Need:

Tutorial Overview​

Spore is an on-chain digital object (DOB) protocol backed by CKB. An β€œon-chain” asset refers to a digital asset with its data directly encoded on the blockchain. A Spore Cell can hold any type of asset users want to store on-chain. The data structure for a Spore Cell is as follows:

data:
content-type: Bytes # String Bytes
content: Bytes
# OPTIONAL
cluster_id: Bytes
type:
hash_type: "data1"
code_hash: SPORE_TYPE_DATA_HASH
args: SPORE_ID
lock:
<user_defined>

Notice that the data field of the Spore Cell contains content-type and content, which allow users to transform any content into a digital object. All fields in a Spore Cell are immutable once created.

In this tutorial, you will learn how to build a simple dApp using the Spore SDK that converts a picture on your computer into a digital object on the blockchain.

Setup Devnet & Run Example​

Step 1: Download the Source Code​

To get started with the tutorial dApp, clone the repository and navigate to the appropriate directory:

git clone https://github.com/nervosnetwork/docs.nervos.org.git --depth 1
cd docs.nervos.org/examples/dApp/create-dob

You can also read the full code online or download it here.

Step 2: Start the Devnet​

To interact with the dApp, ensure that your Devnet is up and running. After installing @offckb/cli, open a terminal and start the Devnet with the following command:

offckb node

Keep the Devnet running throughout the tutorial. Open another terminal to list the pre-funded accounts:

offckb accounts

Private keys are hidden by default. If the tutorial asks you to copy a Devnet private key, append --show-private-keys:

offckb accounts --show-private-keys

Use plain offckb accounts for everyday account checks. These are public development keys for the local OffCKB Devnet; never use them for Mainnet or real assets. See OffCKB's account documentation.

Step 3: Run the Example​

Navigate to your project folder (docs.nervos.org/examples/dApp/create-dob), install the dependencies, and start running the example:

npm install
npm start

Now, the app is running at http://localhost:1234.


Behind the Scenes​

Open the lib.ts file in your project, it lists all the important functions that do the most of work for the project.

Create Digital Object​

Check out the createSporeDOB function:

export async function createSporeDOB(
privkey: string,
content: Uint8Array
): Promise<{ txHash: string; outputIndex: number }>;

It accepts two parameters,

  1. the private key that is used to sign and create the digital object
  2. the content to be stored in the digital object.

The content can be any type of data that is serialized into a Uint8Array. Here we are dealing with images, so the content is the result of FileReader.readAsArrayBuffer. You can check out the following code recipe in handleFileChange function from the react frontend index.tsx:

const reader = new FileReader();
reader.onload = () => {
// Access the file content here
const content = reader.result;
if (content && content instanceof ArrayBuffer) {
const uint8Array = new Uint8Array(content);
setFileContent(uint8Array);
}
};
// Read the file as ArrayBuffer
reader.readAsArrayBuffer(files[0]);

Once we have the picture content and the private key, we will build a transaction that produces a Spore output Cell, aka the digital object Cell. With the help of Spore-SDK, building the transaction becomes very simple:

export async function createSporeDOB(
privkey: string,
content: Uint8Array
): Promise<{ txHash: string; outputIndex: number }> {
const wallet = createDefaultLockWallet(privkey);

const { txSkeleton, outputIndex } = await createSpore({
data: {
contentType: "image/jpeg",
content,
},
toLock: wallet.lock,
fromInfos: [wallet.address],
config: SPORE_CONFIG,
});

const txHash = await wallet.signAndSendTransaction(txSkeleton);
console.log(`Spore created at transaction: ${txHash}`);
console.log(
`Spore ID: ${
txSkeleton.get("outputs").get(outputIndex)!.cellOutput.type!.args
}`
);
return { txHash, outputIndex };
}

Notice that the createDefaultLockWallet and const txHash = await wallet.signAndSendTransaction(txSkeleton); are just some methods that helps us to keep the code clean, all it does is the same as the previous tutorials involving signing and sending transactions.

Render Content from Digital Object​

Once we created our digital object on-chain, what we love to do is to render and show this digital object. To do this, we need to first find the Spore Cell of our digital object and extract the data from the Spore Cell and decode the content from the data to render it in the browser.

Check out the showSporeContent function:

export async function showSporeContent(txHash: string, index = 0) {
const indexHex = "0x" + index.toString(16);
const cell = await cccClient.getCellLive({ txHash, index: indexHex }, true);
if (cell == null) {
return alert("cell not found, please retry later");
}
const sporeData = unpackToRawSporeData(cell.outputData);
console.log("spore data: ", sporeData);
return sporeData;
}

We locate the Spore Cell by accepting a outpoint parameter(txHash and outputIndex), and use cccClient.getCellLive to get the Live Cell. Then we unpack the data content from this Cell:

const sporeData = unpackToRawSporeData(cell.outputData);

To render the image from this raw data, check out the renderSpore function in the index.tsx:

const renderSpore = async () => {
const res = await showSporeContent(txHash, outputIndex);
if (!res) return;
setRawSporeData(res);

// Create Blob from binary data
const buffer = hexStringToUint8Array(res.content.toString().slice(2));
const blob = new Blob([buffer], { type: res.contentType });
const url = URL.createObjectURL(blob);
setImageURL(url);
};

...
{imageURL && <img src={imageURL} />}

Congratulations!​

After following this tutorial, you have mastered how digital objects work on CKB. Here's a quick recap:

  • How Spore protocol works on CKB
  • Create an on-chain digital object with a picture via Spore-SDK
  • Render the picture in the browser from your digital object

Next Step​

To try the example on Testnet, stop the app with Ctrl+C, then run:

npm run start:testnet

Use a separate Testnet-only account funded with test CKB. Devnet balances and assets do not exist on Testnet. To return to Devnet, stop the app and run npm start with NETWORK unset.

For more details, check out the full source code.

Additional Resources​