Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@ckb-ccc/connector-react": "workspace:*",
"@ckb-ccc/lumos-patches": "workspace:*",
"@ckb-ccc/ssri": "workspace:*",
"@ckb-ccc/type-id": "workspace:*",
"@ckb-ccc/udt": "workspace:*",
"@ckb-lumos/ckb-indexer": "0.24.0-next.2",
"@ckb-lumos/common-scripts": "0.24.0-next.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"use client";

import { Button } from "@/src/components/Button";
import { Message } from "@/src/components/Message";
import { useGetExplorerLink } from "@/src/utils";
import { ccc } from "@ckb-ccc/connector-react";
import { FileCode, X } from "lucide-react";
import type { DeployResult } from "./deployLogic";

function formatCellCreationDate(timestampMs: number): string {
try {
return new Date(timestampMs).toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} catch {
return "";
}
}

export function TypeIdCellListItem({
cell,
index,
onSelect,
isSelected,
creationTimestamp,
}: {
cell: ccc.Cell;
index: number;
onSelect: () => void;
isSelected: boolean;
creationTimestamp?: number;
}) {
const outPoint = `${cell.outPoint.txHash}:${cell.outPoint.index}`;

return (
<button
type="button"
onClick={onSelect}
className={`flex w-full flex-col gap-2 rounded-lg border p-4 text-left text-sm transition-colors hover:border-purple-300 hover:bg-purple-50/50 ${
isSelected
? "border-purple-400 bg-purple-50"
: "border-gray-200 bg-white"
}`}
>
Comment on lines +40 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add aria-pressed to the cell selection button.

TypeIdCellListItem signals selection only through border and background colors. Screen-reader users receive no selection state. The list is the primary control for choosing which cell to update or burn, so the state matters.

♿ Proposed change
     <button
       type="button"
       onClick={onSelect}
+      aria-pressed={isSelected}
       className={`flex w-full flex-col gap-2 rounded-lg border p-4 text-left text-sm transition-colors hover:border-purple-300 hover:bg-purple-50/50 ${
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
onClick={onSelect}
className={`flex w-full flex-col gap-2 rounded-lg border p-4 text-left text-sm transition-colors hover:border-purple-300 hover:bg-purple-50/50 ${
isSelected
? "border-purple-400 bg-purple-50"
: "border-gray-200 bg-white"
}`}
>
<button
type="button"
onClick={onSelect}
aria-pressed={isSelected}
className={`flex w-full flex-col gap-2 rounded-lg border p-4 text-left text-sm transition-colors hover:border-purple-300 hover:bg-purple-50/50 ${
isSelected
? "border-purple-400 bg-purple-50"
: "border-gray-200 bg-white"
}`}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/demo/src/app/connected/`(tools)/DeployScript/deployComponents.tsx
around lines 40 - 48, Update the selection button in TypeIdCellListItem to
include an aria-pressed attribute bound to isSelected, exposing the current
selection state to assistive technologies while preserving the existing visual
styling and onClick behavior.

<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2 font-semibold text-gray-700">
<span>#{index + 1}</span>
<span>Type ID</span>
</div>
<span className="text-xs text-gray-600">
<span className="font-medium">Occupied / Capacity:</span>{" "}
{ccc.fixedPointToString(ccc.fixedPointFrom(cell.occupiedSize))} /{" "}
{ccc.fixedPointToString(cell.cellOutput.capacity)} CKB
</span>
</div>
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1">
<span
className="min-w-0 truncate font-mono text-xs text-gray-600"
title={outPoint}
>
<span className="font-sans font-medium">Out Point:</span> {outPoint}
</span>
<span className="shrink-0 text-xs text-gray-500">
{creationTimestamp == null
? "Unavailable"
: formatCellCreationDate(creationTimestamp)}
</span>
</div>
</button>
);
}

export function CellFoundSection({
foundCell,
onClear,
}: {
foundCell: ccc.Cell;
onClear: () => void;
}) {
const { explorerTransaction } = useGetExplorerLink();
const typeScript = foundCell.cellOutput.type;
const typeId = typeScript?.args;

return (
<div>
<div className="flex items-center justify-between text-gray-800">
<div className="flex items-center gap-2">
<FileCode className="h-5 w-5 text-purple-500" />
<p className="font-semibold">Cell to Update</p>
</div>
<button
type="button"
onClick={onClear}
className="rounded-full p-1 text-gray-400 transition-colors hover:text-gray-700"
aria-label="Clear selected cell"
title="Clear selection"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="mt-3 space-y-1 text-sm text-gray-700">
<p>
<span className="font-medium">Out Point:</span>{" "}
{explorerTransaction(
foundCell.outPoint.txHash,
`${foundCell.outPoint.txHash}:${foundCell.outPoint.index}`,
)}
</p>
<p>
<span className="font-medium">Occupied / Capacity:</span>{" "}
{ccc.fixedPointToString(ccc.fixedPointFrom(foundCell.occupiedSize))} /{" "}
{ccc.fixedPointToString(foundCell.cellOutput.capacity)} CKB
</p>
<p>
<span className="font-medium">Data Hash:</span>{" "}
<span className="font-mono break-all">
{ccc.hashCkb(foundCell.outputData ?? "0x")}
</span>
</p>
{typeScript && (
<p>
<span className="font-medium">Type Hash:</span>{" "}
<span className="font-mono break-all">{typeScript.hash()}</span>
</p>
)}
{typeId && typeId !== "0x" && (
<p>
<span className="font-medium">Type ID:</span>{" "}
<span className="font-mono break-all">{typeId}</span>
</p>
)}
</div>
</div>
);
}

export function DeploymentResultSection({
result,
}: {
result: DeployResult & {
immutable: boolean;
action: "deployed" | "updated";
};
}) {
const { explorerTransaction } = useGetExplorerLink();
const outPoint = `${result.txHash}:0`;

return (
<Message
title={`Cell ${result.action === "deployed" ? "Deployed" : "Updated"}`}
type="success"
expandable={false}
>
<div className="space-y-1 text-sm text-gray-700">
<p>
<span className="font-medium">Out Point:</span>{" "}
{explorerTransaction(result.txHash, outPoint)}
</p>
<p>
<span className="font-medium">Type ID:</span>{" "}
<span className="font-mono break-all">{result.typeId}</span>
</p>
<p>
<span className="font-medium">Data Hash:</span>{" "}
<span className="font-mono break-all">{result.dataHash}</span>
</p>
{result.immutable && (
<p className="text-green-700">
This cell is immutable and can never be updated.
</p>
)}
</div>
</Message>
);
}

export function BurnButton({
onClick,
disabled,
}: {
onClick: () => void;
disabled?: boolean;
}) {
return (
<Button
variant="danger"
className="ml-2"
onClick={onClick}
disabled={disabled}
>
Burn
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { readFileAsBytes } from "@/src/app/utils/(tools)/FileUpload/page";
import { ccc } from "@ckb-ccc/connector-react";
import { createTypeId, transferTypeId } from "@ckb-ccc/type-id";
import { ReactNode } from "react";
import { createImmutableLock } from "./helpers";

export type Logger = (...args: ReactNode[]) => void;
export type DeployResult = {
txHash: string;
typeId: string;
dataHash: string;
};

export async function runDeploy(
signer: ccc.Signer,
file: File,
immutable: boolean,
foundCell: ccc.Cell | null,
log: Logger,
): Promise<DeployResult> {
const fileBytes = (await readFileAsBytes(file)) as ccc.Bytes;

let tx: ccc.Transaction;
let typeIdArgsValue: string;

if (foundCell) {
const typeId = foundCell.cellOutput.type?.args;
if (!typeId) {
throw new Error("Selected cell does not have a Type ID");
}
log("Updating existing Type ID cell...");

({ tx } = await transferTypeId({
client: signer.client,
id: typeId,
receiver: immutable ? createImmutableLock() : foundCell.cellOutput.lock,
data: fileBytes,
}));
typeIdArgsValue = typeId;
} else {
log("Building transaction...");
const created = await createTypeId({
signer,
data: fileBytes,
receiver: immutable ? createImmutableLock() : undefined,
});
tx = created.tx;
typeIdArgsValue = created.id;
log("Type ID created:", typeIdArgsValue);
}

await tx.completeFeeBy(signer);
log("Sending transaction...");
const txHash = await signer.sendTransaction(tx);
log("Transaction sent:", txHash);
return {
txHash,
typeId: typeIdArgsValue,
dataHash: ccc.hashCkb(fileBytes),
};
}

/** Burn the selected type_id cell: consume it and send capacity back to the lock (no type script). */
export async function runBurn(
signer: ccc.Signer,
foundCell: ccc.Cell,
log: Logger,
): Promise<string | null> {
const { lock } = foundCell.cellOutput;
const tx = ccc.Transaction.from({
inputs: [{ previousOutput: foundCell.outPoint }],
outputs: [{ lock, capacity: ccc.Zero }],
outputsData: ["0x"],
});
await tx.addCellDepsOfKnownScripts(signer.client, ccc.KnownScript.TypeId);
await tx.completeFeeChangeToOutput(signer, 0);
log("Sending burn transaction...");
const txHash = await signer.sendTransaction(tx);
log("Transaction sent:", txHash);
return txHash;
}
16 changes: 16 additions & 0 deletions packages/demo/src/app/connected/(tools)/DeployScript/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ccc } from "@ckb-ccc/connector-react";

/** Normalize Type ID args (strip 0x, trim). */
export function normalizeTypeIdArgs(args: string): string {
const s = (args || "").trim();
return s.startsWith("0x") ? s.slice(2) : s;
}

/** Create an unspendable lock script for immutable cells. */
export function createImmutableLock(): ccc.Script {
return ccc.Script.from({
codeHash: `0x${"00".repeat(32)}`,
hashType: "data",
args: "0x",
});
}
Loading