### Install Lumos and Crypto-Browserify Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx Command to install the necessary `@ckb-lumos/lumos` library and `crypto-browserify` dependency using npm for use in a project. ```bash npm install @ckb-lumos/lumos crypto-browserify --save ``` -------------------------------- ### Install Spore SDK (Bash) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Install the `@spore-sdk/core` package using npm. This SDK provides utilities for interacting with the Spore Protocol and simplifies transaction creation. ```bash npm install @spore-sdk/core --save ``` -------------------------------- ### Start Development Server (Bash) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/index.mdx Starts the local development server for the Next.js application, typically making the application accessible via http://localhost:3000. ```bash npm run dev ``` -------------------------------- ### Installing wagmi and viem (Bash) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx Use this command to install the necessary libraries `wagmi` and `viem` via npm. These libraries are essential for interacting with Ethereum wallets like MetaMask in your project. ```Bash npm install wagmi viem --save ``` -------------------------------- ### Install react-remark for Markdown Rendering (Bash) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/manage-blog-post.mdx Installs the react-remark library using npm, which is required to render Markdown content within the React component for displaying blog posts. ```bash npm install --save react-remark ``` -------------------------------- ### Initializing Blog Site Homepage with Spore SDK (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx This code defines a Next.js page component that fetches and displays details for a specific blog site (represented by a Spore Cluster). It uses the router to get the site ID, queries the CKB indexer for the corresponding Cluster cell using the Spore SDK, unpacks the data, and updates the component state to display the site's name and description. It also includes basic wallet connection logic. ```TypeScript import useWallet from '@/hooks/useWallet'; import { Indexer } from '@ckb-lumos/lumos'; import { getSporeScript, unpackToRawClusterData } from '@spore-sdk/core'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import { Site } from '..'; import { config } from '@/config'; export default function SitePage() { const router = useRouter(); const { id } = router.query; const { lock, isConnected, connect } = useWallet(); const [siteInfo, setSiteInfo] = useState(); useEffect(() => { if (!id) { return; } (async () => { const indexer = new Indexer(config.ckbIndexerUrl); const { script } = getSporeScript(config, 'Cluster'); const collector = indexer.collector({ type: { ...script, args: id as string }, }); for await (const cell of collector.collect()) { const unpacked = unpackToRawClusterData(cell.data); setSiteInfo({ id: cell.cellOutput.type!.args, name: unpacked.name, description: unpacked.description, }); } })(); }, [id, lock]); return (

{siteInfo?.name}

{siteInfo?.description}

