Skip to main content

Create a Digital Object Using Spore Protocol

Tutorial Overview

⏰ Estimated Time: 5 - 10 min
💡 Topics: DOB, NFT, Spore Protocol
🔧 Tools You Need:

Spore Protocol on CKB

Spore is an on-chain digital object (DOB)

protocol backed by CKB. An "on-chain" asset refers to a digital asset whose data is directly encoded onto the blockchain. A Spore Cell can hold any type of assets users want to store on-chain, the following data structure is used in the Spore Cell:

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 turn any content form into a digital object. All the fields in a Spore Cell are immutable once created.

In this tutorial, we will build a simple dApp to turn a picture on your computer into a digital object on the blockchain using the Spore SDK.

Setup Devnet & Run Example

Step 1: Clone the Repository

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

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

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

You might want to check pre-funded accounts and copy private keys for later use. Open another terminal and execute:

offckb accounts

Step 3: Run the Example

Navigate to your project, install the node dependencies, and start running the example:

yarn && NETWORK=devnet yarn start

Now, the app is running in http://localhost:1234


Behind the Scene

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. We can handle all the logic with Lumos.js, but with the help of Spore-SDK, it becomes very simple to do:

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!

By following this tutorial this far, you have mastered how digital-object works 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

So now your dApp works great on the local blockchain, you might want to switch it to different environments like Testnet and Mainnet.

To do that, just change the environment variable NETWORK to testnet:

export NETWORK=testnet

For more details, check out the README.md.

Additional Resources