Skip to content

Commit

Permalink
feat: add spore demo
Browse files Browse the repository at this point in the history
  • Loading branch information
sfsf332 authored and Hanssen0 committed Jan 9, 2025
1 parent e7a0f36 commit c412df8
Show file tree
Hide file tree
Showing 10 changed files with 630 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/real-geckos-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ckb-ccc/spore": patch
---

refactor(spore): unified findSpore(s) interface
1 change: 0 additions & 1 deletion packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"devDependencies": {
"@ckb-ccc/connector-react": "workspace:*",
"@ckb-ccc/lumos-patches": "workspace:*",
"@ckb-ccc/udt": "workspace:*",
"@ckb-lumos/ckb-indexer": "^0.24.0-next.1",
"@ckb-lumos/common-scripts": "^0.24.0-next.1",
"@ckb-lumos/config-manager": "^0.24.0-next.1",
Expand Down
198 changes: 198 additions & 0 deletions packages/demo/src/app/connected/(tools)/CreateCluster/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"use client";

import React, { useEffect, useState } from "react";
import { TextInput } from "@/src/components/Input";
import { Button } from "@/src/components/Button";
import { useGetExplorerLink } from "@/src/utils";
import { useApp } from "@/src/context";
import { ButtonsPanel } from "@/src/components/ButtonsPanel";
import { ccc, spore } from "@ckb-ccc/connector-react";
import { Textarea } from "@/src/components/Textarea";

function generateClusterDescriptionUnderDobProtocol(
client: ccc.Client,
): string {
/**
* Generation example for DOB0
*/
const clusterDescription = "My First DOB Cluster";
const dob0Pattern: spore.dob.PatternElementDob0[] = [
{
traitName: "BackgroundColor",
dobType: "String",
dnaOffset: 0,
dnaLength: 1,
patternType: "options",
traitArgs: ["red", "blue", "green", "black", "white"],
},
{
traitName: "FontSize",
dobType: "Number",
dnaOffset: 1,
dnaLength: 1,
patternType: "range",
traitArgs: [10, 50],
},
{
traitName: "FontFamily",
dobType: "String",
dnaOffset: 2,
dnaLength: 1,
patternType: "options",
traitArgs: ["Arial", "Helvetica", "Times New Roman", "Courier New"],
},
{
traitName: "Timestamp",
dobType: "Number",
dnaOffset: 3,
dnaLength: 4,
patternType: "rawNumber",
},
{
traitName: "ConsoleLog",
dobType: "String",
dnaOffset: 7,
dnaLength: 13,
patternType: "utf8",
},
];

/**
* Generation example for DOB1
*/
const dob1Pattern: spore.dob.PatternElementDob1[] = [
{
imageName: "IMAGE.0",
svgFields: "attributes",
traitName: "",
patternType: "raw",
traitArgs: "xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 200'",
},
{
imageName: "IMAGE.0",
svgFields: "elements",
traitName: "Timestamp",
patternType: "options",
traitArgs: [
[
[0, 1000000],
"<image width='300' height='200' href='btcfs://b2f4560f17679d3e3fca66209ac425c660d28a252ef72444c3325c6eb0364393i0' />",
],
[
["*"],
"<image width='300' height='200' btcfs://eb3910b3e32a5ed9460bd0d75168c01ba1b8f00cc0faf83e4d8b67b48ea79676i0 />",
],
],
},
{
imageName: "IMAGE.0",
svgFields: "elements",
traitName: "BackgroundColor",
patternType: "options",
traitArgs: [
["red", "<rect width='20' height='20' x='5' y='5' fill='red' />"],
["blud", "<rect width='20' height='20' x='20' y='5' fill='blue' />"],
["green", "<rect width='20' height='20' x='5' y='20' fill='green' />"],
[["*"], "<rect width='20' height='20' x='20' y='20' fill='pink' />"],
],
},
{
imageName: "IMAGE.0",
svgFields: "elements",
traitName: "ConsoleLog",
patternType: "options",
traitArgs: [
[
"hello, world!",
"<image width='100' height='100' href='ipfs://QmeQ6TfqzsjJCMtYmpbyZeMxiSzQGc6Aqg6NyJTeLYrrJr' />",
],
[
["*"],
"<image width='100' height='100' href='ipfs://QmYiiN8EXxAnyddatCbXRYzwU9wwAjh21ms4KEJodxg8Fo' />",
],
],
},
];
const dob1: spore.dob.Dob1 = {
description: clusterDescription,
dob: {
ver: 1,
decoders: [
{
decoder: spore.dob.getDecoder(client, "dob0"),
pattern: dob0Pattern,
},
{
decoder: spore.dob.getDecoder(client, "dob1"),
pattern: dob1Pattern,
},
],
},
};
return spore.dob.encodeClusterDescriptionForDob1(dob1);
}
export default function CreateCluster() {
const { signer, createSender } = useApp();
const { client } = ccc.useCcc();
const { log } = createSender("Create Cluster");

const { explorerTransaction } = useGetExplorerLink();

const [name, SetName] = useState<string>("");
const [description, setDescription] = useState<string>("");

useEffect(() => {
setDescription(
JSON.stringify(
JSON.parse(generateClusterDescriptionUnderDobProtocol(client)),
undefined,
2,
),
);
}, [client]);

return (
<div className="flex w-full flex-col items-stretch">
<TextInput
label="Name"
placeholder="Cluster Name"
state={[name, SetName]}
/>
<Textarea
label="Description"
placeholder="Cluster Description"
rows={10}
state={[description, setDescription]}
/>

<ButtonsPanel>
<Button
onClick={async () => {
if (!signer) return;
const { tx, id } = await spore.createSporeCluster({
signer,

data: {
name,
description: JSON.stringify(JSON.parse(description)),
},
});
await tx.completeFeeBy(signer);

const txHash = await signer.sendTransaction(tx);
log(
"Transaction sent:",
explorerTransaction(txHash),
"Cluster ID:",
id,
);
await signer.client.waitTransaction(txHash);
log("Transaction committed:", explorerTransaction(txHash));
}}
>
Create Cluster
</Button>
</ButtonsPanel>
</div>
);
}
101 changes: 101 additions & 0 deletions packages/demo/src/app/connected/(tools)/MintSpore/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client";

import React, { useCallback, useEffect, useState } from "react";
import { TextInput } from "@/src/components/Input";
import { Button } from "@/src/components/Button";
import { useGetExplorerLink } from "@/src/utils";
import { useApp } from "@/src/context";
import { ButtonsPanel } from "@/src/components/ButtonsPanel";
import { Dropdown } from "@/src/components/Dropdown";
import { ccc, spore } from "@ckb-ccc/connector-react";

export default function MintSpore() {
const { signer, createSender } = useApp();
const { log } = createSender("Mint Spore");
const { explorerTransaction } = useGetExplorerLink();
const [dna, setDna] = useState<string>("");
const [clusterId, setClusterId] = useState<string>("");
const [clusterList, setClusterList] = useState([
{
id: "",
name: "Mint Without Cluster",
},
]);

const mintSpore = useCallback(async () => {
if (!signer) return;

const content = `{"dna":"${ccc.hexFrom(dna).slice(2)}"}`;
// Build transaction
const { tx, id } = await spore.createSpore({
signer,
data: {
contentType: "dob/1",
content: ccc.bytesFrom(content, "utf8"),
clusterId: clusterId === "" ? undefined : clusterId,
},
clusterMode: clusterId === "" ? "skip" : "clusterCell",
});
await tx.completeFeeBy(signer);

const txHash = await signer.sendTransaction(tx);
log("Transaction sent:", explorerTransaction(txHash), "Spore ID:", id);
await signer.client.waitTransaction(txHash);
log("Transaction committed:", explorerTransaction(txHash));
}, [signer, log, clusterId, dna, explorerTransaction]);

useEffect(() => {
if (!signer) {
return;
}

(async () => {
const list = [
{
id: "",
name: "Mint Without Cluster",
},
];

for await (const {
cluster,
clusterData,
} of spore.findSporeClustersBySigner({
signer,
order: "desc",
})) {
if (!cluster.cellOutput.type?.args) {
continue;
}

list.push({
id: cluster.cellOutput.type.args,
name: clusterData.name,
});
}

setClusterList(list);
})();
}, [signer]);

return (
<div className="flex w-full flex-col items-stretch">
<TextInput label="DNA" placeholder="Spore DNA" state={[dna, setDna]} />

<label className="mt-4 text-sm">Select a Cluster (optional)</label>
<Dropdown
className="mt-2"
options={clusterList.map((cluster) => ({
name: cluster.id,
displayName: cluster.name,
iconName: "Wheat",
}))}
selected={clusterId}
onSelect={setClusterId}
/>
<ButtonsPanel>
<Button onClick={mintSpore}>Mint Spore</Button>
</ButtonsPanel>
</div>
);
}
Loading

0 comments on commit c412df8

Please sign in to comment.