{isConnected ? ( ) : ( )}
); } ``` -------------------------------- ### Install Spore SDK with npm (Shell) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/resources/spore-sdk.mdx Installs the Spore SDK core package using the npm package manager. This command downloads the necessary dependencies and makes the SDK available in your project. ```shell npm i @spore-sdk/core ``` -------------------------------- ### Install Node Dependencies with pnpm Source: https://github.com/sporeprotocol/spore-docs/blob/main/README.md This command installs all necessary project dependencies using the pnpm package manager. It is the first step required to set up the project for local development. ```shell pnpm i ``` -------------------------------- ### Install Spore SDK with pnpm (Shell) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/resources/spore-sdk.mdx Installs the Spore SDK core package using the pnpm package manager. This command adds the package to your project's dependencies efficiently. ```shell pnpm add @spore-sdk/core ``` -------------------------------- ### Install Spore SDK with yarn (Shell) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/resources/spore-sdk.mdx Installs the Spore SDK core package using the yarn package manager. This command adds the package to your project's dependencies. ```shell yarn add @spore-sdk/core ``` -------------------------------- ### Preparing Spore Transaction for Signing (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-first-spore/explore-code.md Illustrates the initial steps required to prepare a transaction skeleton (`txSkeleton`) for signing, specifically for the SECP256K1_BLAKE160 lock script. It involves getting the Spore configuration, accessing the script details, and using `prepareSigningEntries` to generate the messages that need to be signed by the wallet. This is a prerequisite step before the actual signing. ```TypeScript import { SporeConfig, getSporeConfig } from '@spore-sdk/core'; // Generate messages to be signed const config: SporeConfig = getSporeConfig(); const Secp256k1Blake160 = config.lumos.SCRIPTS['SECP256K1_BLAKE160']!; txSkeleton = secp256k1Blake160.prepareSigningEntries(txSkeleton, { config: config.lumos }); // Sign transaction messages with private key from the `wallet` txSkeleton = wallet.signTransaction(txSkeleton); ``` -------------------------------- ### Run Spore Docs in Development Mode Source: https://github.com/sporeprotocol/spore-docs/blob/main/README.md Execute this command to start the documentation site locally in development mode. This allows you to edit files and see changes reflected immediately in your browser. ```shell pnpm run start ``` -------------------------------- ### Wallet Interface Definition in TypeScript Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-first-spore/explore-code.md Defines the structure of the Wallet type used in the example. It includes properties for the CKB lock script and address, and methods for signing messages, transactions, and signing/sending transactions. ```tsx import { Script, Address, HexString, Hash } from '@ckb-lumos/base'; interface Wallet { lock: Script; address: Address; signMessage(message: HexString): Hash; signTransaction(txSkeleton: helpers.TransactionSkeletonType): helpers.TransactionSkeletonType; signAndSendTransaction(txSkeleton: helpers.TransactionSkeletonType): Promise; } ``` -------------------------------- ### Configure DOB Pattern with Code Hash Decoders (Simplified) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/dob/dob1-protocol.md Provides an alternative configuration for the DOB pattern, focusing on using `code_hash` for both decoders. This version is presented without comments and is intended for documentation purposes, demonstrating a simpler setup for defining data fields and their rendering rules. ```typescript {\n description: "Collection for docs.spore.pro",\n dob: {\n ver: 1,\n decoders: [\n {\n decoder: {\n type: "code_hash",\n hash: "0x13cac78ad8482202f18f9df4ea707611c35f994375fa03ae79121312dda9925c"\n },\n pattern: [\n [\n "Face",\n "String",\n 0,\n 1,\n "options",\n [\n "Laugh",\n "Smile",\n "Sad",\n "Angry"\n ]\n ],\n [\n "Age",\n "Number",\n 1,\n 1,\n "range",\n [\n 0,\n 100\n ]\n ],\n [\n "BirthMonth",\n "Number",\n 2,\n 1,\n "options",\n [\n 1,\n 2,\n 3,\n 4,\n 5,\n 6,\n 7,\n 8,\n 9,\n 10,\n 11,\n 12\n ]\n ],\n [\n "Score",\n "Number",\n 3,\n 1,\n "rawNumber"\n ]\n ]\n },\n {\n decoder: {\n type: "code_hash",\n hash: "0xda3525549b72970b4c95f5b5749357f20d1293d335710b674f09c32f7d54b6dc"\n },\n pattern: [\n [\n "IMAGE.0",\n "attributes",\n "",\n "raw",\n "xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500'"\n ],\n [\n "IMAGE.0",\n "attributes",\n "Face",\n "options",\n [\n [\n "Laugh",\n "fill='#FFFF00'"\n ],\n [\n "Smile",\n "fill='#FF00FF'"\n ],\n [\n "Sad",\n ``` -------------------------------- ### Creating Immortal Spore with Specific Content Type Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/recipes/Create/create-immortal-spore.md This example shows how to use `data.contentTypeParameters` alongside a specific `contentType`, such as 'image/jpeg', to create an immortal spore of that type. The parameters are automatically merged into the final `contentType` string. ```TypeScript await createSpore({ data: { content: CONTENT_AS_BYTES, contentType: 'image/jpeg', contentTypeParameters: { immortal: true, }, }, ... }); ``` -------------------------------- ### Spore Post Content JSON Structure Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/manage-blog-post.mdx This snippet provides an example of the JSON structure used to store the title and content of a blog post within the Spore cell's `content` field. The `contentType` is set to 'application/json' to indicate this format, allowing the data to be parsed correctly when retrieved from the blockchain. ```JSON { "title": "My post", "content": "Hello World" } ``` -------------------------------- ### Encoding DOB DNA Formats (JavaScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/dob/dob0-protocol.md Provides examples of how different DNA formats (string, array, object property) can be encoded into byte groups using the `bytifyRawString` helper function from `@spore-sdk/helpers/buffer` for storage in the DOB content field. ```javascript bytifyRawString(JSON.stringify("df4ffcb5e7a283ea7e6f09a504d0e256")) // or bytifyRawString(JSON.stringify(["df4ffcb5e7a283ea7e6f09a504d0e256"])) // or bytifyRawString(JSON.stringify({ "dna": "df4ffcb5e7a283ea7e6f09a504d0e256" })) // or [0, 223, 79, 252, 181, 231, 162, 131, 234, 126, 111, 9, 165, 4, 208, 226, 86] ``` -------------------------------- ### Refuel Spore Zero-fee Transfer using transferSpore (TSX) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/recipes/Transfer/modify-0-fee-transfer.md Demonstrates how to use the transferSpore function from @spore-sdk/core to add capacity margin to a Spore. This effectively 'refuels' the Zero-fee Transfer feature, allowing the spore to cover more future transaction fees. The example shows setting a capacity margin and disabling the use of the existing margin for the current transaction fee. ```TSX import { transferSpore } from '@spore-sdk/core'; import { BI } from '@ckb-lumos/lumos'; let { txSkeleton } = await transferSpore({ outPoint: SPORE_OUTPOINT, toLock: OWNER_LOCK, fromInfos: [SPONSOR_ADDRESS], // highlight-start capacityMargin: BI.from(1_0000_0000), // Add 1 CKB as margin, default is 0 useCapacityMarginAsFee: false, // Disable the feature // highlight-end }); ``` -------------------------------- ### Setting up Site Creation Form (React/TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Initializes state variables for site name and description using React's useState hook. Renders a form allowing users to input these values and a button to submit. Includes basic wallet connection status display and buttons. ```tsx import { Indexer, RPC } from '@ckb-lumos/lumos'; import { useEffect, useState } from 'react'; import { createCluster, unpackToRawClusterData, getSporeScript } from '@spore-sdk/core'; import { signTransaction } from '@/utils/transaction'; import useWallet from '@/hooks/useWallet'; export default function Home() { const { address, lock, balance, isConnected, connect, disconnect } = useWallet(); const [siteName, setSiteName] = useState(''); const [siteDescription, setSiteDescription] = useState(''); if (!isConnected) { return ; } return (
CKB Address: {address}
Balance: {balance?.toNumber() ?? 0} CKB

Create Site

setSiteName(e.target.value)} />
setSiteDescription(e.target.value)} />
); } ``` -------------------------------- ### Initializing Wagmi Configuration (TypeScript/TSX) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx Include this code in your `src/pages/_app.tsx` file to initialize the Wagmi configuration. It sets up auto-connection and configures a public client using `viem` to interact with the Ethereum mainnet. ```TypeScript import type { AppProps } from 'next/app'; import { WagmiConfig, createConfig, mainnet } from 'wagmi'; import { createPublicClient, http } from 'viem'; const config = createConfig({ autoConnect: true, publicClient: createPublicClient({ chain: mainnet, transport: http(), }), }); export default function App({ Component, pageProps }: AppProps) { return ( ); } ``` -------------------------------- ### Setting up DOB Decoder Server (Bash) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/dob/Introduction.mdx These commands clone the standalone DOB decoder server repository from GitHub and then run the server using `cargo run`. The `RUST_LOG` environment variable is set to enable debug logging for the server. By default, the server runs on CKB Testnet; configuration needs to be swapped for Mainnet. ```bash $ git clone https://github.com/sporeprotocol/dob-decoder-standalone-server $ RUST_LOG=dob_decoder_server=debug cargo run ``` -------------------------------- ### Configure Next.js for Browser Polyfills Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx Configuration for `next.config.js` to add webpack fallbacks for Node.js modules like `crypto` and `buffer`, making `@ckb-lumos/lumos` compatible with browser environments. ```javascript const webpack = require('webpack'); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, webpack: (config) => { // <- Add some webpack config config.resolve.fallback = { ...config.resolve.fallback, crypto: require.resolve('crypto-browserify'), buffer: require.resolve('buffer'), encoding: false, path: false, fs: false, stream: false, }; config.plugins = [ ...config.plugins, new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }), ]; return config; }, }; module.exports = nextConfig; ``` -------------------------------- ### Import Dependencies for Cluster Creation (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Import necessary functions like `createCluster` from `@spore-sdk/core`, the custom `signTransaction` utility, and the application's Spore configuration for use in the site creation logic. ```tsx import { createCluster, unpackToRawClusterData } from '@spore-sdk/core'; import { signTransaction } from '@/utils/transaction'; import { config } from '@/config'; ``` -------------------------------- ### Importing useWallet Hook in Index Page (TSX) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Imports the custom `useWallet` React hook into the `index.tsx` page. This hook encapsulates the wallet connection and address derivation logic, making the page component cleaner. ```TSX import useWallet from '@/hooks/useWallet'; ``` -------------------------------- ### Set up CKB Balance Query Utility Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx This snippet creates a utility function `getCapacities` in `src/utils/balance.ts` to query the CKB balance for a given address on the Nervos CKB testnet using @ckb-lumos/lumos. It initializes the indexer, sets up a collector for the address, and sums the capacities of the collected cells. ```tsx import { Indexer, config, BI, helpers } from '@ckb-lumos/lumos'; const CKB_RPC_URL = 'https://testnet.ckb.dev/rpc'; const CKB_INDEXER_URL = 'https://testnet.ckb.dev/indexer'; const indexer = new Indexer(CKB_INDEXER_URL, CKB_RPC_URL); export async function getCapacities(address: string): Promise { config.initializeConfig(config.predefined.AGGRON4); const collector = indexer.collector({ lock: helpers.parseAddress(address), type: 'empty', data: '0x', }); let capacities = BI.from(0); for await (const cell of collector.collect()) { capacities = capacities.add(cell.cellOutput.capacity); } return capacities; } ``` -------------------------------- ### Implement CKB Transaction Signing (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Define an asynchronous function `signTransaction` that takes a Lumos transaction skeleton, prepares signing entries, signs messages using wagmi, handles ECDSA recovery ID, and packs the signature into the witness for CKB transactions. ```tsx import { commons, config, helpers } from '@ckb-lumos/lumos'; import { blockchain } from '@ckb-lumos/base'; import { bytes } from '@ckb-lumos/codec'; import { signMessage } from 'wagmi/actions'; export async function signTransaction( txSkeleton: helpers.TransactionSkeletonType, ) { config.initializeConfig(config.predefined.AGGRON4); let tx = commons.omnilock.prepareSigningEntries(txSkeleton); const signedWitnesses = new Map(); const signingEntries = tx.get('signingEntries')!; for (let i = 0; i < signingEntries.size; i += 1) { const entry = signingEntries.get(i)!; if (entry.type === 'witness_args_lock') { const { message, index } = entry; if (signedWitnesses.has(message)) { const signedWitness = signedWitnesses.get(message)!; tx = tx.update('witnesses', (witnesses) => { return witnesses.set(index, signedWitness); }); continue; } let signature = await signMessage({ message: { raw: message } as any }); // Fix ECDSA recoveryId v parameter // let v = Number.parseInt(signature.slice(-2), 16); if (v >= 27) v -= 27; signature = ('0x' + signature.slice(2, -2) + v.toString(16).padStart(2, '0')) as `0x${string}`; const signedWitness = bytes.hexify( blockchain.WitnessArgs.pack({ lock: commons.omnilock.OmnilockWitnessLock.pack({ signature: bytes.bytify(signature!).buffer, }), }), ); signedWitnesses.set(message, signedWitness); tx = tx.update('witnesses', (witnesses) => { return witnesses.set(index, signedWitness); }); } } const signedTx = helpers.createTransactionFromSkeleton(tx); return signedTx; } ``` -------------------------------- ### Querying and Displaying User Sites (React/TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Defines a TypeScript type Site for structuring site data. Adds state to store an array of Site objects. Implements a useEffect hook that fetches "Cluster" cells associated with the connected wallet's lock script using the CKB indexer and Spore SDK, updates the sites state, and renders a list of site names. ```tsx import { Indexer, RPC } from '@ckb-lumos/lumos'; import { useEffect, useState } from 'react'; import { createCluster, unpackToRawClusterData, getSporeScript } from '@spore-sdk/core'; import { signTransaction } from '@/utils/transaction'; import useWallet from '@/hooks/useWallet'; import { config } from '@/config'; export type Site = { id: string; name: string; description: string; }; export default function Home() { const { address, lock, balance, isConnected, connect, disconnect } = useWallet(); const [siteName, setSiteName] = useState(''); const [siteDescription, setSiteDescription] = useState(''); const [sites, setSites] = useState([]); // ... useEffect(() => { if (!lock) { return; } (async () => { const indexer = new Indexer(config.ckbIndexerUrl); const { script } = getSporeScript(config, 'Cluster'); const collector = indexer.collector({ type: { ...script, args: '0x' }, lock, }); const sites = []; for await (const cell of collector.collect()) { const unpacked = unpackToRawClusterData(cell.data); sites.push({ id: cell.cellOutput.type!.args, name: unpacked.name, description: unpacked.description, }); } setSites(sites); })(); }, [lock]); // ... return (
CKB Address: {address}
Balance: {balance?.toNumber() ?? 0} CKB

Create Site

setSiteName(e.target.value)} />
setSiteDescription(e.target.value)} />

My Sites

    {sites.map((site) => (
  • {site.name}
  • ))}
); } ``` -------------------------------- ### Using useWallet Hook in Index Page Component (TSX) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Utilizes the `useWallet` hook within the `Home` functional component to access the connected wallet's address, lock script, balance, connection status, and connection/disconnection functions. It replaces previous inline wallet logic. ```TSX // highlight-start import { Indexer, RPC } from '@ckb-lumos/lumos'; import { useEffect, useState } from 'react'; // highlight-end import useWallet from '@/hooks/useWallet'; export default function Home() { // highlight-start const { address, lock, balance, isConnected, connect, disconnect } = useWallet(); // highlight-end } ``` -------------------------------- ### Serve Production Build Locally Source: https://github.com/sporeprotocol/spore-docs/blob/main/README.md After building the production version, use this command to run a local server and preview the production build. This is useful for testing features that behave differently in production. ```shell pnpm run serve ``` -------------------------------- ### Implementing Wallet Connection Hook with Lumos and Wagmi (TSX) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Defines a React hook `useWallet` to manage wallet connection state, retrieve the connected Ethereum address, derive the corresponding CKB lock script and address using Lumos, fetch the CKB balance, and provide functions for connecting and disconnecting the wallet. It depends on `@ckb-lumos/lumos`, `react`, `wagmi`, and a local `getCapacities` utility. ```TSX import { BI, commons, config, helpers } from '@ckb-lumos/lumos'; import { useEffect, useMemo, useState } from 'react'; import { useAccount, useConnect, useDisconnect } from 'wagmi'; import { InjectedConnector } from 'wagmi/connectors/injected'; import { getCapacities } from '@/utils/balance'; export default function useWallet() { const { address: ethAddress, isConnected } = useAccount(); const { connect } = useConnect({ connector: new InjectedConnector(), }); const { disconnect } = useDisconnect(); const [balance, setBalance] = useState(null); const lock = useMemo(() => { if (!ethAddress) return; return commons.omnilock.createOmnilockScript( { auth: { flag: 'ETHEREUM', content: ethAddress ?? '0x' }, }, { config: config.predefined.AGGRON4 }, ); }, [ethAddress]); const address = useMemo( () => lock ? helpers.encodeToAddress(lock, { config: config.predefined.AGGRON4, }) : undefined, [lock], ); useEffect(() => { if (!address) { return; } getCapacities(address).then((capacities) => { setBalance(capacities.div(10 ** 8)); }); }, [address]); return { address, lock, balance, isConnected, connect, disconnect, }; } ``` -------------------------------- ### Export Spore Testnet Configuration (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Initialize and export the predefined Spore Testnet configuration from `@spore-sdk/core`. This configuration includes Lumos settings and is used globally for Spore interactions. ```ts import { predefinedSporeConfigs, setSporeConfig } from '@spore-sdk/core'; import { config as lumosConfig } from '@ckb-lumos/lumos'; const config = predefinedSporeConfigs.Testnet; lumosConfig.initializeConfig(config.lumos); setSporeConfig(config); export { config, }; ``` -------------------------------- ### Connecting and Displaying MetaMask Address (TypeScript/TSX) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx Add this code to your `src/pages/index.tsx` to implement the wallet connection logic. It uses Wagmi hooks to connect to MetaMask via `InjectedConnector`, displays the connected ETH address, and provides buttons for connecting and disconnecting. ```TypeScript import { useAccount, useConnect, useDisconnect } from 'wagmi'; import { InjectedConnector } from 'wagmi/connectors/injected'; export default function Home() { const { address, isConnected } = useAccount(); const { connect } = useConnect({ connector: new InjectedConnector(), }); const { disconnect } = useDisconnect(); return (
{address}
{isConnected ? ( ) : ( )}
); } ``` -------------------------------- ### Build Spore Docs for Production Source: https://github.com/sporeprotocol/spore-docs/blob/main/README.md This command builds the documentation site for production deployment. It compiles and optimizes the site assets, preparing them for serving. ```shell pnpm run build ``` -------------------------------- ### Handle Spore Cluster Creation (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx Implement an asynchronous function `handleCreateSite` that uses `createCluster` to build a transaction skeleton for creating a Spore Cluster, signs it using the custom `signTransaction` function, and sends the signed transaction to the CKB RPC node. ```tsx export default function Home() { // ... // highlight-start const handleCreateSite = async (e: React.FormEvent) => { e.preventDefault(); if (!address || !lock) return; const { txSkeleton } = await createCluster({ data: { name: siteName, description: siteDescription, }, fromInfos: [address], toLock: lock, }); const tx = await signTransaction(txSkeleton); const rpc = new RPC(config.ckbNodeUrl); const hash = await rpc.sendTransaction(tx, 'passthrough'); console.log(hash); }; // highlight-end return (
// ...
); } ``` -------------------------------- ### Fetching and Displaying Site Info and Posts using Spore SDK Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/manage-blog-post.mdx This code snippet, located in `src/pages/site/[id].tsx`, fetches site information (Cluster data) and associated blog posts (Spore data) using the CKB Indexer and Spore SDK. It filters Spore by `contentType` ('application/json') and `clusterId`, parses the content as JSON to extract title and content, and updates the component's state to display the site details and a list of post titles as links. ```TSX import useWallet from '@/hooks/useWallet'; import { Indexer } from '@ckb-lumos/lumos'; // highlight-start import { getSporeScript, bufferToRawString, unpackToRawClusterData, unpackToRawSporeData, } from '@spore-sdk/core'; // highlight-end import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import { Site } from '..'; import { config } from '@/config'; // highlight-start import Link from 'next/link'; export type Post = { id: string; title: string; content: string; }; // highlight-end export default function SitePage() { const router = useRouter(); const { id } = router.query; const { lock, isConnected, connect } = useWallet(); const [siteInfo, setSiteInfo] = useState(); // highlight-next-line const [posts, setPosts] = useState([]); useEffect(() => { if (!id) { return; } (async () => { const indexer = new Indexer(config.ckbIndexerUrl); const { script } = getSporeScript(config, 'Cluster'); const collector = indexer.collector({ type: { ...script, args: id as string }, }); for await (const cell of collector.collect()) { const unpacked = unpackToRawClusterData(cell.data); setSiteInfo({ id: cell.cellOutput.type!.args, name: unpacked.name, description: unpacked.description, }); } })(); // highlight-start (async () => { const indexer = new Indexer(config.ckbIndexerUrl); const { script } = getSporeScript(config, 'Spore'); const collector = indexer.collector({ type: { ...script, args: '0x' }, lock, }); const posts = []; for await (const cell of collector.collect()) { const unpacked = unpackToRawSporeData(cell.data); const { contentType } = unpacked; if (contentType !== 'application/json' || unpacked.clusterId !== id) { continue; } const { title, content } = JSON.parse(bufferToRawString(unpacked.content)) ?? {}; if (title && content) { posts.push({ id: cell.cellOutput.type!.args, title, content, }); } } setPosts(posts); })(); // highlight-end }, [id, lock]); // ... return (

{siteInfo?.name}

{siteInfo?.description}

{isConnected ? ( ) : ( )} {/* highlight-start */}

Posts

    {posts.map((post) => (
  • {post.title}
  • ))}
{/* highlight-end */}
); } ``` -------------------------------- ### Linking to Blog Site Homepage from Index Page (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/create-new-site.mdx This code modifies the src/pages/index.tsx file to add navigation links to the individual blog site homepages. It imports the Link component from next/link and wraps each site name in the list with a Link component, dynamically setting the href based on the site's unique ID. ```TypeScript // ... // highlight-next-line import Link from 'next/link'; // ... export default function Home() { // ... return (
{/* ... */}

My Sites

    {sites.map((site) => (
  • {/* highlight-start */} {site.name} {/* highlight-end */}
  • ))}
); } ``` -------------------------------- ### Integrate and Display CKB Balance in React Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx This snippet demonstrates how to use the `getCapacities` function within a React component (`src/pages/index.tsx`) to fetch and display the CKB balance. It uses wagmi to connect a wallet, derives the CKB address from the connected Ethereum address using Omnilock, fetches the balance using `useEffect`, and displays the address and balance (converted from shannons to CKB) on the page. ```tsx import { useAccount, useConnect, useDisconnect } from 'wagmi'; import { InjectedConnector } from 'wagmi/connectors/injected'; // highlight-start import { BI, commons, config, helpers } from '@ckb-lumos/lumos'; import { useEffect, useMemo, useState } from 'react'; import { getCapacities } from '../utils/balance'; // highlight-end export default function Home() { const { address: ethAddress, isConnected } = useAccount(); const { connect } = useConnect({ connector: new InjectedConnector(), }); const { disconnect } = useDisconnect(); // highlight-next-line const [balance, setBalance] = useState(null); const address = useMemo(() => { if (!ethAddress) return; const lock = commons.omnilock.createOmnilockScript({ auth: { flag: 'ETHEREUM', content: ethAddress ?? '0x' }, }); return helpers.encodeToAddress(lock, { config: config.predefined.AGGRON4 }); }, [ethAddress]); // highlight-start useEffect(() => { if (!address) { return; } getCapacities(address).then((capacities) => { setBalance(capacities.div(10 ** 8)); }); }, [address]); return (
{isConnected ? (
CKB Address: {address}
Balance: {balance?.toNumber() ?? 0} CKB
) : ( )}
); // highlight-end } ``` -------------------------------- ### Generate CKB Address from ETH Address (Lumos) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx React/TypeScript code using `wagmi` and `@ckb-lumos/lumos` to connect to an Ethereum wallet, retrieve the ETH address, and use `useMemo` to generate a corresponding CKB address via the Omnilock script. ```tsx import { useAccount, useConnect, useDisconnect } from 'wagmi'; import { InjectedConnector } from 'wagmi/connectors/injected'; // highlight-start import { commons, config, helpers } from '@ckb-lumos/lumos'; import { useMemo } from 'react'; // highlight-end export default function Home() { // highlight-next-line const { address: ethAddress, isConnected } = useAccount(); const { connect } = useConnect({ connector: new InjectedConnector(), }); const { disconnect } = useDisconnect(); // highlight-start const address = useMemo(() => { if (!ethAddress) return; const lock = commons.omnilock.createOmnilockScript({ auth: { flag: 'ETHEREUM', content: ethAddress ?? '0x' }, }); return helpers.encodeToAddress(lock, { config: config.predefined.AGGRON4 }); }, [ethAddress]); // highlight-end return (
{address}
{isConnected ? ( ) : ( )}
); } ``` -------------------------------- ### Constructing Spore Creation Transaction (TypeScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-first-spore/explore-code.md Demonstrates calling the `createSpore` API to construct a transaction skeleton for minting a new Spore. It includes setting the `data` field with `contentType` ('image/jpeg') and `content` (obtained from `fetchLocalFile`), specifying the owner's lock script in `toLock`, and listing sponsor addresses in `fromInfos` to cover storage and fees. The API returns a `txSkeleton` and the `outputIndex` of the new Spore cell. ```TypeScript const { txSkeleton, outputIndex } = await createSpore({ data: { contentType: 'image/jpeg', content: await fetchLocalFile('./image.jpg'), }, toLock: wallet.lock, fromInfos: [wallet.address], }); ``` -------------------------------- ### Creating DOB/0 Cluster with Spore SDK (JavaScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/dob/dob0-protocol.md Demonstrates how to use the Spore SDK `createCluster` function to mint a Spore Cluster configured for the DOB/0 protocol, embedding the DOB metadata JSON into the cluster's description field. ```jsx import { createCluster } from '@spore-sdk/api'; import { bytifyRawString } from '@spore-sdk/helpers/buffer'; const account = ...; const dob_metadata = { description: 'this is the description for cluster', dob: { ver: 0, decoder: { type: 'code_hash', hash: '...' }, pattern: [["Age", "Number", 1, 1, "range", [0, 100]]], } }; const { txSkeleton, outputIndex } = await createCluster({ data: { name: 'My First DOB Cluster', description: bytifyRawString(JSON.strinify(dob_metadata)), }, fromInfos: [account.address], toLock: account.lock }); // sign for txSkeleton ``` -------------------------------- ### Display Generated CKB Address Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/connect-wallet.mdx Updates the React component to display the generated CKB address instead of the connection button when the wallet is connected, showing the result of the address generation. ```tsx import { useAccount, useConnect, useDisconnect } from 'wagmi'; import { InjectedConnector } from 'wagmi/connectors/injected'; import { commons, config, helpers } from '@ckb-lumos/lumos'; import { useMemo } from 'react'; export default function Home() { const { address: ethAddress, isConnected } = useAccount(); const { connect } = useConnect({ connector: new InjectedConnector(), }); const { disconnect } = useDisconnect(); const address = useMemo(() => { if (!ethAddress) return; const lock = commons.omnilock.createOmnilockScript({ auth: { flag: 'ETHEREUM', content: ethAddress ?? '0x' }, }); return helpers.encodeToAddress(lock, { config: config.predefined.AGGRON4 }); }, [ethAddress]); // highlight-start return (
{isConnected ? (
CKB Address: {address}
) : ( )}
); } // highlight-end ``` -------------------------------- ### Minting DOB with Spore SDK (JavaScript) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/dob/dob0-protocol.md Illustrates the process of minting a Spore (acting as a DOB) using the Spore SDK `createSpore` function, specifying the 'dob/0' content type, embedding the DNA in the content field, and linking it to a pre-configured DOB cluster. ```jsx import { createSpore } from '@spore-sdk/api'; import { bytifyRawString } from '@spore-sdk/helpers/buffer'; const account = ...; const dob_cluster_id = ...; const dob_content = { dna: 'df4ffcb5e7a283ea7e6f09a504d0e256' }; const { txSkeleton, outputIndex } = await createSpore({ data: { content_type: 'dob/0', content: bytifyRawString(JSON.strinify(dob_content)), cluster_id: dob_cluster_id }, fromInfos: [account.address], toLock: account.lock }); // sign for txSkeleton ``` -------------------------------- ### Create Next.js Page to Display Spore Post (TypeScript/React) Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/tutorials/create-on-chain-blog/manage-blog-post.mdx Implements a Next.js dynamic route page ([id].tsx) that fetches a Spore Cell by its ID from the URL, unpacks and parses its JSON content (title, content), and renders the title and Markdown content using react-remark. It uses Lumos Indexer to find the cell. ```typescript import { Indexer } from '@ckb-lumos/lumos'; import { SporeData, bufferToRawString, getSporeScript, } from '@spore-sdk/core'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import { useRemark } from 'react-remark'; import { Post } from '../site/[id]'; import { config } from '@/config'; export default function Post() { const router = useRouter(); const { id } = router.query; const [post, setPost] = useState(); const [reactContent, setMarkdownSource] = useRemark(); useEffect(() => { if (!id) { return; } (async () => { const indexer = new Indexer(config.ckbIndexerUrl); const { script } = getSporeScript(config, 'Spore'); const collector = indexer.collector({ type: { ...script, args: id as string }, }); for await (const cell of collector.collect()) { const unpacked = SporeData.unpack(cell.data); const { title, content } = JSON.parse(bufferToRawString(unpacked.content)) ?? {}; if (title && content) { setPost({ id: cell.cellOutput.type!.args, outPoint: cell.outPoint!, title, content, }); return; } } })(); }, [id]); useEffect(() => { setMarkdownSource(post?.content ?? ''); }, [post, setMarkdownSource]); return (

{post?.title}

{reactContent}
); } ``` -------------------------------- ### Importing Spore SDK and Lumos Dependencies Source: https://github.com/sporeprotocol/spore-docs/blob/main/docs/recipes/Create/create-public-cluster.mdx Imports necessary modules from `@spore-sdk/core` for cluster creation, `@ckb-lumos/codec` for data encoding, and `@ckb-lumos/lumos` for key derivation and address encoding. ```TypeScript import { predefinedSporeConfig, createCluster } from '@spore-sdk/core'; import { bytes, number } from '@ckb-lumos/codec'; import { hd, helpers } from '@ckb-lumos/lumos; ```