From 4ce61a48de4cd3e4a27ea9fe8ca37ecc6b58ddb5 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 17:10:22 +0800 Subject: [PATCH 01/15] feat: add CCC Agent Skills for AI coding assistants Add a set of Agent Skills (agentskills.io spec) that teach AI tools how to build on CKB with the CCC SDK: - Hub skill: ckb-ccc-fundamentals (Cell model, package selection, address/amount handling, hallucination guard) - Spoke skills: ckb-ccc-signer-setup, ckb-ccc-transactions, ckb-ccc-udt, ckb-ccc-spore, ckb-ccc-playground, ckb-ccc-examples-finder Installable via `npx skills add ckb-devrel/ccc`. Includes a maintenance README documenting version bump discipline and update workflow. --- skills/README.md | 22 ++ skills/ckb-ccc-examples-finder/SKILL.md | 87 ++++++ skills/ckb-ccc-fundamentals/SKILL.md | 255 +++++++++++++++++ skills/ckb-ccc-playground/SKILL.md | 131 +++++++++ skills/ckb-ccc-signer-setup/SKILL.md | 150 ++++++++++ skills/ckb-ccc-spore/SKILL.md | 212 ++++++++++++++ skills/ckb-ccc-transactions/SKILL.md | 94 ++++++ skills/ckb-ccc-udt/SKILL.md | 365 ++++++++++++++++++++++++ 8 files changed, 1316 insertions(+) create mode 100644 skills/README.md create mode 100644 skills/ckb-ccc-examples-finder/SKILL.md create mode 100644 skills/ckb-ccc-fundamentals/SKILL.md create mode 100644 skills/ckb-ccc-playground/SKILL.md create mode 100644 skills/ckb-ccc-signer-setup/SKILL.md create mode 100644 skills/ckb-ccc-spore/SKILL.md create mode 100644 skills/ckb-ccc-transactions/SKILL.md create mode 100644 skills/ckb-ccc-udt/SKILL.md diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..6409c728 --- /dev/null +++ b/skills/README.md @@ -0,0 +1,22 @@ +# CCC Agent Skills — Maintenance + +This directory holds the [Agent Skills](https://agentskills.io/specification) that teach AI coding assistants how to build on CKB with the CCC SDK: one hub skill (`ckb-ccc-fundamentals`) plus a spoke skill per task area (`ckb-ccc-signer-setup`, `ckb-ccc-transactions`, `ckb-ccc-udt`, `ckb-ccc-spore`, `ckb-ccc-playground`, `ckb-ccc-examples-finder`). + +They're consumed two ways: +- Directly from this repo via [`npx skills add ckb-devrel/ccc`](https://github.com/vercel-labs/skills), which tracks installs in the consumer's `skills-lock.json` and can later `npx skills check` / `npx skills update` against whatever's on `master`. +- As raw `SKILL.md` URLs, listed at [docs.ckbccc.com/skill.md](https://docs.ckbccc.com/skill.md), for agents that can only fetch URLs. + +Both paths mean **anyone with an existing install only sees your changes after they explicitly re-check/update** — there's no push notification. The one signal they have is the `metadata.version` field in each skill's frontmatter, so it has to be kept honest. + +## When you edit a `SKILL.md` + +1. **Bump `metadata.version`** in that skill's frontmatter — every time, even for small wording fixes. Use semver: + - **patch** (`1.0.0` → `1.0.1`): wording/clarity fixes, no behavior change. + - **minor** (`1.0.0` → `1.1.0`): new guidance added, existing guidance unchanged. + - **major** (`1.0.0` → `2.0.0`): a previous instruction was wrong and is being corrected/reversed — this is the case most worth calling out in the PR description, since an agent that already loaded the old version may be acting on bad guidance. +2. **Keep `docs.ckbccc.com/skill.md`'s skill table in sync** if you add, remove, or rename a skill, or change its `role`/`dependsOn` — it's maintained by hand in `packages/docs/app/skill.md/route.ts`, not generated from this directory. +3. **Don't rely on `npx skills update` alone to signal urgency.** It's a pull-based content-hash diff, not a version check, and has known reliability gaps (e.g. [vercel-labs/skills#484](https://github.com/vercel-labs/skills/issues/484) — sometimes reports "up to date" when the remote has changed). For a correction significant enough that stale guidance would actively mislead an agent (a major bump), call it out in the PR/release notes so it's not silently missed. + +## Verifying a change + +There's no automated eval suite for these skills yet. At minimum, re-run the canary questions in [Verify & Troubleshoot](https://docs.ckbccc.com/en/docs/ai-resources/verify-and-troubleshoot) against a tool with the updated skill loaded, and confirm the answer reflects your change. diff --git a/skills/ckb-ccc-examples-finder/SKILL.md b/skills/ckb-ccc-examples-finder/SKILL.md new file mode 100644 index 00000000..fd849ef8 --- /dev/null +++ b/skills/ckb-ccc-examples-finder/SKILL.md @@ -0,0 +1,87 @@ +--- +name: ckb-ccc-examples-finder +description: Helps locate existing, ready-made CKB/CCC example code and demo repositories by category — transactions, UDT, Spore/DOB, wallet connection, chain queries, backend scripts, on-chain contracts, and local devnet tooling — instead of writing something from scratch or guessing at patterns. Use when the user asks for an example, a demo, a sample project, "is there existing code for X", or wants to see how something is conventionally done in the CKB ecosystem — even if they don't use the word "example" (e.g. "how do other people do NervosDAO deposits"). +metadata: + author: ckb-devrel + version: "1.0.0" + role: spoke + depends-on: "ckb-ccc-fundamentals" + priority: normal +--- + +# CKB CCC — Finding Example Code + +Before writing example code from memory, check whether a working, tested example already exists — CKB/CCC has enough moving parts (Cell model, SSRI, DOB rendering) that reusing a known-good example is safer than reconstructing one. Check sources in this order. + +--- + +## Step 1 — CCC's own example gallery (check first, always) + +`docs.ckbccc.com` maintains a tagged gallery of runnable examples, each with a raw source URL and a direct Playground link: + +``` +GET https://docs.ckbccc.com/en/docs/code-examples.md +``` + +The gallery is rendered as an `` component — fetch the page's Markdown source and extract the `source` field from each item in the `items` array to get the raw TypeScript. Filter by the `tags` field for the category you need. Known tags as of this writing: + +| Tag | Meaning | +|---|---| +| `playground` | Runs directly in the Playground (`.ts` script, use with `ckb-ccc-playground`) | +| `app` | Lives in the CCC App (`app.ckbccc.com`), a GUI tool rather than a script | +| `tool` | Standalone utility (hash calculator, mnemonic generator, keystore decryptor) | +| `transaction` | General transaction composition | +| `udt` | UDT/xUDT token operations | +| `spore` | Spore protocol (DOB) operations | +| `wallet` | Wallet connection / signer patterns | +| `query` | Read-only chain queries | +| `backend` | Node.js / non-browser scripts | + +**This table is a cached snapshot, not the source of truth** — the gallery changes as new examples are added. Always fetch the live page rather than relying on this list for anything beyond a quick sanity check of what categories exist. + +Examples known to exist at time of writing (non-exhaustive): transferring CKB, sweeping a full balance, transferring UDT, signing/verifying a message, custom wallet-connection UI, querying balance/cells/transactions, NervosDAO deposit & withdraw, creating/transferring a Spore DOB, a Node.js backend transfer, issuing xUDT (Single-Use Lock and Type ID variants), Spore Cluster creation, time-locked transfers, an SSRI contract call, and utility tools (hash, mnemonic, keystore, dep group manager). + +--- + +## Step 2 — Protocol-specific external repos (if Step 1 doesn't cover it) + +| Repo | Covers | Layer | +|---|---|---| +| `sporeprotocol/dob-cookbook` | Deeper DOB/0 and DOB/1 recipes beyond the basics — image-linked traits via BTCFS/IPFS, programmatic images, DOB/1 SVG composition. For the core Spore/DOB concept and a basic DOB/0 walkthrough, see `ckb-ccc-spore` first — this repo is for recipes beyond that. Confirmed active (41+ commits, MIT license, has `README.md`/`BestPractices.md`/`FAQ.md`) | CCC / TypeScript | +| `ckb-devrel/nervdao` | NervosDAO-focused examples/tooling beyond the single deposit/withdraw example in Step 1 | CCC / TypeScript | + +**Fetching `dob-cookbook` efficiently**: each example's `.md` file (e.g. `examples/dob0/0.basic-loot.md`) embeds the **complete `.ts` source inline** in a fenced code block under its "## Code" heading — fetching the `.md` gets you the explanation and the full working code in one request; you don't need to separately fetch the matching `.ts` file. The repo's `README.md` is itself a categorized index (DOB/0 examples, DOB/1 examples, each linked directly) — fetch it first to find the right example before drilling into one. `BestPractices.md` and `FAQ.md` at the repo root are also worth checking for DOB-specific gotchas not covered by any single example. + +--- + +## Step 3 — Is this actually an on-chain contract question, not a CCC/TS question? + +If the user's question is about writing or modifying the **on-chain script/contract itself** (Rust, C, or Lua running in the CKB VM) rather than calling an existing script from TypeScript — this is a different layer than what CCC or this skill set covers. Redirect to: + +| Repo/Resource | Covers | +|---|---| +| `nervosnetwork/docs.nervos.org` (docs.nervos.org) | Official CKB developer docs — contract frameworks, the Rust/C SDKs, script development guides, and general protocol reference. Use this as the entry point when the need is outside CCC's scope entirely | + +--- + +## Step 4 — Still nothing found + +Search the `ckb-devrel` GitHub org directly, or ask via DeepWiki/Context7 +MCP (see `ckb-ccc-fundamentals` Step 0) whether an example exists anywhere +in the indexed CKB ecosystem repos before writing one from scratch. + +--- + +## Gotchas + +| Symptom | Cause | Fix | +|---|---|---| +| Recommended repo is archived / renamed / 404s | This table is a manually compiled snapshot and can go stale | Verify the repo still exists and has recent activity before pointing a developer to it; prefer Step 1 (the live gallery) whenever possible since it's actively maintained alongside the SDK | +| Confusing a CCC (TypeScript, off-chain) example with an on-chain contract example | Both "CKB example" and "CCC example" get used loosely | Check which layer the user actually needs (see Step 3) before recommending — the two are not interchangeable | + +--- + +## Related skills + +- `ckb-ccc-fundamentals` — Step 0 (DeepWiki/Context7 for API verification) and the doc-navigation pattern this skill builds on +- `ckb-ccc-playground` — how to actually run a `playground`-tagged example once found diff --git a/skills/ckb-ccc-fundamentals/SKILL.md b/skills/ckb-ccc-fundamentals/SKILL.md new file mode 100644 index 00000000..f18f716a --- /dev/null +++ b/skills/ckb-ccc-fundamentals/SKILL.md @@ -0,0 +1,255 @@ +--- +name: ckb-ccc-fundamentals +description: Provides foundational CKB (Nervos) blockchain knowledge needed before writing any CCC SDK code — the Cell model (CKB is UTXO-based, not account-based like Ethereum), which @ckb-ccc/* package to install for a given environment, address and Shannon/CKB amount handling, and the KnownScript enum — plus how to look up exact API signatures via DeepWiki MCP, Context7, or api.ckbccc.com, and how to navigate docs.ckbccc.com (llms.txt / per-page Markdown / llms-full.txt). Use this skill for any CKB or CCC SDK question, even if the user doesn't say "CCC" or "Nervos" explicitly — e.g. "how do I build on CKB", "what package do I need", "CKB vs Ethereum", "migrating from Lumos", "what does method X return", "where are the CCC docs". Load alongside a vertical skill (ckb-ccc-signer-setup / ckb-ccc-transactions / ckb-ccc-udt / ckb-ccc-spore) once the task's specific area is clear. +metadata: + author: ckb-devrel + version: "1.0.0" + role: hub + depends-on: "none" + priority: critical +--- + +# CKB CCC — Fundamentals + +CCC (CKBers' Codebase) is the one-stop TypeScript/JavaScript SDK for CKB. +- Full docs: https://docs.ckbccc.com +- llms.txt: https://docs.ckbccc.com/llms.txt + +This is the **hub skill** — read this first for any CKB/CCC task. For a specific scenario, also load the matching vertical skill: + +| Task | Skill | +|---|---| +| Connecting a wallet (React) or creating a backend signer | `ckb-ccc-signer-setup` | +| Composing/sending a transaction, querying cells, cell deps | `ckb-ccc-transactions` | +| Spore protocol NFTs/DOBs | `ckb-ccc-spore` | +| UDT / xUDT fungible tokens | `ckb-ccc-udt` | + +--- + +## Critical: CKB is NOT an Account Model Chain + +CKB uses the **Cell model** — a generalized UTXO model. This is the #1 source of AI hallucination when helping CKB developers. Internalize this before generating any code: + +| Concept | Ethereum (EVM) | CKB | +|---|---|---| +| State unit | Account balance | Cell (capacity + lock + type + data) | +| Ownership | `msg.sender` | Lock script (verified by CKB VM) | +| Asset rules | Smart contract storage | Type script | +| "Send tokens" | Transfer from account | Consume input Cells, create output Cells | +| Minimum unit | wei (1e-18 ETH) | Shannon (1 CKB = 100,000,000 Shannon) | + +**There are no accounts, no balances, no `msg.sender` in CKB.** + +--- + +## Package Selection — Always Get This Right First + +| Scenario | Install | Import | +|---|---|---| +| React/Next.js dApp | `@ckb-ccc/connector-react` | `import { ccc } from "@ckb-ccc/connector-react"` | +| Node.js backend / scripts | `@ckb-ccc/shell` | `import { ccc } from "@ckb-ccc/shell"` | +| Custom wallet UI (non-React) | `@ckb-ccc/ccc` | `import { ccc } from "@ckb-ccc/ccc"` | +| Framework-agnostic Web Component | `@ckb-ccc/connector` | Web Component `` | +| Library authoring (minimal deps) | `@ckb-ccc/core` | `import { ccc } from "@ckb-ccc/core"` | + +**Rule**: `@ckb-ccc/shell` and `@ckb-ccc/connector-react` already re-export everything from `@ckb-ccc/core` — do not install core separately unless authoring a library. + +--- + +## Amount Conversion (Shannon ↔ CKB) + +```typescript +ccc.fixedPointFrom("100") // 100 CKB → 10_000_000_000n Shannon +ccc.fixedPointFrom(100) // same +ccc.fixedPointFrom("0.01") // 0.01 CKB → 1_000_000n Shannon +ccc.fixedPointToString(balance) // bigint Shannon → "100" (human-readable CKB) +``` + +All capacity values in CCC are `bigint` in Shannon. **Never use floating point.** +Always work in Shannon internally; convert to/from CKB only at the UI boundary. + +--- + +## Address Handling + +```typescript +// Parse an address string into its Script +const { script: lock } = await ccc.Address.fromString(addressStr, client); + +// Get address from connected signer +const addressStr = await signer.getRecommendedAddress(); +const addressObj = await signer.getRecommendedAddressObj(); // includes Script +const { script: lock } = addressObj; // preferred when you need the lock script +``` + +Address prefix: `ckb` = mainnet, `ckt` = testnet. Mixing them throws. + +--- + +## Known Scripts (KnownScript enum) + +Do **not** hardcode codeHash values. Use `KnownScript`: + +```typescript +// Resolve script from name +const xudtType = await ccc.Script.fromKnownScript( + client, + ccc.KnownScript.XUdt, + "0x", +); + +// Available values (selection): +// KnownScript.Secp256k1Blake160 — standard CKB lock +// KnownScript.XUdt — extensible UDT (fungible tokens) +// KnownScript.NervosDao — NervosDAO deposit/withdraw +// KnownScript.JoyId — JoyID passkey lock +// KnownScript.OmniLock — EVM/BTC cross-chain lock +// KnownScript.NostrLock — Nostr protocol lock +// KnownScript.TypeId — Type ID (upgradeable contracts) +// KnownScript.COTA — COTA NFT standard +// KnownScript.PWLock — PWLock +// KnownScript.UniqueType — unique type identifier +// KnownScript.DidCkb — web5 DID(decentralized identity) on CKB +// KnownScript.AlwaysSuccess — always success lock +// KnownScript.InputTypeProxyLock — proxy lock for input types +// KnownScript.OutputTypeProxyLock — proxy lock for output types +// KnownScript.LockProxyLock — proxy lock for locks +// KnownScript.SingleUseLock — single-use lock +// KnownScript.TypeBurnLock — type burn lock +// KnownScript.EasyToDiscoverType — easy to discover type +// KnownScript.TimeLock — time-locked transfer +``` + +--- + +## Common Gotchas (cross-cutting / project setup) + +| Symptom / Error | Cause | Fix | +|---|---|---| +| `ERR_REQUIRE_ESM` | CJS project importing CCC | Add `"type": "module"` to package.json or use dynamic `import()` | +| `Property '*' not found on ccc` | Wrong `moduleResolution` | Set `"moduleResolution": "bundler"` in tsconfig; do not use `node` or `classic` | +| Wrong address on wrong network | Mainnet/testnet mismatch | `ckb` prefix = mainnet, `ckt` prefix = testnet; client and address must match | + +For gotchas about signer/wallet setup, transaction composition, UDT, or Spore, see the matching vertical skill. + +--- + +## Hallucination Guard (cross-cutting) + +- ❌ `signer.getBalance()` returns a `number` — it returns `bigint` (Shannon) +- ❌ Using EVM address format (`0x...` 20-byte) as CKB address — CKB uses bech32m (`ckb1...`) +- ❌ `ccc.fixedPointFrom(100.5)` with float arithmetic on Shannon — use string `"100.5"` +- ❌ Generating a method signature, parameter list, or return type from memory/pattern-matching without checking DeepWiki MCP / Context7 / api.ckbccc.com first (see "Step 0" under "How to Use This Documentation") — this applies even when the signature looks plausible or resembles a similar SDK (ethers.js, Lumos) +- ❌ Treating a single example from a cookbook/demo repo as authoritative protocol behavior — a single `.ts` file can contain typos or copy-paste errors (e.g. a wrong `contentType` value) that look internally consistent but are still wrong. When a protocol-level spec exists (e.g. `docs.spore.pro` for DOB, the CKB RFCs for script/cell behavior), cross-check against it rather than trusting one example in isolation — especially before stating something as a general rule rather than "this is what one example did" + +For hallucination guards specific to signers, transactions, UDT, or Spore, see the matching vertical skill. + +--- + +## Pre-submission Checklist (cross-cutting) + +Before finalizing any CCC code, verify: + +- [ ] **Package** — Correct package for the environment (`connector-react` / `shell` / `ccc`) +- [ ] **TypeScript** — `tsconfig.json` has `moduleResolution: "bundler"` (or `node16`/`nodenext`) +- [ ] **Network match** — Client network (testnet/mainnet) matches address prefix (`ckt`/`ckb`) +- [ ] **Amount conversion** — User-facing amounts go through `fixedPointFrom()` / `fixedPointToString()` + +For checklist items specific to signers, transactions, UDT, or Spore, see the matching vertical skill. + +--- + +## How to Use This Documentation + +When you need information beyond what this skill covers, fetch it directly rather than guessing. The docs site is structured for programmatic access: + +### Step 0 — For exact API signatures, query DeepWiki MCP first + +CCC's source is indexed on DeepWiki (`ckb-devrel/ccc`) and Context7 (`ckb-devrel/ccc`). Before generating any code that calls a specific method — its parameters, return type, overloads, or a class's members — do not rely on pattern-matching against similar SDKs (e.g. ethers.js, Lumos) or on this skill's code snippets as the source of truth for exact signatures. Verify in this order: + +1. **DeepWiki MCP** (preferred) — if a DeepWiki MCP tool is available in this session (commonly exposed as `ask_question`, `read_wiki_contents`, or `read_wiki_structure` — check your available tools for one tagged `deepwiki`), call it against repo `ckb-devrel/ccc` with the specific question, e.g. "What are the parameters and return type of `Udt.completeBy`?". It returns source-grounded answers with real usage examples — use `ask_question` for a specific method/class, and `read_wiki_structure` first only if you need to locate which part of the repo covers an unfamiliar concept. +2. **Context7 MCP** (fallback) — if DeepWiki is unavailable or the answer is inconclusive, resolve the library ID for `ckb-devrel/ccc` and query its docs. +3. **api.ckbccc.com** (fallback) — if no MCP tool is available this session, fetch the TypeDoc HTML reference directly (see "API reference" below). Slower to parse reliably, but always reachable as a plain URL. +4. **If none confirm the signature** — say so explicitly and ask the user, or mark the code as unverified, rather than inventing a plausible-looking signature. This rule overrides confidence from familiarity with similar SDKs — see "Hallucination Guard". + +### Step 1 — Start with llms.txt for navigation + +``` +GET https://docs.ckbccc.com/llms.txt +``` + +Returns a structured index of all documentation pages with titles and URLs. Use this to identify which page covers the topic you need. + +### Step 2 — Fetch the specific page as Markdown + +Append `.md` to any docs URL to get clean Markdown without HTML boilerplate: + +``` +# Example: fetch the Cell model concept page +GET https://docs.ckbccc.com/en/docs/concepts/cell-model.md + +# Pattern for any page: +GET https://docs.ckbccc.com/en/docs/
/.md +``` + +Alternatively, send `Accept: text/markdown` and the server will serve Markdown automatically via content negotiation. + +### Step 3 — Use llms-full.txt for broad questions + +``` +GET https://docs.ckbccc.com/llms-full.txt +``` + +Contains the full text of all English documentation pages concatenated. Use when the question spans multiple pages or you need a complete overview. Prefer per-page fetches when the relevant section is known — narrower context means less noise. + +### Key page URLs (most commonly needed) + +| Topic | Markdown URL | +|---|---| +| Cell model, Script, Transaction concepts | `https://docs.ckbccc.com/en/docs/concepts/cell-model.md` | +| Signer interface | `https://docs.ckbccc.com/en/docs/concepts/signer.md` | +| Connecting wallets | `https://docs.ckbccc.com/en/docs/guides/connect-wallets.md` | +| Composing transactions | `https://docs.ckbccc.com/en/docs/guides/compose-transactions.md` | +| UDT token guide | `https://docs.ckbccc.com/en/docs/guides/udt-tokens.md` | +| Spore protocol guide | `https://docs.ckbccc.com/en/docs/guides/spore-protocol.md` | +| Node.js backend guide | `https://docs.ckbccc.com/en/docs/guides/node-js-backend.md` | +| Package overview | `https://docs.ckbccc.com/en/docs/packages/core-packages.md` | +| AI setup & prompting guide (for the human you're helping) | `https://docs.ckbccc.com/en/docs/ai-resources.md` | + +### For API signatures and types — use the API reference (fallback only) + +``` +https://api.ckbccc.com +``` + +TypeDoc-generated reference for all `@ckb-ccc/*` packages. Only reach for this when no DeepWiki or Context7 MCP tool is available this session — see "Step 0" above, which is the preferred path for any exact method signature, interface field, or enum value. When you do use this fallback, search by class or method name directly in the URL: + +``` +https://api.ckbccc.com/modules/_ckb-ccc_connector-react +https://api.ckbccc.com/functions/_ckb-ccc_connector-react.index.useccc +https://api.ckbccc.com/classes/_ckb-ccc_core.index.transaction +https://api.ckbccc.com/enums/_ckb-ccc_core.index.knownscript +``` + +### Decision guide: which resource to fetch + +| Situation | Fetch | +|---|---| +| "How do I do X with CCC?" | Per-page `.md` from docs | +| "What does method Y return?" / exact signature | DeepWiki MCP first → Context7 MCP → api.ckbccc.com (see Step 0) | +| "Give me a working example" | Fetch https://docs.ckbccc.com/en/docs/code-examples.md, extract source fields from the items, then fetch the raw GitHub URL to get the TypeScript source | +| "Which page covers topic Z?" | llms.txt index first, then the page | +| "Broad overview of CCC" | llms-full.txt | + +--- + +## Reference Links + +- Docs: https://docs.ckbccc.com +- llms.txt: https://docs.ckbccc.com/llms.txt +- llms-full.txt: https://docs.ckbccc.com/llms-full.txt +- Per-page markdown: append `.md` to any docs URL +- API reference (fallback only — see Step 0): https://api.ckbccc.com +- Source indexed on DeepWiki MCP / Context7 (preferred for exact signatures — see Step 0): repo `ckb-devrel/ccc` +- Playground (live code): https://live.ckbccc.com +- GitHub: https://github.com/ckb-devrel/ccc \ No newline at end of file diff --git a/skills/ckb-ccc-playground/SKILL.md b/skills/ckb-ccc-playground/SKILL.md new file mode 100644 index 00000000..cb84d117 --- /dev/null +++ b/skills/ckb-ccc-playground/SKILL.md @@ -0,0 +1,131 @@ +--- +name: ckb-ccc-playground +description: Covers the CCC Playground (live.ckbccc.com) — an in-browser environment for running and sharing CCC TypeScript code with zero setup — including the @ckb-ccc/playground module's render()/signer helpers, the UI controls and the two ways to share code. Use when the user asks how to try/test CCC code in the browser, how to share a code snippet or reproduce a bug, what render() or the playground's pre-connected signer does, or how to contribute an example to the docs gallery — even if they just say "playground" or "live.ckbccc.com" without more context. Requires ckb-ccc-fundamentals and ckb-ccc-transactions for the underlying SDK concepts being demonstrated. +metadata: + author: ckb-devrel + version: "1.0.0" + role: spoke + depends-on: "ckb-ccc-fundamentals, ckb-ccc-transactions" + priority: normal +--- + +# CKB CCC — Playground + +The CCC Playground (https://live.ckbccc.com) is an in-browser TypeScript sandbox for CCC — no local project setup, no wallet extension required to start. It ships with a pre-connected signer and a step-by-step transaction visualizer, and is the fastest way to verify that a piece of CCC code (your own, or something fetched from DeepWiki/api.ckbccc.com per `ckb-ccc-fundamentals` Step 0) actually works before shipping it. + +--- + +## The `@ckb-ccc/playground` module + +Playground scripts import from `@ckb-ccc/playground`, not `@ckb-ccc/shell` or `@ckb-ccc/core` directly — this module wires up a ready-to-use signer and a visualization helper so you don't have to set up a client/signer yourself. + +**Always start a playground script with these two imports**: + +```typescript +import { ccc } from "@ckb-ccc/ccc" +import { render, signer, client } from "@ckb-ccc/playground"; +``` + +- `ccc` — the SDK itself (`@ckb-ccc/ccc`), for `Address`, `Transaction`, `fixedPointFrom`, etc. +- `render` / `signer` / `client` — playground-provided helpers (`@ckb-ccc/playground`); `signer` is pre-connected, `client` is its underlying `ClientPublicTestnet`/`ClientPublicMainnet` instance — import `client` whenever a call needs the client directly instead of going through `signer.client`. + +```typescript +// signer` is already connected — Testnet by default, no setup needed +console.log(await signer.getRecommendedAddress()); + +// replace with the recipient address +const receiverAddress = "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgy2q6qz79wyaexr2pcez0eejmk5xgw6jcfw7zmg"; + +const { script: lock } = await ccc.Address.fromString(receiverAddress, signer.client); +const tx = ccc.Transaction.from({ + outputs: [{ capacity: ccc.fixedPointFrom(100), lock }], +}); +await render(tx); // visualize the tx as built so far + +await tx.completeInputsByCapacity(signer); +await render(tx); // visualize again after inputs filled + +await tx.completeFeeBy(signer, 1000); +await render(tx); // visualize again after fee paid +``` + +**Pattern**: call `await render(tx)` after each meaningful transformation of the transaction (outputs declared → inputs completed → fee paid) rather than only once at the end. This is what the official examples in the `code-examples` gallery do, and it's what makes the visualizer useful — you see the transaction evolve instead of just the final state. + +--- + +## Using Spore in the Playground + +There are two equivalent ways to call Spore functions in a playground script: + +1. **Via `ccc.spore`** — no extra import needed, just use the `ccc` import you already have: + + ```typescript + const { tx, id: sporeId } = await ccc.spore.createSpore({ signer, data: { ... } }); + ``` + +2. **Import `spore` directly** from `@ckb-ccc/spore`, then call `spore.xxx`: + + ```typescript + import { spore } from "@ckb-ccc/spore"; + + const { tx, id: sporeId } = await spore.createSpore({ signer, data: { ... } }); + ``` + +Both resolve to the same implementation — pick whichever keeps the script's import list minimal. See `ckb-ccc-spore` for the full Spore API (create/transfer/melt, cluster handling). + +--- + +## UI controls + +| Control | Purpose | +|---|---| +| **Testnet** toggle | Switches the pre-connected `signer`'s network. Testnet by default — **Check this before assuming a code snippet is safe to run; mainnet transactions are real.** | +| **Format** | Formats/prettifies the code in the editor. | +| **Run** | Executes the script from the top. | +| **Step** | Steps through the script's `render()` checkpoints one at a time, instead of running to completion — use this to inspect the transaction at each stage rather than only the final result. | +| **Share** | Publishes the current code to a Nostr relay and generates a share link. | +| **Console** | Shows `console.log` output and errors from the running script. | +| **About** | Playground info/help panel. | + +--- + +## Sharing code: two methods, different guarantees + +1. **Share button (Nostr)** — fast, no repo needed, good for quick back-and-forth with a colleague. **Not guaranteed permanent** — Nostr relay nodes don't guarantee long-term data retention, so these links can expire. +2. **`?src=` query parameter (raw URL)** — loads code from any publicly reachable raw file URL: `https://live.ckbccc.com/?src=`. + +For **stable, long-lived links**, host the script in a GitHub repo and point `?src=` at the `raw.githubusercontent.com` URL — this is exactly how every example in the official `code-examples` gallery is linked (see `ckb-ccc-examples-finder`). The link stays valid as long as the file exists in the repo. + +--- + +## Contributing an example back to the docs gallery + +If a snippet you built in the Playground is broadly useful, it can be added +to `docs.ckbccc.com`'s example gallery: + +1. Fork `ckb-devrel/ccc`, clone locally. +2. Create a new `.ts` file in `packages/examples/src/`, camelCase naming + (e.g. `myNewExample.ts`). +3. Import the SDK from `@ckb-ccc/ccc` and `render`/`signer` from + `@ckb-ccc/playground`; call `await render(tx)` at key steps. +4. Paste the script into the Playground and verify it runs on Testnet. +5. Open a PR against `master` describing the example's purpose. + +--- + +## Gotchas + +| Symptom | Cause | Fix | +|---|---|---| +| `render(tx)` output doesn't update | Missing `await` | `render()` is async — always `await render(tx)` | +| Script behaves differently than expected vs. a real project | Playground's `signer` is pre-wired for you | Don't assume the same code will "just work" without a signer/client in a real project — see `ckb-ccc-signer-setup` for how to create one yourself | +| Sharing a link that stops working after a while | Used the Share (Nostr) button for something long-lived | Host the script on GitHub and share the `?src=` raw URL instead | + +--- + +## Related skills + +- `ckb-ccc-fundamentals` — package selection, amount/address handling used in every playground script +- `ckb-ccc-transactions` — the outputs → completeInputsByCapacity → completeFeeBy pattern shown above +- `ckb-ccc-spore` — the full Spore API (create/transfer/melt, cluster handling) used via `ccc.spore` or `@ckb-ccc/spore` in the playground +- `ckb-ccc-examples-finder` — the full catalog of ready-made playground examples by category diff --git a/skills/ckb-ccc-signer-setup/SKILL.md b/skills/ckb-ccc-signer-setup/SKILL.md new file mode 100644 index 00000000..1c87314c --- /dev/null +++ b/skills/ckb-ccc-signer-setup/SKILL.md @@ -0,0 +1,150 @@ +--- +name: ckb-ccc-signer-setup +description: Covers obtaining a working CCC Signer — connecting a wallet in a React/Next.js dApp via ccc.Provider and hooks, or creating a private-key signer in a Node.js backend script — plus the supported wallet matrix (JoyID, MetaMask/EIP-6963, UniSat, OKX, Xverse, Nostr, UTXO Global, REI) and message signing/verification. Use when the user asks how to connect a wallet, which wallet package to install, how to authenticate a backend script, or how to sign/verify a message — even if they just say "log in" or "connect" without mentioning "signer" or "wallet" by name. Requires ckb-ccc-fundamentals for package selection and address handling. +metadata: + author: ckb-devrel + version: "1.0.0" + role: spoke + depends-on: "ckb-ccc-fundamentals" + priority: normal +--- + +# CKB CCC — Signer Setup + +Covers every way to get a working `Signer` instance: connecting a wallet in a +React dApp, or creating a private-key signer in a Node.js backend. Both paths +converge on the same `Signer` interface, so the rest of your code (building +and sending transactions) is identical either way — see `ckb-ccc-transactions` +for that part. + +Package selection (`connector-react` vs `shell`) is covered in +`ckb-ccc-fundamentals` — read that first if you haven't picked a package yet. + +--- + +## Building a React dApp with wallet connection + +1. **Install and wrap Provider** — Install `@ckb-ccc/connector-react`. Wrap root component with ``. Add `"use client"` for Next.js App Router. +2. **Add wallet connection UI** — Use `ccc.useCcc()` to get `open`, `wallet`, `signerInfo`, `disconnect`. Render connect/disconnect button based on `signerInfo` presence. +3. **Get signer** — Call `const signer = ccc.useSigner()` inside a component; guard with `if (!signer) return` before any transaction operation. +4. **Resolve recipient** — `const { script: lock } = await ccc.Address.fromString(toAddress, signer.client)`. +5. **Build transaction** — `const tx = ccc.Transaction.from({ outputs: [{ capacity: ccc.fixedPointFrom("100"), lock }] })`. +6. **Complete inputs** — `await tx.completeInputsByCapacity(signer)` — must come before fee calculation. +7. **Pay fee** — `await tx.completeFeeBy(signer)` — omit fee rate argument to use automatic network rate. +8. **Send** — `const txHash = await signer.sendTransaction(tx)`. +9. **Verify** — Optionally wait for confirmation: `await signer.client.waitTransaction(txHash, 1)`. + +(Steps 4–9 are the general transaction-composition pattern — see +`ckb-ccc-transactions` for the full explanation, cell deps, and querying.) + +```tsx +"use client"; +import { ccc } from "@ckb-ccc/connector-react"; + +// Root — wrap once +export default function App({ children }) { + return {children}; +} + +// Component — use inside Provider +function ConnectButton() { + const { open, disconnect, wallet, signerInfo } = ccc.useCcc(); + const signer = ccc.useSigner(); + return signerInfo + ? + : ; +} +``` + +--- + +## Building a Node.js backend script + +1. **Import and connect** — Install `@ckb-ccc/shell`. Create client: `new ccc.ClientPublicTestnet()` or `ClientPublicMainnet()`. +2. **Create signer** — `new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!)`. Never hardcode keys. +3. **Check connection** — Some signers require `await signer.connect()`. Check with `await signer.isConnected()` if operations fail unexpectedly. +4. **Query data** — `await signer.getRecommendedAddress()`, `await signer.getBalance()`, `for await (const cell of client.findCellsByLock(...))`. +5. **Build and send** — Follow the transaction-composition pattern in `ckb-ccc-transactions`; the pattern is identical regardless of how the signer was created. +6. **Verify** — Log `txHash`; use `client.getTransaction(txHash)` to poll for confirmation. + +```typescript +import { ccc } from "@ckb-ccc/shell"; + +// Always load private key from environment — never hardcode +const client = new ccc.ClientPublicTestnet(); // or ClientPublicMainnet +const signer = new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!); +await signer.connect(); + +const address = await signer.getRecommendedAddress(); // "ckt1q..." or "ckb1q..." +const balance = await signer.getBalance(); // bigint in Shannon +``` + +**TypeScript config** — `@ckb-ccc/shell` ships ESM only: +```json +{ "compilerOptions": { "moduleResolution": "bundler" } } +``` + +--- + +## Wallet Support Matrix + +CCC bridges multiple chain ecosystems into CKB via a unified `Signer` interface: + +| Package | Wallet(s) | Chain(s) | +|---|---|---| +| `@ckb-ccc/joy-id` | JoyID (passkey, no seed phrase) | CKB / BTC / EVM / Nostr | +| `@ckb-ccc/eip6963` | MetaMask, Rabby, OKX EVM, any EIP-6963 wallet | EVM | +| `@ckb-ccc/uni-sat` | UniSat | BTC | +| `@ckb-ccc/okx` | OKX Wallet | BTC / Nostr | +| `@ckb-ccc/xverse` | Xverse, SATS Connect wallets | BTC | +| `@ckb-ccc/nip07` | nos2x, Alby, NIP-07 extensions | Nostr | +| `@ckb-ccc/utxo-global` | UTXO Global | CKB / BTC / DOGE | +| `@ckb-ccc/rei` | REI Wallet | CKB | + +All wallet packages are pre-bundled in `@ckb-ccc/connector-react` and `@ckb-ccc/ccc`. +Only install individually for custom integrations. + +--- + +## Message Signing and Verification + +```typescript +// Sign (works identically across all wallet types) +const sig = await signer.signMessage("Hello world"); +// sig.signature — hex string +// sig.signType — "CkbSecp256k1" | "EvmPersonal" | "BtcEcdsa" | "JoyId" | ... +// sig.identity — signer's address or public key + +// Verify (static — no connected wallet needed) +const isValid = await ccc.Signer.verifyMessage("Hello world", sig); // true +const isFail = await ccc.Signer.verifyMessage("Wrong", sig); // false +``` + +--- + +## Gotchas (signer/wallet-specific) + +| Symptom / Error | Cause | Fix | +|---|---|---| +| `createContext is not a function` | Missing `"use client"` in Next.js | Add `"use client"` to any file using `ccc.Provider` or `useCcc()` | +| `"invalid private key"` | Wrong key format | Key must be 32 bytes (64 hex chars); the `0x` prefix is optional | +| Signer methods fail silently | Wallet not connected | Check `await signer.isConnected()`; some signers need explicit `await signer.connect()` | +| `useSigner()` returns `undefined` | Called outside `` | Ensure `ccc.Provider` wraps the component tree | + +--- + +## Hallucination Guard + +- ❌ `useSigner()` outside `` — will return undefined +- ❌ Hardcoding private keys in source — always use environment variables + +--- + +## Checklist (signer/wallet-specific) + +- [ ] **React setup** — `"use client"` on files using `ccc.Provider` or hooks (Next.js App Router) +- [ ] **Signer guard** — `if (!signer) return` before any transaction operation +- [ ] **No hardcoded secrets** — Private keys loaded from environment variables +- [ ] **Error handling** — `try/catch` around `connect()` / `getBalance()` + +Also check the cross-cutting checklist in `ckb-ccc-fundamentals`. diff --git a/skills/ckb-ccc-spore/SKILL.md b/skills/ckb-ccc-spore/SKILL.md new file mode 100644 index 00000000..2b53ba73 --- /dev/null +++ b/skills/ckb-ccc-spore/SKILL.md @@ -0,0 +1,212 @@ +--- +name: ckb-ccc-spore +description: Covers creating, transferring, and melting Spore protocol NFTs/DOBs(on-chain digital objects) on CKB with CCC, including cluster handling and the DOB/0 and DOB/1 content-type conventions built on top of Spore (DNA-driven trait patterns, decoders, SVG composition). Use when the user asks about Spore, DOB, DOB/0, DOB/1, on-chain NFTs, or clusters on CKB — even if they just say "NFT" or "digital object" without naming Spore or DOB specifically. Builds on the standard transaction pattern in ckb-ccc-transactions. +metadata: + author: ckb-devrel + version: "1.0.0" + role: spoke + depends-on: "ckb-ccc-fundamentals, ckb-ccc-transactions" + priority: normal +--- + +# CKB CCC — Spore Protocol + +Covers Spore (on-chain NFT/DOB) creation, transfer, and destruction. Assumes a connected `Signer` (see `ckb-ccc-signer-setup`) and the standard transaction pattern (see `ckb-ccc-transactions`) — this skill only adds the Spore-specific steps on top of that pattern. + +--- + +## Spore vs. DOB — how they relate + +**Spore is the base protocol**: a cell holding arbitrary `content` + `contentType`, optionally belonging to a Cluster. Everything in "Creating and managing Spore NFTs" below works for *any* content type. + +**DOB (Digital Object) is a family of specialized `contentType` conventions built on top of Spore** — DOB/0 and DOB/1 each define their own content encoding and a matching decoder, so wallets/explorers know how to render them consistently: + +- **DOB/0** (`contentType: "dob/0"`) — the content is a **DNA** string (raw hex bytes). The Cluster's description defines a **pattern**: a list of traits, each specifying which bytes of the DNA to read (`dnaOffset`, `dnaLength`) and how to interpret them (`options` → pick from a list, `range` → numeric range, `rawNumber` → raw value). A **decoder** applies the pattern to the DNA to produce human-readable traits, which compatible platforms (JoyID, Omiga, CKB Explorer, Mobit, Dobby) render. +- **DOB/1** (`contentType: "dob/1"`) — takes DOB/0's *decoded output* as input and assembles it into an SVG image (backgrounds, icons, compositing). Per the official protocol spec, the DOB/0 decoder produces named traits as **TEXT**, and the DOB/1 decoder consumes those traits to produce the final **SVG** string — so a DOB/1 cluster registers *both* decoders (`ver: 1`, `decoders: [dob0Entry, dob1Entry]`), and the minted spore's `contentType` is `"dob/1"`. + +If the user just says "NFT", "DOB" or "digital object" without specifying a format, a plain Spore (no DOB pattern) is usually the simpler starting point — reach for DOB/0 when they specifically want DNA-driven, generative/random traits (loot-style items, PFP traits, etc.). + +--- + +## Creating a DOB/0 (cluster + pattern + minted DOB) + +1. **Define the trait pattern** — an array of `PatternElementDob0` entries, each mapping a slice of the DNA to a named trait. +2. **Encode it into the cluster description** — via `ccc.spore.dob.encodeClusterDescriptionForDob0()`, referencing a decoder obtained from `ccc.spore.dob.getDecoder(client, "dob0")`. +3. **Create the Cluster** — `ccc.spore.createSporeCluster({ signer, data: { name, description } })`, then `completeFeeBy` + `sendTransaction` as usual. +4. **Create the Spore with `contentType: "dob/0"`** — `content` is the DNA bytes (commonly a JSON string embedding a random hex DNA); set `clusterId` and `clusterMode: "clusterCell"`. + +```typescript +import { ccc } from "@ckb-ccc/ccc"; +import { client, signer } from "@ckb-ccc/playground"; // or your own client/signer + +function generateSimpleDNA(length: number): string { + return Array.from({ length }, () => Math.floor(Math.random() * 16).toString(16)).join(""); +} + +// 1-2. Define pattern and encode the cluster description +const dob0Pattern: ccc.spore.dob.PatternElementDob0[] = [ + { traitName: "BackgroundColor", dobType: "String", dnaOffset: 0, dnaLength: 1, patternType: "options", traitArgs: ["red", "blue", "green", "black", "white"] }, + { traitName: "Type", dobType: "Number", dnaOffset: 1, dnaLength: 1, patternType: "range", traitArgs: [10, 50] }, + { traitName: "Timestamp", dobType: "Number", dnaOffset: 2, dnaLength: 4, patternType: "rawNumber" }, +]; + +const dob0: ccc.spore.dob.Dob0 = { + description: "A simple loot cluster", + dob: { ver: 0, decoder: ccc.spore.dob.getDecoder(client, "dob0"), pattern: dob0Pattern }, +}; +const clusterDescription = ccc.spore.dob.encodeClusterDescriptionForDob0(dob0); + +// 3. Create the cluster +const { tx: clusterTx, id: clusterId } = await ccc.spore.createSporeCluster({ + signer, + data: { name: "Simple loot", description: clusterDescription }, +}); +await clusterTx.completeFeeBy(signer); +await signer.sendTransaction(clusterTx); + +// 4. Mint a DOB/0 spore into that cluster +const { tx: sporeTx, id: sporeId } = await ccc.spore.createSpore({ + signer, + data: { + contentType: "dob/0", + content: ccc.bytesFrom(`{ "dna": "${generateSimpleDNA(16)}" }`, "utf8"), + clusterId, + }, + clusterMode: "clusterCell", +}); +await sporeTx.completeFeeBy(signer); +await signer.sendTransaction(sporeTx); +``` + +Transfer and melt for a DOB/0 spore use the exact same `transferSpore` / `meltSpore` calls shown below — DOB only changes how the content is *encoded and rendered*, not how the Spore cell itself is managed on-chain. + +## Creating a DOB/1 (SVG composition on top of DOB/0) + +DOB/1 doesn't replace DOB/0's DNA pattern — it **adds a second decoder on top of it**. Per the official protocol spec, the cluster description registers *two* decoders: the DOB/0 one (DNA → named traits, as TEXT) and a DOB/1 one (those traits → an SVG string). The minted Spore's `contentType` is `"dob/1"`. + +1. **Define the DOB/0 pattern** — same as a plain DOB/0 (traits from DNA bytes). +2. **Define the DOB/1 pattern** — an array of `PatternElementDob1` entries. + Each targets an `imageName` (e.g. `"IMAGE.0"`) and an `svgFields` (`"attributes"` sets the `` tag's attributes; `"elements"` maps a trait's value — or a numeric range — to a chunk of SVG markup, with a `[["*"], fallbackSvg]` entry as the wildcard/default case). A `traitName: ""` with `patternType: "raw"` inserts static markup that isn't tied to any trait (e.g. an always-present background image). +3. **Encode both into the cluster description** — via `ccc.spore.dob.encodeClusterDescriptionForDob1()`, passing `ver: 1` and a `decoders` array containing both the dob0 and dob1 `{ decoder, pattern }` pairs. +4. **Create the Cluster and mint the Spore** — identical to the DOB/0 flow, except `contentType: "dob/1"`; `content` is still the DNA. + +```typescript +import { ccc } from "@ckb-ccc/ccc"; +import { client, signer } from "@ckb-ccc/playground"; + +const dob0Pattern: ccc.spore.dob.PatternElementDob0[] = [ + { traitName: "Level", dobType: "String", dnaOffset: 0, dnaLength: 1, patternType: "options", traitArgs: ["Gold", "Silver", "Copper", "Blue"] }, + { traitName: "Member ID", dobType: "String", dnaOffset: 1, dnaLength: 10, patternType: "rawString" }, +]; + +const dob1Pattern: ccc.spore.dob.PatternElementDob1[] = [ + { imageName: "IMAGE.0", svgFields: "attributes", traitName: "", patternType: "raw", traitArgs: "xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500'" }, + { imageName: "IMAGE.0", svgFields: "elements", traitName: "", patternType: "raw", traitArgs: "" }, + { + imageName: "IMAGE.0", svgFields: "elements", traitName: "Level", patternType: "options", + traitArgs: [ + ["Gold", ""], + ["Silver", ""], + // ...one entry per Level option; no wildcard needed since Level's own + // dob0Pattern options already cover every possible DNA value + ], + }, +]; + +const dob1: ccc.spore.dob.Dob1 = { + description: "Owning a Spore Genesis DOB grants exclusive access to special events, governance participation, and future airdrops within the Spore ecosystem.", + dob: { + ver: 1, + decoders: [ + { decoder: ccc.spore.dob.getDecoder(client, "dob0"), pattern: dob0Pattern }, + { decoder: ccc.spore.dob.getDecoder(client, "dob1"), pattern: dob1Pattern }, + ], + }, +}; +const clusterDescription = ccc.spore.dob.encodeClusterDescriptionForDob1(dob1); + +const { tx: clusterTx, id: clusterId } = await ccc.spore.createSporeCluster({ + signer, + data: { name: "Spore Genesis", description: clusterDescription }, +}); +await clusterTx.completeFeeBy(signer); +await signer.sendTransaction(clusterTx); + +const { tx: sporeTx, id: sporeId } = await ccc.spore.createSpore({ + signer, + data: { + contentType: "dob/1", // note: dob/1, not dob/0 — this mints a DOB/1, not a DOB/0 + content: ccc.bytesFrom(`{ "dna": "${generateSimpleDNA(16)}" }`, "utf8"), // same generateSimpleDNA helper as the DOB/0 example + clusterId, + }, + clusterMode: "clusterCell", +}); +await sporeTx.completeFeeBy(signer); +await signer.sendTransaction(sporeTx); +``` + +**Displaying `btcfs://` / `ipfs://` image URIs**: these aren't URLs a browser can load directly — they need a resolver (e.g. the [dob-render sdk](https://www.npmjs.com/package/@nervina-labs/dob-render)) to turn them into an actual displayable image (base64 or similar) before rendering the SVG. Don't assume `` renders as-is in a plain ``/browser context. + +## Deeper recipes + +More DOB/0 patterns (image-linked traits via BTCFS/IPFS, programmatic images) and more DOB/1 compositions (genesis-style layered images, btcfs/ipfs-linked backgrounds) are covered with worked, runnable examples in `sporeprotocol/dob-cookbook` — see `ckb-ccc-examples-finder` for how to fetch specific examples from that repo. For the underlying byte-level protocol spec (not just a working example), see the official docs: `docs.spore.pro/dob/dob0-protocol` and `docs.spore.pro/dob/dob1-protocol`. + +--- + +## Creating and managing Spore NFTs + +1. **Create Spore** — `const { tx, id: sporeId } = await spore.createSpore({ signer, data: { contentType: "text/plain", content: bytes } })`. +2. **If using a Cluster** — Specify `clusterMode: "lockProxy" | "clusterCell" | "skip"` in `createSpore()`. Omitting it when `clusterId` is set throws. +3. **Complete and send** — `await tx.completeInputsByCapacity(signer)`, `await tx.completeFeeBy(signer)`, `await signer.sendTransaction(tx)`. +4. **Save `sporeId`** — It is the Type script args; required for all subsequent transfer/melt operations. +5. **Transfer** — `const { tx } = await spore.transferSpore({ signer, id: sporeId, to: newOwnerLock })`. Then `completeFeeBy` and `sendTransaction`. +6. **Melt (destroy)** — `const { tx } = await spore.meltSpore({ signer, id: sporeId })`. **Irreversible** — permanently destroys the NFT and all on-chain content. + +```typescript +import { spore } from "@ckb-ccc/spore"; // or ccc.spore from @ckb-ccc/shell + +// Create a Spore (stores content permanently on-chain) +const { tx, id: sporeId } = await spore.createSpore({ + signer, + data: { + contentType: "text/plain", + content: new TextEncoder().encode("Hello, Spore!"), + }, +}); +await tx.completeInputsByCapacity(signer); +await tx.completeFeeBy(signer); +const txHash = await signer.sendTransaction(tx); +// Save sporeId — it's the Type script args, needed for transfer/melt + +// Transfer a Spore +const { script: newOwner } = await ccc.Address.fromString(recipientAddr, signer.client); +const { tx: transferTx } = await spore.transferSpore({ signer, id: sporeId, to: newOwner }); +await transferTx.completeFeeBy(signer); +await signer.sendTransaction(transferTx); + +// Melt (destroy) a Spore — irreversible +const { tx: meltTx } = await spore.meltSpore({ signer, id: sporeId }); +await meltTx.completeFeeBy(signer); +await signer.sendTransaction(meltTx); +``` + +Spore stores content fully on-chain. Large content (images) requires substantial CKB +capacity. Text Spore ≈ 200+ CKB minimum. + +--- + +## Gotchas (Spore-specific) + +| Symptom / Error | Cause | Fix | +|---|---|---| +| `createSpore` throws on `clusterId` | `clusterMode` not specified | Specify `clusterMode: "lockProxy" \| "clusterCell" \| "skip"` when `clusterId` is set | +| DOB doesn't render as expected on wallets/explorers | `contentType` doesn't match the DOB version actually encoded in the cluster's decoder chain | A DOB/0-only cluster → `contentType: "dob/0"`; a cluster with both dob0+dob1 decoders → `contentType: "dob/1"`. Mismatching these is an easy copy-paste mistake between examples — verify against the official protocol docs (`docs.spore.pro`), not just a similar-looking example | +| `btcfs://...` / `ipfs://...` image doesn't display | These aren't URLs a browser can load directly | Needs a resolver (e.g. JoyID's `dob-render-sdk`) to convert to a displayable image before rendering | + +--- + +## Checklist (Spore-specific) + +- [ ] **Spore cluster** — `clusterMode` specified when `clusterId` is set in `createSpore()` + +Also check the checklists in `ckb-ccc-fundamentals` and `ckb-ccc-transactions`. diff --git a/skills/ckb-ccc-transactions/SKILL.md b/skills/ckb-ccc-transactions/SKILL.md new file mode 100644 index 00000000..ba7970d4 --- /dev/null +++ b/skills/ckb-ccc-transactions/SKILL.md @@ -0,0 +1,94 @@ +--- +name: ckb-ccc-transactions +description: Covers composing and sending CKB transactions with CCC — building transaction outputs, completing inputs and fees, adding cell dependencies, and querying the chain (cells by lock, balance, transaction confirmation). Use when the user asks how to build, compose, or send a transaction, add cell deps, or query on-chain data — even if they just say "transfer CKB" or "check this transaction" without naming CCC methods. UDT and Spore transfers build on this pattern; see ckb-ccc-udt / ckb-ccc-spore for those specifics. Requires a connected Signer — see ckb-ccc-signer-setup if one hasn't been created yet. +metadata: + author: ckb-devrel + version: "1.0.0" + role: spoke + depends-on: "ckb-ccc-fundamentals, ckb-ccc-signer-setup" + priority: normal +--- + +# CKB CCC — Transactions + +The canonical CCC transaction pattern. This applies to plain CKB transfers and is the foundation that `ckb-ccc-udt` and `ckb-ccc-spore` build on top of — those skills only cover what's different for their asset type, not the full pattern again. + +You need a connected `Signer` before any of this — see `ckb-ccc-signer-setup`. + +--- + +## Standard transaction composition pattern + +1. **Resolve recipient** — `const { script: lock } = await ccc.Address.fromString(toAddress, signer.client)`. +2. **Build transaction** — `const tx = ccc.Transaction.from({ outputs: [{ capacity: ccc.fixedPointFrom("100"), lock }] })`. +3. **Complete inputs** — `await tx.completeInputsByCapacity(signer)` — must come before fee calculation. +4. **Pay fee** — `await tx.completeFeeBy(signer)` — omit fee rate argument to use automatic network rate. +5. **Send** — `const txHash = await signer.sendTransaction(tx)`. +6. **Verify** — Optionally wait for confirmation: `await signer.client.waitTransaction(txHash, 1)`. + +**This order is mandatory**: `outputs declared → completeInputsByCapacity → completeFeeBy → sendTransaction`. +Calling `completeFeeBy` before `completeInputsByCapacity` produces an incorrect fee. + +--- + +## Cell Dep Management + +```typescript +// Add deps for built-in scripts (preferred) +await tx.addCellDepsOfKnownScripts(client, ccc.KnownScript.XUdt); + +// Add custom cell dep +tx.addCellDeps({ outPoint: { txHash: "0x...", index: 0 }, depType: "depGroup" }); +``` + +`signer.prepareTransaction()` (called automatically inside `sendTransaction`) adds +deps for the signer's own lock script. Do not add them again manually. + +--- + +## Querying the Chain + +```typescript +// Iterate cells by lock script (async generator — streams lazily) +for await (const cell of client.findCellsByLock(lockScript, null, true)) { + console.log(cell.outPoint, cell.cellOutput.capacity); +} + +// Wait for transaction confirmation +await client.waitTransaction(txHash, 1); // 1 confirmation, 60s timeout default + +// Get balance +const balance = await signer.getBalance(); // total Shannon across all addresses +``` + +--- + +## Gotchas (transaction-specific) + +| Symptom / Error | Cause | Fix | +|---|---|---| +| `"not enough capacity"` | Insufficient CKB | Fund the address; each output cell needs ≥ 61 CKB | +| `"InsufficientCellCapacity"` | Output cell below minimum | Each basic output needs ≥ 61 CKB capacity; use CCC helpers, not manual calculation | +| Transaction rejected by node | Fee too low or missing | Always call `completeFeeBy` after `completeInputsByCapacity`; omit fee rate arg for automatic rate | +| `addCellDepsOfKnownScripts is not a function` | Wrong import or outdated version | Use `@ckb-ccc/shell` not `@ckb-ccc/core` for backend; update CCC to latest | + +--- + +## Hallucination Guard + +- ❌ `new ccc.Transaction()` — always use `ccc.Transaction.from({ outputs: [...] })` +- ❌ Manually computing fees — always use `completeFeeBy` +- ❌ Manually selecting input cells — always use `completeInputsByCapacity` +- ❌ Calling `completeFeeBy` before `completeInputsByCapacity` — order is mandatory +- ❌ `tx.addCellDepsOfKnownScripts` without `await` — it's async + +--- + +## Checklist (transaction-specific) + +- [ ] **Transaction order** — `outputs declared → completeInputsByCapacity → completeFeeBy → sendTransaction` +- [ ] **Capacity** — Output cells have ≥ 61 CKB (CCC helpers enforce this; manual calc may not) +- [ ] **Fee rate** — `completeFeeBy` called without explicit rate (automatic) unless custom rate needed +- [ ] **Error handling** — `try/catch` around `sendTransaction` + +Also check the cross-cutting checklist in `ckb-ccc-fundamentals`. diff --git a/skills/ckb-ccc-udt/SKILL.md b/skills/ckb-ccc-udt/SKILL.md new file mode 100644 index 00000000..378ae493 --- /dev/null +++ b/skills/ckb-ccc-udt/SKILL.md @@ -0,0 +1,365 @@ +--- +name: ckb-ccc-udt +description: Covers issuing, transferring, and reading metadata for UDT / xUDT fungible tokens on CKB with the current CCC UDT API (`ccc.udt.Udt`, `@ckb-ccc/udt`). NOTE: ckb-devrel is releasing a replacement, `@ckb-ccc/coin`, within the next few weeks — once it ships, `@ckb-ccc/udt` will be removed and unmaintained, not kept for compatibility. This skill still teaches the current `@ckb-ccc/udt`-based pattern since it is the only working option until then, and this file will be replaced in place once `@ckb-ccc/coin` ships. Use when the user asks about UDT, xUDT, fungible tokens, token issuance, or token transfers on CKB — even if they just say "token" without naming UDT specifically. Builds on the standard transaction pattern in ckb-ccc-transactions. +metadata: + author: ckb-devrel + version: "1.0.0" + role: spoke + depends-on: "ckb-ccc-fundamentals, ckb-ccc-transactions" + priority: normal + status: "current API — @ckb-ccc/coin expected within weeks; @ckb-ccc/udt will then be removed/unmaintained. This file becomes a redirect stub for one release cycle (new content goes in ckb-ccc-coin/, this directory is deleted afterward" +--- + +# CKB CCC — UDT Tokens + +Covers xUDT (extensible User Defined Token) issuance, transfer, and metadata reads. Assumes a connected `Signer` (see `ckb-ccc-signer-setup`) and the standard transaction pattern (see `ckb-ccc-transactions`) — this skill only adds the UDT-specific steps on top of that pattern. + +--- + +## Issuing xUDT tokens (Single-Use-Seal pattern) + +xUDT issuance requires the Single-Use-Seal (SUS) pattern, which involves three sequential transactions to ensure token uniqueness and authority. + +```typescript +import { ccc } from "@ckb-ccc/ccc"; +import { signer } from "@ckb-ccc/playground"; + +/* ============================================================ + * Types + * ============================================================ */ + +/** + * @public + * @category Token + */ +interface TokenMetadata { + decimals: number; + symbol: string; + /** Falls back to `symbol` when omitted/blank */ + name?: string; +} + +/** + * The 3 on-chain steps of the SingleUseLock (SUS) issuance flow, in order. + * @public + * @category Token + */ +type IssueStep = "seal" | "owner" | "mint"; + +/** + * @public + * @category Token + */ +interface IssueXudtSusParams extends TokenMetadata { + /** Human-readable total supply, e.g. 100_000_000 (converted to Shannon internally) */ + totalSupply: ccc.FixedPointLike; + /** Optional hook, fired after each of the 3 on-chain steps is broadcast */ + onProgress?: (step: IssueStep, txHash: ccc.Hex) => void; +} + +/** + * @public + * @category Token + */ +interface IssueXudtSusResult { + sealTxHash: ccc.Hex; + ownerTxHash: ccc.Hex; + mintTxHash: ccc.Hex; + typeScriptHash: ccc.Hex; +} + +/* ============================================================ + * Token metadata encoding (pure, no chain I/O) + * ============================================================ */ + +/** + * Encodes xUDT token metadata (decimals + name + symbol) into the + * `UniqueType` cell data layout. + * @public + * @category Token + * + * @param metadata - The token's decimals, symbol, and optional name. + * @returns The encoded bytes to be used as `outputsData` for the `UniqueType` cell. + * @throws If `symbol` is missing, `decimals` is out of the 0-255 range, or + * either `symbol`/`name` exceeds 255 UTF-8 bytes. + */ +function encodeTokenInfo({ + decimals, + symbol, + name, +}: TokenMetadata): Uint8Array { + if (!symbol) { + throw new Error("symbol is required"); + } + if (decimals < 0 || decimals > 255) { + throw new Error("decimals must be within 0-255 (1-byte field)"); + } + + const symbolBytes = ccc.bytesFrom(symbol, "utf8"); + const nameBytes = ccc.bytesFrom(name?.trim() ? name : symbol, "utf8"); + + // Each string is length-prefixed with a single byte -> hard cap at 255 bytes + if (symbolBytes.length > 255 || nameBytes.length > 255) { + throw new Error("symbol/name must each be <= 255 bytes when UTF-8 encoded"); + } + + return ccc.bytesConcat( + ccc.numToBytes(decimals, 1), + ccc.numToBytes(nameBytes.length, 1), + nameBytes, + ccc.numToBytes(symbolBytes.length, 1), + symbolBytes, + ); +} + +/** + * Issues an xUDT token using the Single-Use-Seal (SUS) pattern: a seal cell + * anchors a `SingleUseLock` owner cell, which is then consumed together with + * the seal to mint the xUDT cell and its `UniqueType` metadata cell. + * + * @public + * @category Blockchain + * @category Token + */ +class XudtSusIssuer { + /** + * @param signer - The signer used to fund and sign all 3 transactions. + */ + constructor(private readonly signer: ccc.Signer) {} + + /** + * Runs the full seal -> owner -> mint flow. + * + * @param params - Token metadata, total supply, and an optional progress hook. + * @returns The tx hashes of all 3 steps and the resulting xUDT type script hash. + * + * @example + * ```typescript + * const result = await new XudtSusIssuer(signer).issue({ + * decimals: 8, + * symbol: "SPARK", + * totalSupply: 100_000_000, + * onProgress: (step, txHash) => console.log(step, txHash), + * }); + * ``` + */ + async issue(params: IssueXudtSusParams): Promise { + const { script: ownerLock } = await this.signer.getRecommendedAddressObj(); + + const sealTxHash = await this.createSealCell(ownerLock); + params.onProgress?.("seal", sealTxHash); + + const singleUseLock = await ccc.Script.fromKnownScript( + this.signer.client, + ccc.KnownScript.SingleUseLock, + ccc.OutPoint.from({ txHash: sealTxHash, index: 0 }).toBytes(), + ); + + const ownerTxHash = await this.createOwnerCell(singleUseLock); + params.onProgress?.("owner", ownerTxHash); + + const { mintTxHash, typeScriptHash } = await this.mintToken({ + sealTxHash, + ownerTxHash, + singleUseLock, + ownerLock, + metadata: params, + }); + params.onProgress?.("mint", mintTxHash); + + return { sealTxHash, ownerTxHash, mintTxHash, typeScriptHash }; + } + + /** Step 1 - the "seal" cell that SingleUseLock will bind to and that mint() later consumes as proof */ + private async createSealCell(lock: ccc.Script): Promise { + const tx = ccc.Transaction.from({ outputs: [{ lock }] }); + await tx.completeInputsByCapacity(this.signer); + await tx.completeFeeBy(this.signer); + const txHash = await this.signer.sendTransaction(tx); + + // Reserve this output so later completeInputsByCapacity() calls in this + // flow don't accidentally spend it as generic fee-funding - it must + // survive untouched until the mint transaction consumes it as the seal. + await this.signer.client.cache.markUnusable({ txHash, index: 0 }); + + return txHash; + } + + /** Step 2 - the owner cell, locked with SingleUseLock bound to the seal's outpoint */ + private async createOwnerCell(singleUseLock: ccc.Script): Promise { + const tx = ccc.Transaction.from({ outputs: [{ lock: singleUseLock }] }); + await tx.completeInputsByCapacity(this.signer); + await tx.completeFeeBy(this.signer); + return this.signer.sendTransaction(tx); + } + + /** Step 3 - consume seal + owner cells to mint the xUDT cell and its UniqueType metadata cell */ + private async mintToken(args: { + sealTxHash: ccc.Hex; + ownerTxHash: ccc.Hex; + singleUseLock: ccc.Script; + ownerLock: ccc.Script; + metadata: IssueXudtSusParams; + }): Promise<{ mintTxHash: ccc.Hex; typeScriptHash: ccc.Hex }> { + const { sealTxHash, ownerTxHash, singleUseLock, ownerLock, metadata } = args; + const { decimals, symbol, name, totalSupply } = metadata; + + const xudtType = await ccc.Script.fromKnownScript( + this.signer.client, + ccc.KnownScript.XUdt, + singleUseLock.hash(), + ); + const uniqueType = await ccc.Script.fromKnownScript( + this.signer.client, + ccc.KnownScript.UniqueType, + "00".repeat(32), // placeholder - patched below with the real TypeId once inputs are known + ); + + const tx = ccc.Transaction.from({ + inputs: [ + { previousOutput: { txHash: sealTxHash, index: 0 } }, + { previousOutput: { txHash: ownerTxHash, index: 0 } }, + ], + outputs: [ + { lock: ownerLock, type: xudtType }, + { lock: ownerLock, type: uniqueType }, + ], + outputsData: [ + ccc.numLeToBytes(ccc.fixedPointFrom(totalSupply, decimals), 16), + encodeTokenInfo({ decimals, symbol, name }), + ], + }); + + await tx.addCellDepsOfKnownScripts( + this.signer.client, + ccc.KnownScript.SingleUseLock, + ccc.KnownScript.XUdt, + ccc.KnownScript.UniqueType, + ); + + await tx.completeInputsByCapacity(this.signer); + + // UniqueType's args must be a TypeId derived from the first input + output + // index, which is only known once inputs are finalized above. + if (!tx.outputs[1].type) { + throw new Error("UniqueType output unexpectedly missing before TypeId patch"); + } + tx.outputs[1].type.args = ccc.hexFrom( + ccc.bytesFrom(ccc.hashTypeId(tx.inputs[0], 1)).slice(0, 20), + ); + + await tx.completeFeeBy(this.signer); + const mintTxHash = await this.signer.sendTransaction(tx); + await this.signer.client.waitTransaction(mintTxHash); + + return { mintTxHash, typeScriptHash: tx.outputs[0].type!.hash() }; + } +} + +/* ============================================================ + * Convenience entrypoint - keeps the same call shape as before + * ============================================================ */ + +/** + * Issues an xUDT token via the Single-Use-Seal pattern using the + * playground's default signer. + * @public + * @category Token + * + * @example + * ```typescript + * const result = await issueXudtWithSUS({ + * decimals: 8, + * symbol: "SPARK", + * totalSupply: 100_000_000, + * onProgress: (step, txHash) => console.log(step, txHash), + * }); + * console.log(result); + * ``` + */ +export async function issueXudtWithSUS( + params: IssueXudtSusParams, +): Promise { + return new XudtSusIssuer(signer).issue(params); +} + +// Example usage in the playground: +const result = await issueXudtWithSUS({ + decimals: 8, + symbol: "ZXMOTO", + totalSupply: 100_000_000, + onProgress: (step, txHash) => console.log(step, txHash), +}); +console.log(result); +``` + +## Transferring UDT tokens + +1. **Construct UDT instance** — Resolve type script: `await ccc.Script.fromKnownScript(client, ccc.KnownScript.XUdt, args)`. Get code OutPoint from cell deps. Create: `new ccc.udt.Udt(code, typeScript)`. +2. **Build transfer** — `const { res: tx } = await udt.transfer(signer, [{ to: lock, amount: 100n }])`. +3. **Complete UDT inputs** — `tx = await udt.completeBy(tx, signer)` — adds UDT inputs and change output. **Do not skip**: omitting this loses tokens permanently. +4. **Complete CKB capacity** — `await tx.completeInputsByCapacity(signer)`. +5. **Pay fee and send** — `await tx.completeFeeBy(signer)`, then `await signer.sendTransaction(tx)`. +6. **Read metadata (SSRI tokens only)** — `udt.name()`, `udt.symbol()`, `udt.decimals()`, `udt.icon()`. Always check return value is not `undefined` — legacy sUDT tokens do not implement SSRI. + +```typescript + +async function transferUdt(signer: ccc.Signer, receiverAddress: string) { + const { script: recipientLock } = await ccc.Address.fromString(receiverAddress, signer.client); + + const type = await ccc.Script.fromKnownScript( + signer.client, ccc.KnownScript.XUdt, "0x" + ); + const code = (await signer.client.getCellDeps( + (await signer.client.getKnownScript(ccc.KnownScript.XUdt)).cellDeps + ))[0].outPoint; + + // Construct a Udt instance + const udt = new ccc.udt.Udt(code, type); + + // Transfer tokens — always three steps after udt.transfer + const decimals = 8; + let { res: tx } = await udt.transfer(signer, [{ to: recipientLock, amount: ccc.fixedPointFrom(100, decimals)}]); + tx = await udt.completeBy(tx, signer); // fill UDT inputs + change + await tx.completeInputsByCapacity(signer); // fill CKB capacity + await tx.completeFeeBy(signer); + const txHash = await signer.sendTransaction(tx); + + return txHash; +} +``` + +**Rule**: UDT transfers need `completeBy` (UDT) then `completeInputsByCapacity` (CKB) — order matters. + +--- + +## Gotchas (UDT-specific) + +| Symptom / Error | Cause | Fix | +|---|---|---| +| UDT tokens lost after transfer | `udt.completeBy()` not called | Always call `udt.completeBy(tx, signer)` before `completeInputsByCapacity`; it adds the token change output | +| `udt.name()` / `symbol()` returns `undefined` | Token doesn't implement SSRI | Only xUDT with SSRI support returns metadata; always guard with `?? "unknown"` | +| On-chain amount is 10^decimals smaller than expected | Used human-facing amount directly instead of scaling by decimals | Always use `ccc.fixedPointFrom(humanFacingAmount, decimals)` for both mint and transfer — the on-chain integer is `display * 10^decimals`, not the human-facing number | + +--- + +## Q&A + +**Q: How do I find the args for a specific xUDT?** + +A: You can view it in the CKB explorer: +1. Find the xUDT in the CKB explorer (e.g., https://testnet.explorer.nervos.org/xudt/0xb5378a3a22ed158233a4493ec0994483aada2bc6de9446952184653d90bdf889) +2. Navigate to the xUDT's detail page +3. In the info section, look for the **Type Script** section and find the **Args** field +4. This Args value is the xUDT's args, used when constructing `ccc.Script.fromKnownScript(client, ccc.KnownScript.XUdt, args)` + +--- + +## Checklist (UDT-specific) + +- [ ] **UDT change** — `udt.completeBy()` called before `completeInputsByCapacity` for token transfers +- [ ] **Supply scaling** — `totalSupply` (and any amount) is the on-chain integer (`display * 10^decimals`), not the human-facing number +- [ ] **Tested on testnet first** — for real issuance, run the full flow on `ClientPublicTestnet` and verify in an explorer before switching to `ClientPublicMainnet` +- [ ] **symbol vs name** — `symbol` holds the ticker/display identifier; `name` is supplementary description + +Also check the checklists in `ckb-ccc-fundamentals` and `ckb-ccc-transactions`. \ No newline at end of file From 784d0d7af632cdfc2f931ecdac1363124e6ad8f6 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 21:52:38 +0800 Subject: [PATCH 02/15] docs: restructure AI resources and remove monolithic skill.md - Remove packages/docs/public/skill.md (475 lines) - the old monolithic skill file - Add AI Resources section metadata (meta.json and meta.zh.json) - Update llms.txt route to reference skill.md as "Agent skills index" and add AI Resources link - Refactor playground guide Example 2 from UDT token issuance to Spore creation (both EN and ZH) - Update verify-llm.mjs script references This is part of the multi-skill architecture migration where the single skill.md is replaced by a hub skill (ckb-ccc-fundamentals) plus spoke skills for each task domain. --- packages/docs/app/llms.txt/route.ts | 4 +- .../docs/content/docs/ai-resources/meta.json | 8 + .../content/docs/ai-resources/meta.zh.json | 8 + .../docs/getting-started/introduction.mdx | 5 +- .../docs/content/docs/guides/playground.mdx | 40 +- .../content/docs/guides/playground.zh.mdx | 38 +- packages/docs/public/skill.md | 475 ------------------ packages/docs/scripts/verify-llm.mjs | 7 +- 8 files changed, 49 insertions(+), 536 deletions(-) create mode 100644 packages/docs/content/docs/ai-resources/meta.json create mode 100644 packages/docs/content/docs/ai-resources/meta.zh.json delete mode 100644 packages/docs/public/skill.md diff --git a/packages/docs/app/llms.txt/route.ts b/packages/docs/app/llms.txt/route.ts index 41bb80b7..039edc43 100644 --- a/packages/docs/app/llms.txt/route.ts +++ b/packages/docs/app/llms.txt/route.ts @@ -17,8 +17,7 @@ Notes for AI agents: - All links below are absolute URLs. Append \`.md\` to any docs page URL to get its raw Markdown version (e.g. ${siteUrl}/en/docs/getting-started/introduction.md). - The entire documentation concatenated into a single file: ${siteUrl}/llms-full.txt -- Agent skill (product-specific operating guidance): ${siteUrl}/skill.md -- Blog (release notes & tutorials): ${siteUrl}/en/blog/rgbpp-new-sdk-preview +- Agent skills index (product-specific operating guidance, split by topic): ${siteUrl}/skill.md - Chinese versions of every page are available by replacing \`/en/\` with \`/zh/\` in the URL. - API reference: https://api.ckbccc.com | Playground: https://live.ckbccc.com | Source: https://github.com/ckb-devrel/ccc @@ -31,6 +30,7 @@ Notes for AI agents: - [Core Packages](${siteUrl}/en/docs/packages/core-packages): Core primitives, aggregated entry points, and wallet connectors. - [Protocol Support Layer](${siteUrl}/en/docs/packages/protocol-sdks): Protocol-level SDKs — Spore, UDT, SSRI, and Lumos patches. - [Wallet Integrations](${siteUrl}/en/docs/packages/wallet-integrations): Connect any wallet through one unified \`Signer\` interface. +- [AI Resources](${siteUrl}/en/docs/ai-resources): How this documentation is built for AI agents, and how developers should configure and prompt their own AI tools to use it correctly. ## Common tasks (recommended reading paths) diff --git a/packages/docs/content/docs/ai-resources/meta.json b/packages/docs/content/docs/ai-resources/meta.json new file mode 100644 index 00000000..8c64d69a --- /dev/null +++ b/packages/docs/content/docs/ai-resources/meta.json @@ -0,0 +1,8 @@ +{ + "title": "AI Resources", + "pages": [ + "set-up-ai-tools", + "prompting-best-practices", + "verify-and-troubleshoot" + ] +} diff --git a/packages/docs/content/docs/ai-resources/meta.zh.json b/packages/docs/content/docs/ai-resources/meta.zh.json new file mode 100644 index 00000000..531317af --- /dev/null +++ b/packages/docs/content/docs/ai-resources/meta.zh.json @@ -0,0 +1,8 @@ +{ + "title": "AI 资源", + "pages": [ + "set-up-ai-tools", + "prompting-best-practices", + "verify-and-troubleshoot" + ] +} diff --git a/packages/docs/content/docs/getting-started/introduction.mdx b/packages/docs/content/docs/getting-started/introduction.mdx index ff68aaa2..44b2ba9b 100644 --- a/packages/docs/content/docs/getting-started/introduction.mdx +++ b/packages/docs/content/docs/getting-started/introduction.mdx @@ -4,7 +4,7 @@ description: Everything you need to build on CKB — wallets, transactions, sign icon: BookOpen --- -import { Rocket, Package, BookOpen, Map } from 'lucide-react'; +import { Rocket, Package, BookOpen, Map, Bot } from 'lucide-react'; CKB is a UTXO-based blockchain that natively supports Bitcoin and EVM wallets — making it uniquely suited for cross-chain applications. @@ -55,4 +55,7 @@ CCC powers production applications across the CKB ecosystem — including } href="../guides/connect-wallets"> Find the recipe for what you're trying to build — step-by-step. + } href="../ai-resources"> + Point Cursor, Claude Code, Copilot, or Windsurf at CCC's docs in under 2 minutes so they stop guessing at CKB APIs. + \ No newline at end of file diff --git a/packages/docs/content/docs/guides/playground.mdx b/packages/docs/content/docs/guides/playground.mdx index ab807361..70a359a7 100644 --- a/packages/docs/content/docs/guides/playground.mdx +++ b/packages/docs/content/docs/guides/playground.mdx @@ -115,45 +115,29 @@ await render(tx); Click **Step** instead of **Run** to pause at each `render()` call. Click **Continue** to advance to the next breakpoint. This is useful for studying how CCC builds a transaction step by step. -Now that we've mastered the basic transfer flow, let's explore a more complex scenario — issuing a token on chain. +Now that we've mastered the basic transfer flow, let's explore a more complex scenario — creating a Spore. -## Example 2 — Issue a custom UDT token +## Example 2 — Create a Spore -This example shows how to issue a UDT (User Defined Token) — a fungible token on CKB: +This example shows how to create a Spore — a non-fungible token on CKB: ```typescript import { ccc } from "@ckb-ccc/ccc"; -import { render, signer } from "@ckb-ccc/playground"; - -// Get the signer's lock script — this will be the token owner -const lock = (await signer.getRecommendedAddressObj()).script; -console.log("Token owner lock:", lock); - -// Build a transaction with the UDT output -const tx = ccc.Transaction.from({ - outputs: [{ lock }], - outputsData: [ccc.numLeToBytes(1000000, 16)], // initial supply: 1,000,000 tokens +import { render, signer, client } from "@ckb-ccc/playground"; + +const { tx, id: sporeId } = await ccc.spore.createSpore({ + signer, + data: { + contentType: "text/plain", + content: new TextEncoder().encode("Hello, Spore!"), + }, }); await render(tx); -// The Type script for xUDT uses the first input's outPoint as a unique ID -// We need to add inputs first, then set the Type script await tx.completeInputsByCapacity(signer); await render(tx); -const firstInput = tx.inputs[0]; -const typeScript = await ccc.Script.fromKnownScript( - signer.client, - ccc.KnownScript.XUdt, - (await firstInput.getCell(signer.client)).cellOutput.lock.hash(), -); -await render(tx); - -tx.outputs[0].type = typeScript; -await render(tx); - -// Recalculate inputs (the Type script changes occupied capacity) -await tx.completeFeeBy(signer, 1000); +await tx.completeFeeBy(signer); await render(tx); ``` diff --git a/packages/docs/content/docs/guides/playground.zh.mdx b/packages/docs/content/docs/guides/playground.zh.mdx index b875be23..1aa1eb80 100644 --- a/packages/docs/content/docs/guides/playground.zh.mdx +++ b/packages/docs/content/docs/guides/playground.zh.mdx @@ -115,43 +115,27 @@ await render(tx); 掌握了基本的转账流程后,来看一个更复杂的场景——在链上发行代币。 -## 示例二:发行自定义 UDT 代币 +## 示例二:创建 Spore -本示例演示如何在 CKB 上发行用户自定义代币(User-Defined Token,简称 UDT): +本示例演示如何在 CKB 上创建一个 Spore: ```typescript import { ccc } from "@ckb-ccc/ccc"; -import { render, signer } from "@ckb-ccc/playground"; - -// 获取 signer 的 Lock Script,作为代币所有者 -const lock = (await signer.getRecommendedAddressObj()).script; -console.log("代币所有者 Lock:", lock); - -// 构建包含 UDT 输出的交易 -const tx = ccc.Transaction.from({ - outputs: [{ lock }], - outputsData: [ccc.numLeToBytes(1000000, 16)], // 初始供应量:1,000,000 个代币 +import { render, signer, client } from "@ckb-ccc/playground"; + +const { tx, id: sporeId } = await ccc.spore.createSpore({ + signer, + data: { + contentType: "text/plain", + content: new TextEncoder().encode("Hello, Spore!"), + }, }); await render(tx); -// xUDT 的 Type Script 以第一个输入的 OutPoint 作为唯一 ID -// 需要先添加输入,再设置 Type Script await tx.completeInputsByCapacity(signer); await render(tx); -const firstInput = tx.inputs[0]; -const typeScript = await ccc.Script.fromKnownScript( - signer.client, - ccc.KnownScript.XUdt, - (await firstInput.getCell(signer.client)).cellOutput.lock.hash(), -); -await render(tx); - -tx.outputs[0].type = typeScript; -await render(tx); - -// 重新计算输入(Type Script 会改变占用容量) -await tx.completeFeeBy(signer, 1000); +await tx.completeFeeBy(signer); await render(tx); ``` diff --git a/packages/docs/public/skill.md b/packages/docs/public/skill.md deleted file mode 100644 index 5ab20d21..00000000 --- a/packages/docs/public/skill.md +++ /dev/null @@ -1,475 +0,0 @@ ---- -name: ckb-ccc -description: > - Expert guidance for building on CKB (Nervos Common Knowledge Base) using the - CCC TypeScript/JavaScript SDK. Use this skill whenever a user asks about CKB - development, CCC SDK usage, building dApps on CKB, connecting wallets to CKB, - composing CKB transactions, working with UDT tokens, Spore protocol (DOBs/NFTs), - NervosDAO, or anything involving @ckb-ccc/* packages. Also trigger for questions - about CKB's Cell model, CKB vs EVM differences, or migrating from Lumos to CCC. ---- - -# CKB CCC Development Skill - -CCC (CKBers' Codebase) is the one-stop TypeScript/JavaScript SDK for CKB. -Full docs: https://docs.ckbccc.com — llms.txt: https://docs.ckbccc.com/llms.txt - ---- - -## Critical: CKB is NOT an Account Model Chain - -CKB uses the **Cell model** — a generalized UTXO model. This is the #1 source of -AI hallucination when helping CKB developers. Internalize this before generating any code: - -| Concept | Ethereum (EVM) | CKB | -|---|---|---| -| State unit | Account balance | Cell (capacity + lock + type + data) | -| Ownership | `msg.sender` | Lock script (verified by CKB VM) | -| Asset rules | Smart contract storage | Type script | -| "Send tokens" | Transfer from account | Consume input Cells, create output Cells | -| Minimum unit | wei (1e-18 ETH) | Shannon (1 CKB = 100,000,000 Shannon) | - -**There are no accounts, no balances, no `msg.sender` in CKB.** - ---- - -## Package Selection — Always Get This Right First - -| Scenario | Install | Import | -|---|---|---| -| React/Next.js dApp | `@ckb-ccc/connector-react` | `import { ccc } from "@ckb-ccc/connector-react"` | -| Node.js backend / scripts | `@ckb-ccc/shell` | `import { ccc } from "@ckb-ccc/shell"` | -| Custom wallet UI (non-React) | `@ckb-ccc/ccc` | `import { ccc } from "@ckb-ccc/ccc"` | -| Framework-agnostic Web Component | `@ckb-ccc/connector` | Web Component `` | -| Library authoring (minimal deps) | `@ckb-ccc/core` | `import { ccc } from "@ckb-ccc/core"` | - -**Rule**: `@ckb-ccc/shell` and `@ckb-ccc/connector-react` already re-export everything -from `@ckb-ccc/core` — do not install core separately unless authoring a library. - ---- - -## Workflows - -### Building a React dApp with wallet connection - -1. **Install and wrap Provider** — Install `@ckb-ccc/connector-react`. Wrap root component with ``. Add `"use client"` for Next.js App Router. -2. **Add wallet connection UI** — Use `ccc.useCcc()` to get `open`, `wallet`, `signerInfo`, `disconnect`. Render connect/disconnect button based on `signerInfo` presence. -3. **Get signer** — Call `const signer = ccc.useSigner()` inside a component; guard with `if (!signer) return` before any transaction operation. -4. **Resolve recipient** — `const { script: lock } = await ccc.Address.fromString(toAddress, signer.client)`. -5. **Build transaction** — `const tx = ccc.Transaction.from({ outputs: [{ capacity: ccc.fixedPointFrom("100"), lock }] })`. -6. **Complete inputs** — `await tx.completeInputsByCapacity(signer)` — must come before fee calculation. -7. **Pay fee** — `await tx.completeFeeBy(signer)` — omit fee rate argument to use automatic network rate. -8. **Send** — `const txHash = await signer.sendTransaction(tx)`. -9. **Verify** — Optionally wait for confirmation: `await signer.client.waitTransaction(txHash, 1)`. - -```tsx -"use client"; -import { ccc } from "@ckb-ccc/connector-react"; - -// Root — wrap once -export default function App({ children }) { - return {children}; -} - -// Component — use inside Provider -function ConnectButton() { - const { open, disconnect, wallet, signerInfo } = ccc.useCcc(); - const signer = ccc.useSigner(); - return signerInfo - ? - : ; -} -``` - -### Building a Node.js backend script - -1. **Import and connect** — Install `@ckb-ccc/shell`. Create client: `new ccc.ClientPublicTestnet()` or `ClientPublicMainnet()`. -2. **Create signer** — `new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!)`. Never hardcode keys. -3. **Check connection** — Some signers require `await signer.connect()`. Check with `await signer.isConnected()` if operations fail unexpectedly. -4. **Query data** — `await signer.getRecommendedAddress()`, `await signer.getBalance()`, `for await (const cell of client.findCellsByLock(...))`. -5. **Build and send** — Follow steps 4–8 from the React workflow above; transaction pattern is identical. -6. **Verify** — Log `txHash`; use `client.getTransaction(txHash)` to poll for confirmation. - -```typescript -import { ccc } from "@ckb-ccc/shell"; - -// Always load private key from environment — never hardcode -const client = new ccc.ClientPublicTestnet(); // or ClientPublicMainnet -const signer = new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!); -await signer.connect(); - -const address = await signer.getRecommendedAddress(); // "ckt1q..." or "ckb1q..." -const balance = await signer.getBalance(); // bigint in Shannon -``` - -**TypeScript config** — `@ckb-ccc/shell` ships ESM only: -```json -{ "compilerOptions": { "moduleResolution": "bundler" } } -``` - -### Issuing and transferring UDT tokens - -1. **Construct UDT instance** — Resolve type script: `await ccc.Script.fromKnownScript(client, ccc.KnownScript.XUdt, args)`. Get code OutPoint from cell deps. Create: `new ccc.udt.Udt(code, typeScript)`. -2. **Build transfer** — `const { res: tx } = await udt.transfer(signer, [{ to: lock, amount: 100n }])`. -3. **Complete UDT inputs** — `tx = await udt.completeBy(tx, signer)` — adds UDT inputs and change output. **Do not skip**: omitting this loses tokens permanently. -4. **Complete CKB capacity** — `await tx.completeInputsByCapacity(signer)`. -5. **Pay fee and send** — `await tx.completeFeeBy(signer)`, then `await signer.sendTransaction(tx)`. -6. **Read metadata (SSRI tokens only)** — `udt.name()`, `udt.symbol()`, `udt.decimals()`, `udt.icon()`. Always check return value is not `undefined` — legacy sUDT tokens do not implement SSRI. - -### Creating and managing Spore NFTs - -1. **Create Spore** — `const { tx, id: sporeId } = await spore.createSpore({ signer, data: { contentType: "text/plain", content: bytes } })`. -2. **If using a Cluster** — Specify `clusterMode: "lockProxy" | "clusterCell" | "skip"` in `createSpore()`. Omitting it when `clusterId` is set throws. -3. **Complete and send** — `await tx.completeInputsByCapacity(signer)`, `await tx.completeFeeBy(signer)`, `await signer.sendTransaction(tx)`. -4. **Save `sporeId`** — It is the Type script args; required for all subsequent transfer/melt operations. -5. **Transfer** — `const { tx } = await spore.transferSpore({ signer, id: sporeId, to: newOwnerLock })`. Then `completeFeeBy` and `sendTransaction`. -6. **Melt (destroy)** — `const { tx } = await spore.meltSpore({ signer, id: sporeId })`. **Irreversible** — permanently destroys the NFT and all on-chain content. - -### Amount Conversion (Shannon ↔ CKB) - -```typescript -ccc.fixedPointFrom("100") // 100 CKB → 10_000_000_000n Shannon -ccc.fixedPointFrom(100) // same -ccc.fixedPointFrom("0.01") // 0.01 CKB → 1_000_000n Shannon -ccc.fixedPointToString(balance) // bigint Shannon → "100" (human-readable CKB) -``` - -All capacity values in CCC are `bigint` in Shannon. **Never use floating point.** -Always work in Shannon internally; convert to/from CKB only at the UI boundary. - ---- - - - -## Address Handling - -```typescript -// Parse an address string into its Script -const { script: lock } = await ccc.Address.fromString(addressStr, client); - -// Get address from connected signer -const addressStr = await signer.getRecommendedAddress(); -const addressObj = await signer.getRecommendedAddressObj(); // includes Script -const { script: lock } = addressObj; // preferred when you need the lock script -``` - -Address prefix: `ckb` = mainnet, `ckt` = testnet. Mixing them throws. - ---- - -## Known Scripts (KnownScript enum) - -Do **not** hardcode codeHash values. Use `KnownScript`: - -```typescript -// Resolve script from name -const xudtType = await ccc.Script.fromKnownScript( - client, - ccc.KnownScript.XUdt, - "0x", -); - -// Available values (selection): -// KnownScript.Secp256k1Blake160 — standard CKB lock -// KnownScript.XUdt — extensible UDT (fungible tokens) -// KnownScript.NervosDao — NervosDAO deposit/withdraw -// KnownScript.JoyId — JoyID passkey lock -// KnownScript.OmniLock — EVM/BTC cross-chain lock -// KnownScript.NostrLock — Nostr protocol lock -// KnownScript.TypeId — Type ID (upgradeable contracts) -// KnownScript.COTA — COTA NFT standard -// KnownScript.PWLock — PWLock -// KnownScript.UniqueType — unique type identifier -// KnownScript.DidCkb — web5 DID(decentralized identity) on CKB -// KnownScript.AlwaysSuccess — always success lock -// KnownScript.InputTypeProxyLock — proxy lock for input types -// KnownScript.OutputTypeProxyLock — proxy lock for output types -// KnownScript.LockProxyLock — proxy lock for locks -// KnownScript.SingleUseLock — single-use lock -// KnownScript.TypeBurnLock — type burn lock -// KnownScript.EasyToDiscoverType — easy to discover type -// KnownScript.TimeLock — time-locked transfer -``` - ---- - -## Wallet Support Matrix - -CCC bridges multiple chain ecosystems into CKB via a unified `Signer` interface: - -| Package | Wallet(s) | Chain(s) | -|---|---|---| -| `@ckb-ccc/joy-id` | JoyID (passkey, no seed phrase) | CKB / BTC / EVM / Nostr | -| `@ckb-ccc/eip6963` | MetaMask, Rabby, OKX EVM, any EIP-6963 wallet | EVM | -| `@ckb-ccc/uni-sat` | UniSat | BTC | -| `@ckb-ccc/okx` | OKX Wallet | BTC / Nostr | -| `@ckb-ccc/xverse` | Xverse, SATS Connect wallets | BTC | -| `@ckb-ccc/nip07` | nos2x, Alby, NIP-07 extensions | Nostr | -| `@ckb-ccc/utxo-global` | UTXO Global | CKB / BTC / DOGE | -| `@ckb-ccc/rei` | REI Wallet | CKB | - -All wallet packages are pre-bundled in `@ckb-ccc/connector-react` and `@ckb-ccc/ccc`. -Only install individually for custom integrations. - ---- - -## UDT Tokens (Fungible) - -```typescript -import { ccc } from "@ckb-ccc/shell"; - -// Construct a Udt instance -const type = await ccc.Script.fromKnownScript( - signer.client, ccc.KnownScript.XUdt, "0x" -); -const code = (await signer.client.getCellDeps( - (await signer.client.getKnownScript(ccc.KnownScript.XUdt)).cellDeps -))[0].outPoint; - -const udt = new ccc.udt.Udt(code, type); - -// Transfer tokens — always three steps after udt.transfer -let { res: tx } = await udt.transfer(signer, [{ to: recipientLock, amount: 100n }]); -tx = await udt.completeBy(tx, signer); // fill UDT inputs + change -await tx.completeInputsByCapacity(signer); // fill CKB capacity -await tx.completeFeeBy(signer); -const txHash = await signer.sendTransaction(tx); -``` - -**Rule**: UDT transfers need `completeBy` (UDT) then `completeInputsByCapacity` (CKB) — -order matters. - ---- - -## Spore Protocol (DOBs / On-chain NFTs) - -```typescript -import { spore } from "@ckb-ccc/spore"; // or ccc.spore from @ckb-ccc/shell - -// Create a Spore (stores content permanently on-chain) -const { tx, id: sporeId } = await spore.createSpore({ - signer, - data: { - contentType: "text/plain", - content: new TextEncoder().encode("Hello, Spore!"), - }, -}); -await tx.completeInputsByCapacity(signer); -await tx.completeFeeBy(signer); -const txHash = await signer.sendTransaction(tx); -// Save sporeId — it's the Type script args, needed for transfer/melt - -// Transfer a Spore -const { script: newOwner } = await ccc.Address.fromString(recipientAddr, signer.client); -const { tx: transferTx } = await spore.transferSpore({ signer, id: sporeId, to: newOwner }); -await transferTx.completeFeeBy(signer); -await signer.sendTransaction(transferTx); - -// Melt (destroy) a Spore — irreversible -const { tx: meltTx } = await spore.meltSpore({ signer, id: sporeId }); -await meltTx.completeFeeBy(signer); -await signer.sendTransaction(meltTx); -``` - -Spore stores content fully on-chain. Large content (images) requires substantial CKB -capacity. Text Spore ≈ 200+ CKB minimum. - ---- - -## Message Signing and Verification - -```typescript -// Sign (works identically across all wallet types) -const sig = await signer.signMessage("Hello world"); -// sig.signature — hex string -// sig.signType — "CkbSecp256k1" | "EvmPersonal" | "BtcEcdsa" | "JoyId" | ... -// sig.identity — signer's address or public key - -// Verify (static — no connected wallet needed) -const isValid = await ccc.Signer.verifyMessage("Hello world", sig); // true -const isFail = await ccc.Signer.verifyMessage("Wrong", sig); // false -``` - ---- - -## Cell Dep Management - -```typescript -// Add deps for built-in scripts (preferred) -await tx.addCellDepsOfKnownScripts(client, ccc.KnownScript.XUdt); - -// Add custom cell dep -tx.addCellDeps({ outPoint: { txHash: "0x...", index: 0 }, depType: "depGroup" }); -``` - -`signer.prepareTransaction()` (called automatically inside `sendTransaction`) adds -deps for the signer's own lock script. Do not add them again manually. - ---- - -## Querying the Chain - -```typescript -// Iterate cells by lock script (async generator — streams lazily) -for await (const cell of client.findCellsByLock(lockScript, null, true)) { - console.log(cell.outPoint, cell.cellOutput.capacity); -} - -// Wait for transaction confirmation -await client.waitTransaction(txHash, 1); // 1 confirmation, 60s timeout default - -// Get balance -const balance = await signer.getBalance(); // total Shannon across all addresses -``` - ---- - -## Common Gotchas - -| Symptom / Error | Cause | Fix | -|---|---|---| -| `ERR_REQUIRE_ESM` | CJS project importing CCC | Add `"type": "module"` to package.json or use dynamic `import()` | -| `createContext is not a function` | Missing `"use client"` in Next.js | Add `"use client"` to any file using `ccc.Provider` or `useCcc()` | -| `Property '*' not found on ccc` | Wrong `moduleResolution` | Set `"moduleResolution": "bundler"` in tsconfig; do not use `node` or `classic` | -| `"not enough capacity"` | Insufficient CKB | Fund the address; each output cell needs ≥ 61 CKB | -| `"InsufficientCellCapacity"` | Output cell below minimum | Each basic output needs ≥ 61 CKB capacity; use CCC helpers, not manual calculation | -| `"invalid private key"` | Wrong key format | Key must be 32 bytes (64 hex chars); the `0x` prefix is optional | -| Transaction rejected by node | Fee too low or missing | Always call `completeFeeBy` after `completeInputsByCapacity`; omit fee rate arg for automatic rate | -| Wrong address on wrong network | Mainnet/testnet mismatch | `ckb` prefix = mainnet, `ckt` prefix = testnet; client and address must match | -| Signer methods fail silently | Wallet not connected | Check `await signer.isConnected()`; some signers need explicit `await signer.connect()` | -| `useSigner()` returns `undefined` | Called outside `` | Ensure `ccc.Provider` wraps the component tree | -| UDT tokens lost after transfer | `udt.completeBy()` not called | Always call `udt.completeBy(tx, signer)` before `completeInputsByCapacity`; it adds the token change output | -| `createSpore` throws on `clusterId` | `clusterMode` not specified | Specify `clusterMode: "lockProxy" \| "clusterCell" \| "skip"` when `clusterId` is set | -| `udt.name()` / `symbol()` returns `undefined` | Token doesn't implement SSRI | Only xUDT with SSRI support returns metadata; always guard with `?? "unknown"` | -| `addCellDepsOfKnownScripts is not a function` | Wrong import or outdated version | Use `@ckb-ccc/shell` not `@ckb-ccc/core` for backend; update CCC to latest | - ---- - -## Hallucination Guard: What NOT to Generate - -These patterns look plausible but are wrong for CKB/CCC: - -- ❌ `signer.getBalance()` returns a `number` — it returns `bigint` (Shannon) -- ❌ `new ccc.Transaction()` — always use `ccc.Transaction.from({ outputs: [...] })` -- ❌ Manually computing fees — always use `completeFeeBy` -- ❌ Manually selecting input cells — always use `completeInputsByCapacity` -- ❌ Calling `completeFeeBy` before `completeInputsByCapacity` — order is mandatory -- ❌ `tx.addCellDepsOfKnownScripts` without `await` — it's async -- ❌ `useSigner()` outside `` — will return undefined -- ❌ Using EVM address format (`0x...` 20-byte) as CKB address — CKB uses bech32m (`ckb1...`) -- ❌ `ccc.fixedPointFrom(100.5)` with float arithmetic on Shannon — use string `"100.5"` -- ❌ Hardcoding private keys in source — always use environment variables - ---- - -## Pre-submission Checklist - -Before finalizing any CCC code, verify: - -- [ ] **Package** — Correct package for the environment (`connector-react` / `shell` / `ccc`) -- [ ] **TypeScript** — `tsconfig.json` has `moduleResolution: "bundler"` (or `node16`/`nodenext`) -- [ ] **React setup** — `"use client"` on files using `ccc.Provider` or hooks (Next.js App Router) -- [ ] **Network match** — Client network (testnet/mainnet) matches address prefix (`ckt`/`ckb`) -- [ ] **Transaction order** — `outputs declared → completeInputsByCapacity → completeFeeBy → sendTransaction` -- [ ] **Signer guard** — `if (!signer) return` before any transaction operation -- [ ] **Capacity** — Output cells have ≥ 61 CKB (CCC helpers enforce this; manual calc may not) -- [ ] **UDT change** — `udt.completeBy()` called before `completeInputsByCapacity` for token transfers -- [ ] **Spore cluster** — `clusterMode` specified when `clusterId` is set in `createSpore()` -- [ ] **Fee rate** — `completeFeeBy` called without explicit rate (automatic) unless custom rate needed -- [ ] **Amount conversion** — User-facing amounts go through `fixedPointFrom()` / `fixedPointToString()` -- [ ] **No hardcoded secrets** — Private keys loaded from environment variables -- [ ] **Error handling** — `try/catch` around `sendTransaction`, `connect`, `getBalance` - ---- - -## How to Use This Documentation - -When you need information beyond what this skill covers, fetch it directly rather -than guessing. The docs site is structured for programmatic access: - -### Step 1 — Start with llms.txt for navigation - -``` -GET https://docs.ckbccc.com/llms.txt -``` - -Returns a structured index of all documentation pages with titles and URLs. -Use this to identify which page covers the topic you need. - -### Step 2 — Fetch the specific page as Markdown - -Append `.md` to any docs URL to get clean Markdown without HTML boilerplate: - -``` -# Example: fetch the Cell model concept page -GET https://docs.ckbccc.com/en/docs/concepts/cell-model.md - -# Pattern for any page: -GET https://docs.ckbccc.com/en/docs/
/.md -``` - -Alternatively, send `Accept: text/markdown` and the server will serve Markdown -automatically via content negotiation. - -### Step 3 — Use llms-full.txt for broad questions - -``` -GET https://docs.ckbccc.com/llms-full.txt -``` - -Contains the full text of all English documentation pages concatenated. -Use when the question spans multiple pages or you need a complete overview. -Prefer per-page fetches when the relevant section is known — narrower context -means less noise. - -### Key page URLs (most commonly needed) - -| Topic | Markdown URL | -|---|---| -| Cell model, Script, Transaction concepts | `https://docs.ckbccc.com/en/docs/concepts/cell-model.md` | -| Signer interface | `https://docs.ckbccc.com/en/docs/concepts/signer.md` | -| Connecting wallets | `https://docs.ckbccc.com/en/docs/guides/connect-wallets.md` | -| Composing transactions | `https://docs.ckbccc.com/en/docs/guides/compose-transactions.md` | -| UDT token guide | `https://docs.ckbccc.com/en/docs/guides/udt-tokens.md` | -| Spore protocol guide | `https://docs.ckbccc.com/en/docs/guides/spore-protocol.md` | -| Node.js backend guide | `https://docs.ckbccc.com/en/docs/guides/node-js-backend.md` | -| Package overview | `https://docs.ckbccc.com/en/docs/packages/core-packages.md` | - -### For API signatures and types — use the API reference - -``` -https://api.ckbccc.com -``` - -TypeDoc-generated reference for all `@ckb-ccc/*` packages. Use when you need -the exact signature of a method, the fields of an interface, or available -enum values. Search by class or method name directly in the URL: - -``` -https://api.ckbccc.com/modules/_ckb-ccc_connector-react -https://api.ckbccc.com/functions/_ckb-ccc_connector-react.index.useccc -https://api.ckbccc.com/classes/_ckb-ccc_core.index.transaction -https://api.ckbccc.com/enums/_ckb-ccc_core.index.knownscript -``` - -### Decision guide: which resource to fetch - -| Situation | Fetch | -|---|---| -| "How do I do X with CCC?" | Per-page `.md` from docs | -| "What does method Y return?" | api.ckbccc.com | -| "Give me a working example" | Fetch https://docs.ckbccc.com/en/docs/code-examples.md, extract source fields from the items, then fetch the raw GitHub URL to get the TypeScript source | -| "Which page covers topic Z?" | llms.txt index first, then the page | -| "Broad overview of CCC" | llms-full.txt | - ---- - -## Reference Links - -- Docs: https://docs.ckbccc.com -- llms.txt: https://docs.ckbccc.com/llms.txt -- llms-full.txt: https://docs.ckbccc.com/llms-full.txt -- Per-page markdown: append `.md` to any docs URL -- API reference: https://api.ckbccc.com -- Playground (live code): https://live.ckbccc.com -- GitHub: https://github.com/ckb-devrel/ccc \ No newline at end of file diff --git a/packages/docs/scripts/verify-llm.mjs b/packages/docs/scripts/verify-llm.mjs index 67ac34d1..c3396792 100644 --- a/packages/docs/scripts/verify-llm.mjs +++ b/packages/docs/scripts/verify-llm.mjs @@ -97,10 +97,11 @@ async function checkLlmsFull() { ); } -async function checkSkill() { +async function checkSkillsIndex() { const { res, body } = await fetchText('/skill.md'); check('/skill.md returns 200', res.status === 200, `status ${res.status}`); - check('/skill.md has skill heading', body.includes('# CKB CCC Development Skill')); + check('/skill.md has skills heading', body.includes('# CCC Agent Skills')); + check('/skill.md lists the hub skill', body.includes('ckb-ccc-fundamentals')); } async function checkHtmlDirective() { @@ -203,7 +204,7 @@ async function checkInternalLinks(llmsBody) { async function run() { const llmsBody = await checkLlmsIndex(); await checkLlmsFull(); - await checkSkill(); + await checkSkillsIndex(); await checkHtmlDirective(); await checkSitemap(); await checkSampleMarkdown(); From 6f409724cd8bc146a96ffe592e91c265cd1aa12b Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 21:54:18 +0800 Subject: [PATCH 03/15] docs: add multi-skill architecture and AI Resources section - Add /skill.md route (app/skill.md/route.ts) with hub/spoke skill table, dependencies, and npx skills CLI installation - Add AI Resources section with 4 pages (EN and ZH): - index: overview and navigation cards - set-up-ai-tools: npx skills add installation, keep-skills-up-to-date section - prompting-best-practices: skill-native vs web chat prompt templates, 7 task categories - verify-and-troubleshoot: canary questions, npx skills check/update troubleshooting - Update docs meta.json/meta.zh.json to include AI Resources section - Update introduction.zh.mdx to reference new skill.md endpoint This completes the multi-skill architecture migration by providing the new modular skill structure and comprehensive AI tooling documentation. --- packages/docs/app/skill.md/route.ts | 105 +++++++++++++ .../docs/content/docs/ai-resources/index.mdx | 37 +++++ .../content/docs/ai-resources/index.zh.mdx | 37 +++++ .../ai-resources/prompting-best-practices.mdx | 143 ++++++++++++++++++ .../prompting-best-practices.zh.mdx | 143 ++++++++++++++++++ .../docs/ai-resources/set-up-ai-tools.mdx | 85 +++++++++++ .../docs/ai-resources/set-up-ai-tools.zh.mdx | 85 +++++++++++ .../ai-resources/verify-and-troubleshoot.mdx | 47 ++++++ .../verify-and-troubleshoot.zh.mdx | 47 ++++++ .../docs/getting-started/introduction.zh.mdx | 5 +- packages/docs/content/docs/meta.json | 5 + packages/docs/content/docs/meta.zh.json | 5 + 12 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 packages/docs/app/skill.md/route.ts create mode 100644 packages/docs/content/docs/ai-resources/index.mdx create mode 100644 packages/docs/content/docs/ai-resources/index.zh.mdx create mode 100644 packages/docs/content/docs/ai-resources/prompting-best-practices.mdx create mode 100644 packages/docs/content/docs/ai-resources/prompting-best-practices.zh.mdx create mode 100644 packages/docs/content/docs/ai-resources/set-up-ai-tools.mdx create mode 100644 packages/docs/content/docs/ai-resources/set-up-ai-tools.zh.mdx create mode 100644 packages/docs/content/docs/ai-resources/verify-and-troubleshoot.mdx create mode 100644 packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx diff --git a/packages/docs/app/skill.md/route.ts b/packages/docs/app/skill.md/route.ts new file mode 100644 index 00000000..6ec3c948 --- /dev/null +++ b/packages/docs/app/skill.md/route.ts @@ -0,0 +1,105 @@ +import { gitConfig, siteUrl } from '@/lib/shared'; + +export const revalidate = false; + +const repoSlug = `${gitConfig.user}/${gitConfig.repo}`; +const rawBase = `https://raw.githubusercontent.com/${repoSlug}/refs/heads/${gitConfig.branch}/skills`; + +interface SkillEntry { + name: string; + role: 'hub' | 'spoke'; + dependsOn: string; + summary: string; +} + +// Kept in sync manually with the `skills/` directory at the repo root — +// update this list whenever a skill is added, renamed, or removed there. +const skills: SkillEntry[] = [ + { + name: 'ckb-ccc-fundamentals', + role: 'hub', + dependsOn: 'none', + summary: + 'Cell model, package selection, address handling, Shannon/CKB amount conversion, the KnownScript enum, and how to look up exact API signatures (DeepWiki/Context7/api.ckbccc.com) or navigate docs.ckbccc.com. Load this first for any CKB/CCC task.', + }, + { + name: 'ckb-ccc-signer-setup', + role: 'spoke', + dependsOn: 'ckb-ccc-fundamentals', + summary: 'Connecting a wallet in a React/Next.js dApp, or creating a private-key Signer in a Node.js backend; the supported wallet matrix; message signing/verification.', + }, + { + name: 'ckb-ccc-transactions', + role: 'spoke', + dependsOn: 'ckb-ccc-fundamentals, ckb-ccc-signer-setup', + summary: 'Composing and sending CKB transactions — outputs, completeInputsByCapacity/completeFeeBy ordering, cell deps, and querying the chain. The base pattern ckb-ccc-udt and ckb-ccc-spore build on.', + }, + { + name: 'ckb-ccc-udt', + role: 'spoke', + dependsOn: 'ckb-ccc-fundamentals, ckb-ccc-transactions', + summary: 'Issuing (Single-Use-Seal), transferring, and reading metadata for UDT/xUDT fungible tokens.', + }, + { + name: 'ckb-ccc-spore', + role: 'spoke', + dependsOn: 'ckb-ccc-fundamentals, ckb-ccc-transactions', + summary: 'Creating, transferring, and melting Spore protocol NFTs/DOBs, including cluster handling and the DOB/0 + DOB/1 content-type conventions.', + }, + { + name: 'ckb-ccc-playground', + role: 'spoke', + dependsOn: 'ckb-ccc-fundamentals, ckb-ccc-transactions', + summary: 'The CCC Playground (live.ckbccc.com) — the @ckb-ccc/playground module, UI controls, and the two ways to share runnable code.', + }, + { + name: 'ckb-ccc-examples-finder', + role: 'spoke', + dependsOn: 'ckb-ccc-fundamentals', + summary: 'Locating existing, ready-made CKB/CCC example code by category instead of writing something from scratch or guessing at patterns.', + }, +]; + +const preamble = `# CCC Agent Skills (CKB / CCC development) + +> Modular, machine-readable operating guidance for AI coding assistants building on CKB with the CCC SDK. Each skill below is a directory with a SKILL.md (YAML frontmatter + Markdown body), following the Agent Skills format (https://agentskills.io/specification). + +## Install + +If your tool/agent can run a shell command, install with the [\`skills\` CLI](https://github.com/vercel-labs/skills) (supports Cursor, Claude Code, GitHub Copilot, Windsurf, Codex, and 60+ other agents — auto-discovers the \`skills/\` directory and writes each skill to the right path for your tool): + +\`\`\`bash +npx skills add ${repoSlug} +\`\`\` + +If your tool/agent can only fetch URLs (no shell access — browser-based agents, plain chat models, etc.), fetch the raw \`SKILL.md\` files directly from the table below. Always fetch \`ckb-ccc-fundamentals\` first, then the spoke skill matching the task. + +## Notes for AI agents + +- Always load \`ckb-ccc-fundamentals\` first — it's the hub skill; every spoke skill assumes it's already loaded. +- Then load the spoke skill(s) matching the task at hand — see the table below, or each skill's own \`description\` frontmatter field for exactly when to use it. +- Tools that natively support multiple skill files/directories (e.g. Claude Code) should fetch the whole \`skills/\` directory at once. +- Tools that only support a single rule/context file should load \`ckb-ccc-fundamentals\` as the default and fetch additional skill URLs on demand, per the task. +- Raw file URLs below always reflect the latest version on the \`${gitConfig.branch}\` branch. +- Full docs: ${siteUrl} — docs index: ${siteUrl}/llms.txt — API reference: https://api.ckbccc.com + +## Skills + +| Skill | Role | Depends on | SKILL.md | +| --- | --- | --- | --- | +${skills + .map((s) => `| \`${s.name}\` | ${s.role} | ${s.dependsOn} | ${rawBase}/${s.name}/SKILL.md |`) + .join('\n')} + +## Skill summaries + +${skills.map((s) => `### ${s.name}\n\n${s.summary}\n`).join('\n')}`; + +export function GET() { + return new Response(preamble, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Cache-Control': 'public, max-age=3600, must-revalidate', + }, + }); +} diff --git a/packages/docs/content/docs/ai-resources/index.mdx b/packages/docs/content/docs/ai-resources/index.mdx new file mode 100644 index 00000000..fcd3b61a --- /dev/null +++ b/packages/docs/content/docs/ai-resources/index.mdx @@ -0,0 +1,37 @@ +--- +title: AI Resources +description: Make Cursor, Claude Code, Copilot, and other AI assistants write correct CKB/CCC code — set them up, prompt them well, and verify the result. +icon: Bot +--- + +import { Wrench, MessageSquare, ShieldCheck } from 'lucide-react'; + +This section is for developers who use an AI coding assistant (Cursor, Claude Code, GitHub Copilot, Windsurf, ChatGPT, …) to build on CKB with CCC. Its only goal: make your assistant write **correct** CCC code instead of confidently wrong code. + +Why the extra effort is needed: CKB uses the **Cell model** (a generalized UTXO model), which barely appears in the Ethereum-heavy data most LLMs were trained on. Left unguided, an assistant will produce CCC code that looks right but isn't — wrong transaction ordering, invented methods, `number` where CKB needs `bigint`, EVM-style assumptions. CCC ships a set of machine-readable [Agent Skills](/skill.md) — one hub skill (`ckb-ccc-fundamentals`) plus a spoke skill per task area (signer setup, transactions, UDT, Spore, playground, examples) — that encode the CCC-specific rules; these pages show you how to feed them to your tool and get reliable output. + +## Do this once, then it just works + +1. **[Set up your tool](./ai-resources/set-up-ai-tools)** — one-time config so your assistant always loads CCC's rules (under 2 minutes). +2. **[Verify it worked](./ai-resources/verify-and-troubleshoot)** — ask one canary question to confirm the rules actually loaded. Don't skip this: a silently-failed setup looks configured but isn't. +3. **[Prompt it well](./ai-resources/prompting-best-practices)** — per request, name the exact package and point it at the relevant guide. + + + New here? Start with **[Set Up Your AI Tools](./ai-resources/set-up-ai-tools)** and do the 2-minute setup for the tool you use — everything else builds on it. + + +## Pages in this section + + + } href="./ai-resources/set-up-ai-tools"> + Copy-paste config for Cursor, Claude Code, GitHub Copilot, Windsurf, and ChatGPT — plus which CCC links to point your assistant at. + + + } href="./ai-resources/prompting-best-practices"> + A before/after example, prompt templates by task, and the specific CKB mistakes AI makes — with the one-line correction for each. + + + } href="./ai-resources/verify-and-troubleshoot"> + Canary questions to confirm your setup actually works, and a step-by-step decision tree for when the assistant still gets CKB wrong. + + diff --git a/packages/docs/content/docs/ai-resources/index.zh.mdx b/packages/docs/content/docs/ai-resources/index.zh.mdx new file mode 100644 index 00000000..447811ea --- /dev/null +++ b/packages/docs/content/docs/ai-resources/index.zh.mdx @@ -0,0 +1,37 @@ +--- +title: AI 资源 +description: 让 Cursor、Claude Code、Copilot 及其他 AI 助手写出正确的 CKB/CCC 代码——配置工具、写好提示词、验证结果。 +icon: Bot +--- + +import { Wrench, MessageSquare, ShieldCheck } from 'lucide-react'; + +本部分面向使用 AI 编程助手(Cursor、Claude Code、GitHub Copilot、Windsurf、ChatGPT……)在 CKB 上基于 CCC 开发的开发者。其唯一目标是:让你的助手写出**正确**的 CCC 代码,而不是自信满满地写出错误代码。 + +为什么需要额外花这些功夫:CKB 采用 **Cell 模型**(泛化的 UTXO 模型),这在 LLM 训练数据中占绝大比重的以太坊相关内容里几乎不存在。如果不加引导,助手生成的 CCC 代码看起来像那么回事,但实际是错的——交易顺序不对、方法凭空捏造、该用 `bigint` 的地方用了 `number`、沿袭 EVM 风格的前提假设。CCC 提供了一套机器可读的 [Agent Skills](/skill.md)——包含一个 hub skill(`ckb-ccc-fundamentals`)和每个任务领域对应的 spoke skill(signer 配置、交易、UDT、Spore、playground、示例)——其中编码了 CCC 特有的规则;本部分页面会告诉你如何将这些 skill 喂给工具,并稳定获得正确输出。 + +## 一次配置,之后无需操心 + +1. **[配置你的工具](./ai-resources/set-up-ai-tools)**——一次性配置,让你的助手始终加载 CCC 的规则(不到 2 分钟)。 +2. **[验证配置是否生效](./ai-resources/verify-and-troubleshoot)**——用一个金丝雀问题确认规则确实被加载了。不要跳过这一步:静默加载失败的配置看起来配好了,但实际上没有。 +3. **[写好提示词](./ai-resources/prompting-best-practices)**——每次请求时,明确指出要用的包名,并将其引导至相关的指南页面。 + + + 新手?从 **[配置你的工具](./ai-resources/set-up-ai-tools)** 开始,花 2 分钟为你使用的工具完成配置——其余内容都建立在这个基础上。 + + +## 本部分页面 + + + } href="./ai-resources/set-up-ai-tools"> + Cursor、Claude Code、GitHub Copilot、Windsurf、ChatGPT 的配置粘贴方案——以及应该将哪些 CCC 链接指向你的助手。 + + + } href="./ai-resources/prompting-best-practices"> + 改造前后的示例、按任务分类的提示词模板,以及 AI 常犯的 CKB 特定错误——并附上每种错误的单行修正方法。 + + + } href="./ai-resources/verify-and-troubleshoot"> + 用于确认配置是否真正生效的金丝雀问题,以及当助手仍然答错 CKB 相关问题时的排查步骤。 + + \ No newline at end of file diff --git a/packages/docs/content/docs/ai-resources/prompting-best-practices.mdx b/packages/docs/content/docs/ai-resources/prompting-best-practices.mdx new file mode 100644 index 00000000..a2404f74 --- /dev/null +++ b/packages/docs/content/docs/ai-resources/prompting-best-practices.mdx @@ -0,0 +1,143 @@ +--- +title: Prompting Best Practices +description: Prompt templates and self-check habits that measurably reduce incorrect CKB/CCC code from AI assistants. +icon: MessageSquare +--- + +Even with CCC's [Agent Skills](/skill.md) loaded, *how* you ask still matters. This page collects prompt patterns that work well for CCC specifically, and the failure modes they prevent. + +## Match your prompt to your environment + +Every template on this page comes in two forms, because "reference the skill" only means something if the skill is actually sitting in the assistant's context: + +- **Skill-native tools** — Cursor, Claude Code, and anything else set up via [Set Up Your AI Tools](./set-up-ai-tools). The skill files are already loaded, so naming a skill (`` `ckb-ccc-fundamentals` ``) is enough — the assistant resolves it locally, no fetch needed. +- **Web chat tools** — ChatGPT, DeepSeek, or any browser-based assistant without the `npx skills` setup. Naming a skill does nothing here; there's no file for it to resolve. Every prompt needs the actual URL, and should explicitly ask the assistant to fetch it before answering — most web chat tools can browse a URL if told to, but won't do it unprompted. + +If you're not sure which situation you're in: did you run the install command from [Set Up Your AI Tools](./set-up-ai-tools) in this project? If not, use the web chat version. + +## The core pattern: ground, then act + +The single most effective habit is asking the assistant to fetch specifics before generating code, instead of relying on what it already "remembers": + +```text +Using CCC (@ckb-ccc/connector-react), add a button that connects a wallet and sends 100 CKB to a hardcoded address. +Before writing code, check https://docs.ckbccc.com/en/docs/guides/connect-wallets.md and +https://docs.ckbccc.com/en/docs/guides/compose-transactions.md for the current API, +then follow the pre-submission checklists in the ckb-ccc-fundamentals and ckb-ccc-transactions skills (https://docs.ckbccc.com/skill.md). +``` + +This works because it does three things a vague prompt doesn't: +- Names the **exact package** (`@ckb-ccc/connector-react`), preventing the assistant from mixing APIs from `@ckb-ccc/core` or an unrelated package. +- Points at **specific pages**, not "the docs" — narrower context, less noise, less chance of pulling in an unrelated example. +- Invokes the **pre-submission checklist**, which forces a self-review pass against known gotchas (transaction ordering, `"use client"`, capacity minimums) before the code reaches you. + +The example above is written in the **web chat form** (full URLs) so it works everywhere. In a skill-native tool, you can shorten it: *"Using CCC (`@ckb-ccc/connector-react`), add a button that connects a wallet and sends 100 CKB to a hardcoded address. Follow the pre-submission checklists in `ckb-ccc-fundamentals` and `ckb-ccc-transactions`."* — no URLs needed, the assistant already has the skills loaded. + +## Before / after: what grounding actually changes + +Same task, two prompts, illustrating why "ground, then act" isn't just a nicety: + +**Vague prompt:** *"Write a function that sends 100 CKB from the connected wallet to an address using CCC."* + +A model relying on memorized/generic patterns typically produces something shaped like this — plausible-looking, and wrong in three CCC-specific ways: + +```ts +// Illustrative of a common wrong answer — do not copy +async function send(signer, toAddress) { + const balance = await signer.getBalance(); // ❌ treated as `number` below + const tx = ccc.Transaction.from({ + outputs: [{ lock: (await ccc.Address.fromString(toAddress, signer.client)).script, capacity: 100 * 1e8 }], + }); + await tx.completeFeeBy(signer); // ❌ fee completed before inputs exist + await tx.completeInputsByCapacity(signer); + return signer.sendTransaction(tx); +} +``` + +**Grounded prompt:** *"Using CCC, write a function that sends 100 CKB from the connected wallet to an address. Follow the transaction pipeline order in the `ckb-ccc-transactions` skill and the Amount Conversion rules in the `ckb-ccc-fundamentals` skill (`https://docs.ckbccc.com/skill.md`) before writing code."* + +```ts +async function send(signer: ccc.Signer, toAddress: string) { + const { script: toLock } = await ccc.Address.fromString(toAddress, signer.client); + const tx = ccc.Transaction.from({ + outputs: [{ lock: toLock, capacity: ccc.fixedPointFrom(100) }], // ✅ bigint Shannon, not a float + }); + await tx.completeInputsByCapacity(signer); // ✅ inputs before fee + await tx.completeFeeBy(signer); + return signer.sendTransaction(tx); +} +``` + +The fix isn't "the model got smarter" — it's that the second prompt gave it somewhere authoritative to check `completeInputsByCapacity`-before-`completeFeeBy` ordering and `bigint` Shannon amounts, instead of falling back on generic/EVM-shaped training data. This is the entire value proposition of grounding: it converts "probably right" into "checked against a source." + +## Prompt templates by task + +Each task below has a **skill-native** version (Cursor, Claude Code — skill already loaded, reference by name) and a **web chat** version (ChatGPT, DeepSeek — no skill files, always include the URL and ask it to fetch first). Swap in your own package/error/method. + +**New feature, unsure which package** +- *Skill-native:* "I'm building ``. Which `@ckb-ccc/*` package should I use, per the Package Selection table in `ckb-ccc-fundamentals`?" +- *Web chat:* "I'm building `` on CKB with CCC. Fetch `https://docs.ckbccc.com/skill.md`, find the `ckb-ccc-fundamentals` skill's Package Selection table, and tell me which `@ckb-ccc/*` package to use." + +**Implementing a known guide** +- *Skill-native:* "Follow the `` guide to implement ``." +- *Web chat:* "Fetch `https://docs.ckbccc.com/en/docs/guides/.md` and follow it to implement ``. If you're not sure of the exact slug, fetch `https://docs.ckbccc.com/llms.txt` first to find the right page." + +**Debugging an error** +- *Skill-native:* "I got this error: ``. Check the Common Gotchas table in `ckb-ccc-fundamentals` (or the matching spoke skill) — does it match a known cause?" +- *Web chat:* "I got this error: ``. Fetch `https://docs.ckbccc.com/skill.md`, find the relevant skill (hub or spoke), and check its Common Gotchas table for a matching cause before guessing." + +**Reviewing AI-generated code** +- *Skill-native:* "Review this code against the Pre-submission Checklist and Hallucination Guard in `ckb-ccc-fundamentals` and `ckb-ccc-transactions`. Go item by item and mark each PASS or FAIL — don't just summarize." +- *Web chat:* "Fetch `https://docs.ckbccc.com/skill.md`, open the `ckb-ccc-fundamentals` and `ckb-ccc-transactions` skills, and review this code against their Pre-submission Checklists and Hallucination Guards. Go item by item and mark each PASS or FAIL — don't just summarize." + +**Exact method signature** +- *Skill-native:* "What are the parameter types for `` on ``? Check DeepWiki/Context7 or `https://api.ckbccc.com` rather than assuming (see `ckb-ccc-fundamentals` Step 0)." +- *Web chat:* "What are the parameter types for `` on `` in `@ckb-ccc/*`? Check `https://api.ckbccc.com` for the exact signature rather than assuming — don't answer from general TypeScript SDK conventions." + +**Looking for existing example code before writing from scratch** +- *Skill-native:* "Is there already a working example for `` in CCC's example gallery or a demo repo, per `ckb-ccc-examples-finder`? Use that as the starting point instead of writing from scratch." +- *Web chat:* "Fetch `https://docs.ckbccc.com/en/docs/code-examples.md` and check if there's an existing example for ``. If not, fetch `https://docs.ckbccc.com/skill.md`, open `ckb-ccc-examples-finder`, and check the external repos it lists before writing new code." + +**Verifying a snippet actually runs before it ships** +- *Skill-native:* "Format this as a runnable script for the CCC Playground (per `ckb-ccc-playground`) so I can paste it into `live.ckbccc.com` and confirm it works on testnet before I put it in the real project." +- *Web chat:* "Fetch `https://docs.ckbccc.com/skill.md`, open `ckb-ccc-playground`, and format this as a runnable script using `render()`/`signer` from `@ckb-ccc/playground` so I can paste it into `live.ckbccc.com` and confirm it works on testnet." + +## Habits that catch mistakes before they ship + +- **Ask for citations.** "Which doc page or skill section is this pattern from?" A confident answer without a citation is a signal to verify manually. +- **Ask it to self-review with the checklist.** The pre-submission checklists in `ckb-ccc-fundamentals` and the relevant spoke skill are written to be run by an AI against its own output — explicitly ask for that pass as a separate step, not folded into the first generation. +- **Testnet first, always.** Never accept "trust me" for anything that sends a mainnet transaction. Ask the assistant to target `ClientPublicTestnet` first and confirm behavior before switching networks — this is also called out in the relevant skill's checklist. +- **Re-ground on version-sensitive questions.** Package versions, download counts, and newly added `KnownScript` entries change over time; re-fetch the relevant page rather than trusting a cached answer from earlier in a long conversation. +- **Don't trust one example for protocol-level behavior.** A single file in a cookbook/demo repo can have a typo (a wrong `contentType`, a copy-pasted value) that looks internally consistent but is still wrong. For anything beyond "how do I call this method" — i.e. how a protocol like DOB/Spore is actually supposed to behave — ask it to cross-check the official protocol docs, not just the one example it found first: *"Before finalizing this, confirm the `contentType`/`decimals`/`` value against the official protocol docs, not just the example you copied it from."* + +## The five mistakes AI makes on CKB — and how to correct them + +These are the CCC-specific slips even a well-configured assistant makes, because they contradict the EVM-shaped patterns in its training data. When you spot one in generated code, don't fix it by hand — hand the correction back as a prompt, so the assistant re-generates with the right rule and keeps it for the rest of the session. Each fix below is written to paste as-is. + +**1. Amounts come back as a `number`, or with decimals.** The model fell back on EVM-style float math. CKB amounts are always `bigint` in Shannon. + +> In CCC all amounts are `bigint` in Shannon, never floating point. Use `ccc.fixedPointFrom("100")` to build them and `ccc.fixedPointToString()` only at the UI boundary. Fix the code accordingly. + +**2. The fee is completed before the inputs.** The model didn't know CCC's pipeline is order-dependent — you can't compute a fee before inputs exist. + +> The transaction order is mandatory: declare outputs → `completeInputsByCapacity(signer)` → `completeFeeBy(signer)` → `sendTransaction(tx)`. Reorder the calls to match. + +**3. A Next.js component using `ccc.Provider` or a CCC hook throws at render.** The file is a Server Component; CCC's React pieces need the client runtime. + +> This file uses `ccc.Provider` / a CCC hook, so it must be a Client Component — add `"use client"` as the first line. + +**4. A UDT transfer silently loses token change.** The model skipped the UDT-specific input step, so surplus tokens aren't returned to the sender. + +> For UDT transfers, call `udt.completeBy(tx, signer)` (adds UDT inputs + change) **before** `tx.completeInputsByCapacity(signer)` (adds CKB capacity). Insert the missing step in that order. + +**5. A Node.js script imports `@ckb-ccc/core`.** The model reached for the low-level package unprompted; backends should use the aggregated one. + +> Backend/Node.js scripts should import from `@ckb-ccc/shell`, which already re-exports `@ckb-ccc/core`. Switch the import. + + + Each of these maps to an entry in the Common Gotchas and Hallucination Guard sections of `ckb-ccc-fundamentals` or the matching spoke skill — see the [skills index](/skill.md), the same one your tool loads during [setup](./set-up-ai-tools). If your assistant gets **all** of them wrong, that's not a prompting problem: the rules probably didn't load. Run the checks on [Verify & Troubleshoot](./verify-and-troubleshoot). + + +## Closing the loop + +Prompting well is a habit applied per request, not a one-time step. Pair it with a correct [tool setup](./set-up-ai-tools) (so the rules are always loaded) and the [verification check](./verify-and-troubleshoot) (so you know they are), and AI-assisted CCC development gets much closer to pairing with a developer who has actually read the docs. \ No newline at end of file diff --git a/packages/docs/content/docs/ai-resources/prompting-best-practices.zh.mdx b/packages/docs/content/docs/ai-resources/prompting-best-practices.zh.mdx new file mode 100644 index 00000000..419142db --- /dev/null +++ b/packages/docs/content/docs/ai-resources/prompting-best-practices.zh.mdx @@ -0,0 +1,143 @@ +--- +title: 提示词最佳实践 +description: 可有效减少 AI 助手生成错误 CKB/CCC 代码的提示词模板与自查习惯。 +icon: MessageSquare +--- + +即使加载了 CCC 的 [Agent Skills](/skill.md),**如何**提问仍然很重要。本页整理了特别适用于 CCC 的提示词模式,以及它们所预防的错误类型。 + +## 根据运行环境选择提示词版本 + +本页每个模板都有两种形式,因为"引用 skill"只有在 skill 确实存在于助手上下文中的情况下才有意义: + +- **Skill 原生工具** —— Cursor、Claude Code,以及通过[配置 AI 工具](./set-up-ai-tools)设置的其他工具。skill 文件已加载,因此只需提及 skill 名称(`` `ckb-ccc-fundamentals` ``)即可——助手会在本地解析,无需额外获取。 +- **Web 聊天工具** —— ChatGPT、DeepSeek,或任何未经 `npx skills` 配置的浏览器版助手。在此类环境中提及 skill 名称无效,因为无可解析的文件。每个提示词都需要提供实际 URL,并明确要求助手在回答前先获取该 URL——大多数 Web 聊天工具在被要求时可以浏览 URL,但不会主动这样做。 + +如果不确定自己属于哪种情况:你是否在本项目中执行过[配置 AI 工具](./set-up-ai-tools)中的安装命令?如果没有,请使用 Web 聊天版本。 + +## 核心模式:先查证,后编写 + +最有效的一个习惯是:要求助手在生成代码前先获取具体信息,而不是依赖它已经"记住"的内容: + +```text +使用 CCC(@ckb-ccc/connector-react),添加一个连接钱包并向硬编码地址发送 100 CKB 的按钮。在写代码之前,先查看 +https://docs.ckbccc.com/en/docs/guides/connect-wallets.md 和 +https://docs.ckbccc.com/en/docs/guides/compose-transactions.md 了解当前 API, +然后遵循 ckb-ccc-fundamentals 和 ckb-ccc-transactions 技能中的提交前检查清单(https://docs.ckbccc.com/skill.md)。 +``` + +这个写法的有效性在于它做了模糊提示词做不到的三件事: +- 指明了**确切的包名**(`@ckb-ccc/connector-react`),防止助手混淆 `@ckb-ccc/core` 或其他无关包的 API。 +- 指向**具体页面**,而非"文档"——范围更窄,干扰更少,减少引入无关示例的可能。 +- 调用了**提交前检查清单**,强制在代码交到你手上之前,对照已知陷阱(交易排序、`"use client"`、容量最小值)进行自查。 + +上述示例使用的是 **Web 聊天形式**(完整 URL),因此适用于所有环境。在 skill 原生工具中,可以缩短为:*"使用 CCC(`@ckb-ccc/connector-react`),添加一个连接钱包并向硬编码地址发送 100 CKB 的按钮。遵循 `ckb-ccc-fundamentals` 和 `ckb-ccc-transactions` 中的提交前检查清单。"*——无需 URL,助手已加载了 skill。 + +## 对比:查证与否的实际差异 + +同一任务,两种提示词,说明"先查证,后编写"并非可有可无: + +**模糊提示词:** *"使用 CCC 编写一个函数,从已连接钱包向一个地址发送 100 CKB。"* + +依赖记忆/通用模式训练的模型通常会生成类似这样的代码——看起来合理,但在 CCC 特有的三个方面是错误的: + +```ts +// 展示常见错误答案——请勿复制 +async function send(signer, toAddress) { + const balance = await signer.getBalance(); // ❌ 下面将其当作 number 处理 + const tx = ccc.Transaction.from({ + outputs: [{ lock: (await ccc.Address.fromString(toAddress, signer.client)).script, capacity: 100 * 1e8 }], + }); + await tx.completeFeeBy(signer); // ❌ 在 inputs 填充之前完成费用计算 + await tx.completeInputsByCapacity(signer); + return signer.sendTransaction(tx); +} +``` + +**有查证的提示词:** *"使用 CCC 编写一个函数,从已连接钱包向一个地址发送 100 CKB。在写代码之前,遵循 `ckb-ccc-transactions` 技能中的交易流水线顺序和 `ckb-ccc-fundamentals` 技能中的数额转换规则(`https://docs.ckbccc.com/skill.md`)。"* + +```ts +async function send(signer: ccc.Signer, toAddress: string) { + const { script: toLock } = await ccc.Address.fromString(toAddress, signer.client); + const tx = ccc.Transaction.from({ + outputs: [{ lock: toLock, capacity: ccc.fixedPointFrom(100) }], // ✅ bigint Shannon,非浮点数 + }); + await tx.completeInputsByCapacity(signer); // ✅ inputs 在费用之前 + await tx.completeFeeBy(signer); + return signer.sendTransaction(tx); +} +``` + +这里的改进并非"模型变聪明了"——而是第二个提示词给了它权威来源来核对 `completeInputsByCapacity` 必须在 `completeFeeBy` 之前调用、以及数额是 `bigint` Shannon 而非浮点数,从而避免退回到通用/EVM 训练数据的模式。这正是"查证"的全部价值所在:将"可能正确"转化为"有来源校验的正确"。 + +## 按任务分类的提示词模板 + +每个任务下方提供 **skill 原生**版本(Cursor、Claude Code——skill 已加载,按名称引用)和 **Web 聊天**版本(ChatGPT、DeepSeek——无 skill 文件,务必包含 URL 并要求先获取)。替换其中的包名/错误信息/方法名即可。 + +**新功能,不确定用哪个包** +- *Skill 原生:* "我正在构建 ``。根据 `ckb-ccc-fundamentals` 中的包选择表格,应该用哪个 `@ckb-ccc/*` 包?" +- *Web 聊天:* "我正在 CKB 上用 CCC 构建 ``。获取 `https://docs.ckbccc.com/skill.md`,找到 `ckb-ccc-fundamentals` 技能的包选择表格,告诉我该用哪个 `@ckb-ccc/*` 包。" + +**实现一个已知指南** +- *Skill 原生:* "按照 `<构造交易 / 连接钱包 / UDT 代币>` 指南实现 `<功能>`。" +- *Web 聊天:* "获取 `https://docs.ckbccc.com/en/docs/guides/.md` 并按指引实现 `<功能>`。如果不确定确切的 slug,先获取 `https://docs.ckbccc.com/llms.txt` 找到正确的页面。" + +**调试错误** +- *Skill 原生:* "我遇到了这个错误:`<错误信息>`。对照 `ckb-ccc-fundamentals`(或对应的 spoke skill)中的常见陷阱表格——是否匹配某个已知原因?" +- *Web 聊天:* "我遇到了这个错误:`<错误信息>`。获取 `https://docs.ckbccc.com/skill.md`,找到相关 skill(hub 或 spoke),在生成猜测前先对照其中的常见陷阱表格。" + +**审查 AI 生成的代码** +- *Skill 原生:* "对照 `ckb-ccc-fundamentals` 和 `ckb-ccc-transactions` 中的提交前检查清单和幻觉防护来审查这段代码。逐项标注 PASS 或 FAIL——不要只给总结。" +- *Web 聊天:* "获取 `https://docs.ckbccc.com/skill.md`,打开 `ckb-ccc-fundamentals` 和 `ckb-ccc-transactions` 技能,对照其中的提交前检查清单和幻觉防护来审查这段代码。逐项标注 PASS 或 FAIL——不要只给总结。" + +**精确的方法签名** +- *Skill 原生:* "`<类>` 上的 `<方法>` 的参数类型是什么?查阅 DeepWiki/Context7 或 `https://api.ckbccc.com`,不要凭猜测(参见 `ckb-ccc-fundamentals` 第 0 步)。" +- *Web 聊天:* "`@ckb-ccc/*` 中 `<类>` 上的 `<方法>` 的参数类型是什么?查阅 `https://api.ckbccc.com` 获取精确签名,不要凭猜测——不要按通用 TypeScript SDK 惯例回答。" + +**寻找已有示例代码,避免从零编写** +- *Skill 原生:* "根据 `ckb-ccc-examples-finder`,CCC 的示例库或演示仓库中是否已有 `<功能>` 的工作示例?以此为基础,不要从零编写。" +- *Web 聊天:* "获取 `https://docs.ckbccc.com/en/docs/code-examples.md`,检查是否有 `<功能>` 的现有示例。如果没有,获取 `https://docs.ckbccc.com/skill.md`,打开 `ckb-ccc-examples-finder`,在编写新代码前检查其列出的外部仓库。" + +**验证代码片段在实际运行前可用** +- *Skill 原生:* "将此格式化为 CCC Playground 的可运行脚本(按 `ckb-ccc-playground`),以便我粘贴到 `live.ckbccc.com` 上在测试网验证后再放入正式项目。" +- *Web 聊天:* "获取 `https://docs.ckbccc.com/skill.md`,打开 `ckb-ccc-playground`,使用 `@ckb-ccc/playground` 中的 `render()`/`signer` 将其格式化为可运行脚本,以便我粘贴到 `live.ckbccc.com` 上在测试网验证后再放入正式项目。" + +## 在交付前发现错误的习惯 + +- **要求引用来源。** "这个模式出自哪份文档页面或 skill 章节?" 没有引用来源的自信回答说明需要手动核实。 +- **要求用检查清单进行自查。** `ckb-ccc-fundamentals` 和相关 spoke skill 中的提交前检查清单本就是为 AI 对照自己的输出而设计的——明确要求将此自查作为独立步骤,而非合并到首次生成中。 +- **始终先在测试网运行。** 对于任何发送主网交易的内容,不要接受"相信我"。要求助手先以 `ClientPublicTestnet` 为目标,确认行为后再切换网络——相关 skill 的检查清单中也专门提到了这一点。 +- **在版本敏感问题上重新查证。** 包版本、下载量和新增加的 `KnownScript` 条目会随时间变化;重新获取相关页面,不要依赖长对话早期缓存的答案。 +- **不要仅凭一个示例来推断协议级行为。** 菜谱/演示仓库中的单个文件可能有笔误(错误的 `contentType`、复制粘贴的值),看起来内部一致但仍然是错的。对于超出"如何调用这个方法"范围的问题——即像 DOB/Spore 这样的协议实际应如何运作——要求它与官方协议文档交叉核对,而非仅凭它找到的第一个示例:*"在最终确定之前,对照官方协议文档确认 `contentType`/`decimals`/`<字段>` 的值,不要仅凭你复制的那一个示例。"* + +## AI 在 CKB 上常犯的五类错误及其纠正方法 + +这些是即使配置良好的助手也容易犯的 CCC 特有错误,因为它们与其训练数据中的 EVM 模式相冲突。当在生成代码中发现这些问题时,不要手动修复——将纠正要求作为提示词回传,让助手按正确规则重新生成并在后续对话中保持。每个纠正内容可直接粘贴使用。 + +**1. 数额返回为 `number`,或带有小数。** 模型退回到了 EVM 风格的浮点数运算。CKB 数额始终以 Shannon 为单位的 `bigint`。 + +> 在 CCC 中所有数额均为 Shannon 单位的 `bigint`,绝非浮点数。使用 `ccc.fixedPointFrom("100")` 构造数额,仅在 UI 边界处使用 `ccc.fixedPointToString()`。请相应修正代码。 + +**2. 费用在 inputs 之前完成计算。** 模型不知道 CCC 的流水线是有顺序依赖的——在 inputs 存在之前无法计算费用。 + +> 交易顺序是强制的:声明 outputs → `completeInputsByCapacity(signer)` → `completeFeeBy(signer)` → `sendTransaction(tx)`。请按此顺序重排调用。 + +**3. 使用 `ccc.Provider` 或 CCC hook 的 Next.js 组件在渲染时报错。** 该文件是服务端组件;CCC 的 React 部分需要客户端运行时环境。 + +> 此文件使用了 `ccc.Provider` 或 CCC hook,因此必须是客户端组件——在第一行添加 `"use client"`。 + +**4. UDT 转账静默丢失找零。** 模型跳过了 UDT 特有的 input 步骤,导致 surplus 代币未退回给发送方。 + +> UDT 转账时,先调用 `udt.completeBy(tx, signer)`(添加 UDT inputs + 找零),**再**调用 `tx.completeInputsByCapacity(signer)`(添加 CKB 容量)。请按此顺序插入缺失的步骤。 + +**5. Node.js 脚本从 `@ckb-ccc/core` 导入。** 模型未经提示使用了底层包;后端应使用聚合包。 + +> 后端/Node.js 脚本应从 `@ckb-ccc/shell` 导入,该包已重新导出 `@ckb-ccc/core`。请切换导入来源。 + + + 上述每一条都对应 `ckb-ccc-fundamentals` 或对应 spoke skill 中"常见陷阱"和"幻觉防护"部分的条目——参见 [skills 索引](/skill.md),即你的工具在[配置](./set-up-ai-tools)期间加载的同一份文件。如果你的助手**全部**答错,那就不只是提示词的问题了:规则可能没有加载。请运行[验证与排查](./verify-and-troubleshoot)中的检查步骤。 + + +## 形成闭环 + +好的提示词习惯是每次请求时应用的习惯,而非一次性步骤。将其与正确的[工具配置](./set-up-ai-tools)(确保规则始终加载)和[验证检查](./verify-and-troubleshoot)(确保你确认它们已加载)配合使用,AI 辅助的 CCC 开发就会更接近与一位真正读过文档的开发者结对编程。 \ No newline at end of file diff --git a/packages/docs/content/docs/ai-resources/set-up-ai-tools.mdx b/packages/docs/content/docs/ai-resources/set-up-ai-tools.mdx new file mode 100644 index 00000000..0afe2341 --- /dev/null +++ b/packages/docs/content/docs/ai-resources/set-up-ai-tools.mdx @@ -0,0 +1,85 @@ +--- +title: Set Up Your AI Tools +description: Install CCC's Agent Skills into Cursor, Claude Code, GitHub Copilot, Windsurf, and 60+ other AI tools with one command. +icon: Wrench +--- + +Your AI assistant only writes correct CCC code once it's actually loading CCC's rules. CCC ships these as a set of [Agent Skills](/skill.md) — one **hub skill**, `ckb-ccc-fundamentals` (package selection, Cell model, address/amount handling, hallucination guard), plus a **spoke skill** per task area (`ckb-ccc-signer-setup`, `ckb-ccc-transactions`, `ckb-ccc-udt`, `ckb-ccc-spore`, `ckb-ccc-playground`, `ckb-ccc-examples-finder`), all committed under `skills/` in the [CCC repo](https://github.com/ckb-devrel/ccc). + +Install them with [`skills`](https://github.com/vercel-labs/skills), the open Agent Skills CLI — it auto-discovers the `skills/` directory and writes each skill to the right path for whichever tool(s) you target: + +```bash +npx skills add ckb-devrel/ccc --all +``` + +`--all` installs every skill non-interactively — the one-line path most projects want. Leave it off and the CLI opens an interactive picker for *which skills* to install (in addition to the tool picker), which is useful if you only want a subset: + +```bash +# Only Cursor and Claude Code, every skill +npx skills add ckb-devrel/ccc --all -a cursor -a claude-code + +# Every supported tool, only the fundamentals + UDT skills +npx skills add ckb-devrel/ccc --skill 'ckb-ccc-fundamentals' --skill 'ckb-ccc-udt' + +# Install globally (all your projects) instead of this project only +npx skills add ckb-devrel/ccc --all -g +``` + +`skills` supports Cursor, Claude Code, GitHub Copilot, Windsurf, Codex, and 60+ other agents — each skill's `description` frontmatter drives when the tool loads it, so no extra rule-file wiring is needed on top of the install. + + + Do this once per project (or once globally with `-g`). It takes about two minutes and is the single highest-leverage thing you can do to improve AI-generated CCC code. After setup, always run the check on [Verify & Troubleshoot](./verify-and-troubleshoot) — a skill that silently failed to load is worse than none. + +For tools without a file-based rules/skills mechanism (e.g. plain ChatGPT), `npx skills add` has nothing to write to. Paste this into the system prompt (or the first message) instead: + +```text +Before writing any CKB/CCC code, fetch https://docs.ckbccc.com/skill.md to see the list of CCC agent skills. +Always fetch and follow the ckb-ccc-fundamentals skill first, +then fetch the spoke skill matching the task (signer setup, transactions, UDT, Spore, +playground, or examples) before generating code. +``` + +## Keep skills up to date + +An installed skill is a point-in-time copy — CCC's skills keep improving, and nothing pushes those improvements to a project that already ran `npx skills add`. `skills` tracks what you installed in a `skills-lock.json`, so you can check and pull updates on demand: + +```bash +# Pull the latest version of every installed skill +npx skills update + +# Update just one +npx skills update ckb-ccc-udt +``` + + + `npx skills update` diffs content hashes, not version numbers, and has a known gap where it can report "up to date" even when the source changed ([vercel-labs/skills#484](https://github.com/vercel-labs/skills/issues/484)). If a canary question on [Verify & Troubleshoot](./verify-and-troubleshoot) starts failing right after a CCC skills update was announced, don't trust `check` alone — remove and re-add the skill (`npx skills remove ckb-ccc-fundamentals && npx skills add ckb-devrel/ccc --skill 'ckb-ccc-fundamentals'`). + + +There's no notification when a skill changes — check periodically (e.g. before starting significant new work) rather than assuming you'll hear about it. + +## Tell your AI where to find answers + +The skills cover the common cases, but for anything beyond them your assistant should fetch from the docs rather than guess. These are the machine-readable entry points to point it at — the same URLs work whether you paste them into a prompt or bake them into a rule: + +| Resource | URL | Use it for | +| --- | --- | --- | +| Skills index | [`/skill.md`](/skill.md) | The default: lists every skill (hub + spoke), what each covers, and its raw `SKILL.md` URL. Load `ckb-ccc-fundamentals` first. | +| Docs index | [`/llms.txt`](/llms.txt) | "Which page covers topic X?" A short, linked index of every docs page — fetch first to navigate. | +| Single page as Markdown | any docs URL `+ .md` | A focused how-to on one topic, e.g. `https://docs.ckbccc.com/en/docs/guides/udt-tokens.md`. Clean Markdown, no page chrome. | +| Full docs dump | [`/llms-full.txt`](/llms-full.txt) | A broad question spanning several topics, when you want everything in one fetch. | +| API reference | [api.ckbccc.com](https://api.ckbccc.com) | Exact method signatures, parameter types, and enum values for any `@ckb-ccc/*` export. | + + + Any docs page has a Markdown twin: append `.md` to its URL (or send an `Accept: text/markdown` header). That's what you want an AI to read — it's the article text without navigation or component markup, so it spends context on content, not layout. + +## Verify it worked + +Ask the assistant a CKB-specific question it would otherwise get wrong: + +> "In CCC, does `signer.getBalance()` return a `number` or a `bigint`?" + +A correctly configured assistant answers `bigint` (in Shannon) — this exact gotcha is called out in the `ckb-ccc-fundamentals` skill's Hallucination Guard. If it answers `number`, the skill/rule isn't loading. See [Verify & Troubleshoot](./verify-and-troubleshoot) for four more of these canary questions and a full decision tree for fixing a broken setup. + +## Next step + +Configuring the tool is necessary but not sufficient — see [Prompting Best Practices](./prompting-best-practices) for how to phrase requests so the assistant actually uses what it now has access to. \ No newline at end of file diff --git a/packages/docs/content/docs/ai-resources/set-up-ai-tools.zh.mdx b/packages/docs/content/docs/ai-resources/set-up-ai-tools.zh.mdx new file mode 100644 index 00000000..82b1eaf1 --- /dev/null +++ b/packages/docs/content/docs/ai-resources/set-up-ai-tools.zh.mdx @@ -0,0 +1,85 @@ +--- +title: 配置你的 AI 工具 +description: 通过一条命令将 CCC 的 Agent Skills 安装到 Cursor、Claude Code、GitHub Copilot、Windsurf 及其他 60 多种 AI 工具中。 +icon: Wrench +--- + +你的 AI 助手只有在真正加载了 CCC 的规则之后,才能写出正确的 CCC 代码。CCC 将这些规则打包为一组 [Agent Skills](/skill.md)——一个 **hub skill** `ckb-ccc-fundamentals`(包含包选择、Cell 模型、地址/数额处理、幻觉防护),外加每个任务领域对应的 **spoke skill**(`ckb-ccc-signer-setup`、`ckb-ccc-transactions`、`ckb-ccc-udt`、`ckb-ccc-spore`、`ckb-ccc-playground`、`ckb-ccc-examples-finder`),全部提交在 [CCC 仓库](https://github.com/ckb-devrel/ccc) 的 `skills/` 目录下。 + +使用开源 Agent Skills CLI —— [`skills`](https://github.com/vercel-labs/skills) 来安装。它会自动发现 `skills/` 目录,并将每个 skill 写入你所指定的工具的对应路径: + +```bash +npx skills add ckb-devrel/ccc --all +``` + +`--all` 会非交互式地安装所有 skill——这是大多数项目所需的单行命令。不加 `--all` 时,CLI 会打开交互式选择器,让你选择*安装哪些 skill*(除了选择工具之外),如果你只需要其中的一部分,这会很有用: + +```bash +# 仅 Cursor 和 Claude Code,安装所有 skill +npx skills add ckb-devrel/ccc --all -a cursor -a claude-code + +# 所有支持的工具,仅安装 fundamentals + UDT 两个 skill +npx skills add ckb-devrel/ccc --skill 'ckb-ccc-fundamentals' --skill 'ckb-ccc-udt' + +# 全局安装(作用于你所有项目),而非仅当前项目 +npx skills add ckb-devrel/ccc --all -g +``` + +`skills` 支持 Cursor、Claude Code、GitHub Copilot、Windsurf、Codex 及其他 60 多种代理——每个 skill 的 `description` 元数据决定了工具何时加载它,因此安装之外无需额外的规则文件配置。 + + + 每个项目执行一次即可(或使用 `-g` 全局执行一次)。大约需要两分钟,这是提高 AI 生成 CCC 代码质量性价比最高的操作。配置完成后,务必运行[验证与排查](./verify-and-troubleshoot)中的检查——一个静默加载失败的 skill 比没有更糟糕。 + + +对于没有基于文件的 rules/skills 机制的工具(例如普通 ChatGPT),`npx skills add` 没有可写入的目标。请将以下内容粘贴到系统提示词(或第一条消息)中: + +```text +在编写任何 CKB/CCC 代码之前,先获取 https://docs.ckbccc.com/skill.md 查看 CCC agent skills 列表。 +始终先获取并遵循 ckb-ccc-fundamentals skill,然后在生成代码之前获取与任务对应的 spoke skill(signer 配置、交易、UDT、Spore、playground 或示例)。 +``` + +## 保持 skills 最新 + +已安装的 skill 是某个时间点的副本——CCC 的 skills 在不断改进,但这些改进不会自动推送到已执行过 `npx skills add` 的项目中。`skills` 会在 `skills-lock.json` 中记录安装内容,因此你可以按需检查并拉取更新: + +```bash +# 拉取所有已安装 skill 的最新版本 +npx skills update + +# 仅更新某一个 +npx skills update ckb-ccc-udt +``` + + + `npx skills update` 比较的是内容哈希而非版本号,存在一个已知缺陷:即使源文件已变更,它仍可能报告"已是最新"([vercel-labs/skills#484](https://github.com/vercel-labs/skills/issues/484))。如果 CCC skills 更新发布后,[验证与排查](./verify-and-troubleshoot)中的检测问题开始失败,不要仅凭 `check` 的结果——请移除并重新添加该 skill(`npx skills remove ckb-ccc-fundamentals && npx skills add ckb-devrel/ccc --skill 'ckb-ccc-fundamentals'`)。 + + +skill 变更时不会有通知——请定期检查(例如在开始重要的新工作之前),不要指望会收到消息。 + +## 告诉你的 AI 去哪里找答案 + +skills 覆盖了常见场景,但超出这些范围的场景,你的助手应该从文档中获取答案而非凭猜测。以下是可供指向的机器可读入口点——无论你是将其粘贴到提示词中还是写入规则,这些 URL 都有效: + +| 资源 | URL | 用途 | +| --- | --- | --- | +| Skills 索引 | [`/skill.md`](/skill.md) | 默认入口:列出所有 skill(hub + spoke)、各自覆盖范围及其原始 `SKILL.md` URL。先加载 `ckb-ccc-fundamentals`。 | +| 文档索引 | [`/llms.txt`](/llms.txt) | "哪个页面覆盖了 X 主题?"——一份简短的、带链接的文档页面索引,先获取以导航。 | +| 单个页面(Markdown 格式) | 任意文档 URL 后加 `.md` | 一个主题的聚焦操作指南,例如 `https://docs.ckbccc.com/en/docs/guides/udt-tokens.md`。干净的 Markdown,无页面框架。 | +| 完整文档导出 | [`/llms-full.txt`](/llms-full.txt) | 跨越多个主题的宽泛问题,当你希望一次获取全部内容时使用。 | +| API 参考 | [api.ckbccc.com](https://api.ckbccc.com) | 任何 `@ckb-ccc/*` 导出项的方法签名、参数类型和枚举值。 | + + + 任意文档页面都有对应的 Markdown 版本:在其 URL 后追加 `.md`(或发送 `Accept: text/markdown` 请求头)。这是你希望 AI 阅读的内容——只有文章正文,没有导航或组件标记,因此上下文消耗在内容上而非布局上。 + + +## 验证是否生效 + +向助手提一个 CKB 特有且它本会答错的问题: + +> "在 CCC 中,`signer.getBalance()` 返回的是 `number` 还是 `bigint`?" + +配置正确的助手会回答 `bigint`(单位为 Shannon)——这个陷阱在 `ckb-ccc-fundamentals` skill 的"幻觉防护"部分有专门说明。如果它回答 `number`,说明 skill/规则未加载。请参阅[验证与排查](./verify-and-troubleshoot),其中包含另外四个这类检测问题以及修复错误配置的完整决策树。 + +## 下一步 + +配置工具是必要的,但还不够——请参阅[提示词最佳实践](./prompting-best-practices),了解如何组织请求,让助手真正使用它现在已能访问的内容。 \ No newline at end of file diff --git a/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.mdx b/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.mdx new file mode 100644 index 00000000..7aed6768 --- /dev/null +++ b/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.mdx @@ -0,0 +1,47 @@ +--- +title: Verify & Troubleshoot +description: Canary questions to confirm your AI tool is actually using CCC's docs, and a step-by-step decision tree for when it still gets CKB wrong. +icon: ShieldCheck +--- + +Setting up a rule and writing a good prompt doesn't guarantee the assistant is actually reading what you pointed it at — rules silently fail to load, context windows get truncated, and models fall back on memorized (often EVM-shaped) assumptions. This page gives you a fast way to check, and a decision tree for when it fails. + +## Canary questions + +Each of these has one objectively correct answer that a model *without* CCC's [Agent Skills](/skill.md) loaded gets wrong more often than not. Ask one after setup, or any time you suspect the assistant has stopped grounding itself. All of them are lifted directly from the Hallucination Guard / Common Gotchas tables in `ckb-ccc-fundamentals` or the matching spoke skill, so if you spot a wrong answer, that's exactly the section to point the assistant back at. + +| Ask | Correct answer | Wrong answer means | +| --- | --- | --- | +| "Does `signer.getBalance()` return a `number` or a `bigint`?" | `bigint`, in Shannon | Model is assuming EVM-style float balances | +| "What's the correct order: build outputs, complete inputs by capacity, complete fee, then send — or complete fee before completing inputs?" | Outputs → `completeInputsByCapacity` → `completeFeeBy` → `sendTransaction` | Model doesn't know CCC's required transaction pipeline | +| "In a Next.js app using `ccc.Provider`, do I need `\"use client\"`?" | Yes | Model isn't aware of the Next.js App Router gotcha | +| "For a UDT transfer, what has to run before `completeInputsByCapacity`?" | `udt.completeBy(tx, signer)` | Model will produce a transaction that silently drops token change | +| "Which package should a plain Node.js script import — `@ckb-ccc/core` or `@ckb-ccc/shell`?" | `@ckb-ccc/shell` (re-exports `core`) | Model picked the low-level package unprompted, a sign it isn't consulting the Package Selection table | + + + A model that answers all five correctly *without* you pasting any doc content into the prompt is a model that has the relevant Agent Skills (or equivalent context) genuinely loaded. That's the bar — not "sounds confident." + + +## If the assistant still gets it wrong + +Work through this in order — each step rules out one layer of the setup from [Set Up Your AI Tools](./set-up-ai-tools): + +1. **Did the install actually run?** Run `npx skills list` and confirm the `ckb-ccc-*` skills show up for your tool. If nothing's listed, re-run `npx skills add ckb-devrel/ccc` from [Set Up Your AI Tools](./set-up-ai-tools) and pick the tool you're testing. +2. **Is it in the right scope?** Project-level rules only apply to that project. If you're testing from a different repo or a scratch file outside any project, a project rule won't load — use the global/personal path instead. +3. **Did the tool actually attach it to this conversation?** Chat-only tools without a file-based rules mechanism (plain ChatGPT, etc.) require you to paste the system-prompt snippet per conversation, not just once — see [Set Up Your AI Tools](./set-up-ai-tools) if you're not using a rules-native tool. +4. **Is the context window full?** In a long-running conversation, early-loaded rules can get pushed out or de-prioritized. Start a fresh conversation and re-ask the canary question before concluding the setup is broken. +5. **Did the assistant fetch a *stale* copy?** Installed skill files are a point-in-time copy, not a live fetch. Run `npx skills update` to pull it — see [Keep skills up to date](./set-up-ai-tools#keep-skills-up-to-date). +6. **Ask it to show its work.** Literally ask: "Quote the exact section of the `ckb-ccc-fundamentals` skill that answers this." If it can't quote anything, it isn't reading the file — it's guessing and happened to land on the right answer, or it's wrong for a different reason than missing context. + +## When the setup is fine but the answer still isn't + +If the rules are loading and the assistant is still wrong on a specific topic, it's hit the edge of what the loaded skill covers — each skill is a curated summary of its area, not the whole documentation. Ground it on the precise source instead of arguing with it: + +- **Point it at the exact guide.** Give it the specific page as Markdown, e.g. `https://docs.ckbccc.com/en/docs/guides/udt-tokens.md`, rather than the skill file. See [Tell your AI where to find answers](./set-up-ai-tools#tell-your-ai-where-to-find-answers) for the full list of what to fetch when. +- **Check the matching spoke skill, not just the hub.** `ckb-ccc-fundamentals` only covers cross-cutting basics — signer/wallet, transaction, UDT, and Spore specifics live in their own spoke skills (see `/skill.md`). A wrong answer on one of those topics can mean the hub loaded but the spoke skill didn't. +- **For signatures and types, use the API reference.** No skill file has every parameter type — send it to DeepWiki/Context7 or [api.ckbccc.com](https://api.ckbccc.com) for the exact signature (see `ckb-ccc-fundamentals` Step 0). +- **Re-ground, don't correct from memory.** In a long conversation, ask it to re-fetch the relevant page before continuing rather than trusting an answer cached earlier in the chat. + +## Recap + +That closes the loop: your tool is [configured](./set-up-ai-tools), you've confirmed it actually loaded the rules, and you [prompt it well](./prompting-best-practices) per request. Repeat the canary check any time you set up a new machine, switch AI tools, or upgrade CCC to a new major version. diff --git a/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx b/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx new file mode 100644 index 00000000..46d129f2 --- /dev/null +++ b/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx @@ -0,0 +1,47 @@ +--- +title: 验证与故障排查 +description: 用于确认你的 AI 工具是否真正使用了 CCC 文档的金丝雀问题,以及当它仍然答错 CKB 相关问题时的排查步骤。 +icon: ShieldCheck +--- + +设定规则并写好提示词,并不能保证 AI 助手真的读了你指定的内容——规则可能静默加载失败,上下文窗口可能被截断,模型可能会依赖记忆中的(通常是 EVM 形态的)假设。本页为你提供一种快速验证方法,以及在验证失败时的决策树。 + +## 金丝雀问题(Canary Questions) + +以下每个问题都有唯一正确的答案,而**未**加载 CCC 的 [Agent Skills](/skill.md) 的模型答错的概率很高。你可以在完成配置后,或在任何怀疑助手已不再基于文档回答时,选一个问题来验证。这些问题都直接取自 `ckb-ccc-fundamentals` 或对应的 spoke skill 中的"幻觉防护 / 常见陷阱"表格,因此如果你发现答案有误,可以直接将助手引导回那部分内容。 + +| 问题 | 正确答案 | 答错意味着 | +| --- | --- | --- | +| "`signer.getBalance()` 返回 `number` 还是 `bigint`?" | `bigint`,单位是 Shannon | 模型在假设 EVM 风格的浮点数余额 | +| "正确顺序是什么:先构建输出(outputs),再按 capacity 补全输入(inputs),然后补全手续费(fee),最后发送——还是先补全手续费再补全输入?" | Outputs → `completeInputsByCapacity` → `completeFeeBy` → `sendTransaction` | 模型不了解 CCC 要求的交易构建流水线 | +| "在使用了 `ccc.Provider` 的 Next.js 应用中,是否需要 `"use client"`?" | 是 | 模型不知道 Next.js App Router 的常见陷阱 | +| "对于 UDT 转账,在 `completeInputsByCapacity` 之前必须执行什么操作?" | `udt.completeBy(tx, signer)` | 模型生成的交易会静默丢失 UDT 找零 | +| "普通的 Node.js 脚本应该导入哪个包——`@ckb-ccc/core` 还是 `@ckb-ccc/shell`?" | `@ckb-ccc/shell`(它重新导出了 `core`) | 模型在未被提示的情况下选了底层包,说明它没有参考"包选择"表格 | + + + 模型能在你未向提示词中粘贴任何文档内容的情况下全部答对这五个问题,说明它确实加载了相关的 Agent Skills(或同等上下文)。这才是衡量标准——而不是"听起来很自信"。 + + +## 如果助手仍然答错 + +按顺序逐一排查——每一步都对应排除 [配置 AI 工具](./set-up-ai-tools) 中的某一层配置问题: + +1. **安装是否真的执行了?** 运行 `npx skills list`,确认你的工具中确实列出了 `ckb-ccc-*` 相关的 skill。如果没有列出,请按[配置 AI 工具](./set-up-ai-tools)中的说明重新运行 `npx skills add ckb-devrel/ccc`,并选择你要测试的工具。 +2. **规则是否在正确的作用域内?** 项目级规则仅适用于该项目。如果你从另一个仓库或项目外的临时文件进行测试,项目级规则不会生效——请改用全局/个人路径。 +3. **工具是否真的将规则附加到了当前会话?** 对于没有基于文件的规则机制的纯聊天工具(如普通 ChatGPT 等),你需要为每次会话粘贴系统提示片段,而非只粘贴一次——详见[配置 AI 工具](./set-up-ai-tools)。 +4. **上下文窗口是否已满?** 在长时间的会话中,早期加载的规则可能被挤出或降低优先级。开启一个新会话,重新问一个金丝雀问题,再下结论说配置有问题。 +5. **助手是否拉取了一份过期的副本?** 已安装的 skill 文件是某个时间点的副本,并非实时拉取。运行 `npx skills update` 来更新——参见[保持 skill 最新](./set-up-ai-tools#keep-skills-up-to-date)。 +6. **要求助手展示依据。** 直接问:"请引用 `ckb-ccc-fundamentals` skill 中回答这个问题的原话。" 如果它无法引用任何内容,说明它没有在读取文件——它是在猜测并碰巧蒙对了,或者答错的原因并非缺少上下文。 + +## 配置没问题但答案仍然不对时 + +如果规则已加载,但助手在某个具体话题上仍然答错,说明问题已经触及了已加载 skill 的边界——每个 skill 都是其领域内容的精选摘要,而非完整的文档。此时应将其引导到精确的源文档,而不是与它争论: + +- **将助手指向具体的指南。** 将具体页面的 Markdown 内容给它,例如 `https://docs.ckbccc.com/en/docs/guides/udt-tokens.md`,而不是给 skill 文件。完整列表参见[告诉你的 AI 去哪里找答案](./set-up-ai-tools#tell-your-ai-where-to-find-answers)。 +- **检查对应的 spoke skill,而不只是 hub。** `ckb-ccc-fundamentals` 只涵盖跨领域的基础知识——signer/wallet、交易、UDT 和 Spore 的细节各自位于独立的 spoke skill 中(参见 `/skill.md`)。如果某个话题答错了,可能意味着 hub 加载了但 spoke skill 没有。 +- **签名和类型相关的问题,使用 API 参考文档。** 没有任何一个 skill 文件包含所有参数类型——将助手导向 DeepWiki/Context7 或 [api.ckbccc.com](https://api.ckbccc.com) 获取精确的方法签名(参见 `ckb-ccc-fundamentals` 的步骤 0)。 +- **重新锚定,不要凭记忆纠正。** 在长会话中,要求助手在继续之前重新拉取相关页面,而不是信任会话早期缓存的答案。 + +## 小结 + +这样就形成了一个闭环:你的工具已经[配置完成](./set-up-ai-tools),你也确认了它确实加载了规则,并且在每次请求时你都[正确地提示它](./prompting-best-practices)。每当你设置新机器、切换 AI 工具,或将 CCC 升级到新的主要版本时,都可以重复金丝雀问题来验证。 \ No newline at end of file diff --git a/packages/docs/content/docs/getting-started/introduction.zh.mdx b/packages/docs/content/docs/getting-started/introduction.zh.mdx index 1a4faaae..31957125 100644 --- a/packages/docs/content/docs/getting-started/introduction.zh.mdx +++ b/packages/docs/content/docs/getting-started/introduction.zh.mdx @@ -4,7 +4,7 @@ description: 构建 CKB 应用所需的一切——钱包连接、交易组装 icon: BookOpen --- -import { Rocket, Package, BookOpen, Map } from 'lucide-react'; +import { Rocket, Package, BookOpen, Map, Bot } from 'lucide-react'; CKB 是一条基于 UTXO 模型的区块链,原生支持 Bitcoin 和 EVM 钱包,天然适合跨链应用的构建。 @@ -55,4 +55,7 @@ CCC 已在 CKB 生态的多个生产应用中落地,包括 } href="../guides/connect-wallets"> 找到你正在构建的功能对应的实现指南——按步骤指导。 + } href="../ai-resources"> + 花不到 2 分钟,把 Cursor、Claude Code、Copilot 或 Windsurf 接入 CCC 的文档,让它们不再瞎猜 CKB API。 + diff --git a/packages/docs/content/docs/meta.json b/packages/docs/content/docs/meta.json index efa4d169..7c919c5f 100644 --- a/packages/docs/content/docs/meta.json +++ b/packages/docs/content/docs/meta.json @@ -21,6 +21,11 @@ "guides/node-js-backend", "---Examples---", "code-examples", + "---AI Resources---", + "ai-resources/index", + "ai-resources/set-up-ai-tools", + "ai-resources/prompting-best-practices", + "ai-resources/verify-and-troubleshoot", "---Packages---", "packages/index", "packages/core-packages", diff --git a/packages/docs/content/docs/meta.zh.json b/packages/docs/content/docs/meta.zh.json index ef73fa67..1752068b 100644 --- a/packages/docs/content/docs/meta.zh.json +++ b/packages/docs/content/docs/meta.zh.json @@ -21,6 +21,11 @@ "guides/node-js-backend", "---示例---", "code-examples", + "---AI 资源---", + "ai-resources/index", + "ai-resources/set-up-ai-tools", + "ai-resources/prompting-best-practices", + "ai-resources/verify-and-troubleshoot", "---CCC 包---", "packages/index", "packages/core-packages", From 82b6f11a13eacafb569f11932379064e5186cd52 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 21:55:53 +0800 Subject: [PATCH 04/15] examples: add xUDT Single-Use-Seal issuance example - Add XudtSusIssuer class that encapsulates the 3-step SUS flow: 1. Create seal cell (reserved until mint) 2. Create owner cell locked with SingleUseLock bound to seal 3. Consume seal + owner to mint xUDT + UniqueType metadata cells - Add issueXudtWithSUS() convenience function for playground usage - Includes progress callback for tracking each transaction step - Handles TypeId calculation for UniqueType after inputs are finalized --- packages/examples/src/issueUdtSus.ts | 270 +++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 packages/examples/src/issueUdtSus.ts diff --git a/packages/examples/src/issueUdtSus.ts b/packages/examples/src/issueUdtSus.ts new file mode 100644 index 00000000..359c10de --- /dev/null +++ b/packages/examples/src/issueUdtSus.ts @@ -0,0 +1,270 @@ +import { ccc } from "@ckb-ccc/ccc"; +import { signer } from "@ckb-ccc/playground"; + +/* ============================================================ + * Types + * ============================================================ */ + +/** + * @public + * @category Token + */ +interface TokenMetadata { + decimals: number; + symbol: string; + /** Falls back to `symbol` when omitted/blank */ + name?: string; +} + +/** + * The 3 on-chain steps of the SingleUseLock (SUS) issuance flow, in order. + * @public + * @category Token + */ +type IssueStep = "seal" | "owner" | "mint"; + +/** + * @public + * @category Token + */ +interface IssueXudtSusParams extends TokenMetadata { + /** Human-readable total supply, e.g. 100_000_000 (converted to Shannon internally) */ + totalSupply: ccc.FixedPointLike; + /** Optional hook, fired after each of the 3 on-chain steps is broadcast */ + onProgress?: (step: IssueStep, txHash: ccc.Hex) => void; +} + +/** + * @public + * @category Token + */ +interface IssueXudtSusResult { + sealTxHash: ccc.Hex; + ownerTxHash: ccc.Hex; + mintTxHash: ccc.Hex; + typeScriptHash: ccc.Hex; +} + +/* ============================================================ + * Token metadata encoding (pure, no chain I/O) + * ============================================================ */ + +/** + * Encodes xUDT token metadata (decimals + name + symbol) into the + * `UniqueType` cell data layout. + * @public + * @category Token + * + * @param metadata - The token's decimals, symbol, and optional name. + * @returns The encoded bytes to be used as `outputsData` for the `UniqueType` cell. + * @throws If `symbol` is missing, `decimals` is out of the 0-255 range, or + * either `symbol`/`name` exceeds 255 UTF-8 bytes. + */ +function encodeTokenInfo({ + decimals, + symbol, + name, +}: TokenMetadata): Uint8Array { + if (!symbol) { + throw new Error("symbol is required"); + } + if (decimals < 0 || decimals > 255) { + throw new Error("decimals must be within 0-255 (1-byte field)"); + } + + const symbolBytes = ccc.bytesFrom(symbol, "utf8"); + const nameBytes = ccc.bytesFrom(name?.trim() ? name : symbol, "utf8"); + + // Each string is length-prefixed with a single byte -> hard cap at 255 bytes + if (symbolBytes.length > 255 || nameBytes.length > 255) { + throw new Error("symbol/name must each be <= 255 bytes when UTF-8 encoded"); + } + + return ccc.bytesConcat( + ccc.numToBytes(decimals, 1), + ccc.numToBytes(nameBytes.length, 1), + nameBytes, + ccc.numToBytes(symbolBytes.length, 1), + symbolBytes, + ); +} + +/** + * Issues an xUDT token using the Single-Use-Seal (SUS) pattern: a seal cell + * anchors a `SingleUseLock` owner cell, which is then consumed together with + * the seal to mint the xUDT cell and its `UniqueType` metadata cell. + * + * @public + * @category Blockchain + * @category Token + */ +class XudtSusIssuer { + /** + * @param signer - The signer used to fund and sign all 3 transactions. + */ + constructor(private readonly signer: ccc.Signer) {} + + /** + * Runs the full seal -> owner -> mint flow. + * + * @param params - Token metadata, total supply, and an optional progress hook. + * @returns The tx hashes of all 3 steps and the resulting xUDT type script hash. + * + * @example + * ```typescript + * const result = await new XudtSusIssuer(signer).issue({ + * decimals: 8, + * symbol: "SPARK", + * totalSupply: 100_000_000, + * onProgress: (step, txHash) => console.log(step, txHash), + * }); + * ``` + */ + async issue(params: IssueXudtSusParams): Promise { + const { script: ownerLock } = await this.signer.getRecommendedAddressObj(); + + const sealTxHash = await this.createSealCell(ownerLock); + params.onProgress?.("seal", sealTxHash); + + const singleUseLock = await ccc.Script.fromKnownScript( + this.signer.client, + ccc.KnownScript.SingleUseLock, + ccc.OutPoint.from({ txHash: sealTxHash, index: 0 }).toBytes(), + ); + + const ownerTxHash = await this.createOwnerCell(singleUseLock); + params.onProgress?.("owner", ownerTxHash); + + const { mintTxHash, typeScriptHash } = await this.mintToken({ + sealTxHash, + ownerTxHash, + singleUseLock, + ownerLock, + metadata: params, + }); + params.onProgress?.("mint", mintTxHash); + + return { sealTxHash, ownerTxHash, mintTxHash, typeScriptHash }; + } + + /** Step 1 - the "seal" cell that SingleUseLock will bind to and that mint() later consumes as proof */ + private async createSealCell(lock: ccc.Script): Promise { + const tx = ccc.Transaction.from({ outputs: [{ lock }] }); + await tx.completeInputsByCapacity(this.signer); + await tx.completeFeeBy(this.signer); + const txHash = await this.signer.sendTransaction(tx); + + // Reserve this output so later completeInputsByCapacity() calls in this + // flow don't accidentally spend it as generic fee-funding - it must + // survive untouched until the mint transaction consumes it as the seal. + await this.signer.client.cache.markUnusable({ txHash, index: 0 }); + + return txHash; + } + + /** Step 2 - the owner cell, locked with SingleUseLock bound to the seal's outpoint */ + private async createOwnerCell(singleUseLock: ccc.Script): Promise { + const tx = ccc.Transaction.from({ outputs: [{ lock: singleUseLock }] }); + await tx.completeInputsByCapacity(this.signer); + await tx.completeFeeBy(this.signer); + return this.signer.sendTransaction(tx); + } + + /** Step 3 - consume seal + owner cells to mint the xUDT cell and its UniqueType metadata cell */ + private async mintToken(args: { + sealTxHash: ccc.Hex; + ownerTxHash: ccc.Hex; + singleUseLock: ccc.Script; + ownerLock: ccc.Script; + metadata: IssueXudtSusParams; + }): Promise<{ mintTxHash: ccc.Hex; typeScriptHash: ccc.Hex }> { + const { sealTxHash, ownerTxHash, singleUseLock, ownerLock, metadata } = args; + const { decimals, symbol, name, totalSupply } = metadata; + + const xudtType = await ccc.Script.fromKnownScript( + this.signer.client, + ccc.KnownScript.XUdt, + singleUseLock.hash(), + ); + const uniqueType = await ccc.Script.fromKnownScript( + this.signer.client, + ccc.KnownScript.UniqueType, + "00".repeat(32), // placeholder - patched below with the real TypeId once inputs are known + ); + + const tx = ccc.Transaction.from({ + inputs: [ + { previousOutput: { txHash: sealTxHash, index: 0 } }, + { previousOutput: { txHash: ownerTxHash, index: 0 } }, + ], + outputs: [ + { lock: ownerLock, type: xudtType }, + { lock: ownerLock, type: uniqueType }, + ], + outputsData: [ + ccc.numLeToBytes(ccc.fixedPointFrom(totalSupply, decimals), 16), + encodeTokenInfo({ decimals, symbol, name }), + ], + }); + + await tx.addCellDepsOfKnownScripts( + this.signer.client, + ccc.KnownScript.SingleUseLock, + ccc.KnownScript.XUdt, + ccc.KnownScript.UniqueType, + ); + + await tx.completeInputsByCapacity(this.signer); + + // UniqueType's args must be a TypeId derived from the first input + output + // index, which is only known once inputs are finalized above. + if (!tx.outputs[1].type) { + throw new Error("UniqueType output unexpectedly missing before TypeId patch"); + } + tx.outputs[1].type.args = ccc.hexFrom( + ccc.bytesFrom(ccc.hashTypeId(tx.inputs[0], 1)).slice(0, 20), + ); + + await tx.completeFeeBy(this.signer); + const mintTxHash = await this.signer.sendTransaction(tx); + await this.signer.client.waitTransaction(mintTxHash); + + return { mintTxHash, typeScriptHash: tx.outputs[0].type!.hash() }; + } +} + +/* ============================================================ + * Convenience entrypoint - keeps the same call shape as before + * ============================================================ */ + +/** + * Issues an xUDT token via the Single-Use-Seal pattern using the + * playground's default signer. + * @public + * @category Token + * + * @example + * ```typescript + * const result = await issueXudtWithSUS({ + * decimals: 8, + * symbol: "SPARK", + * totalSupply: 100_000_000, + * onProgress: (step, txHash) => console.log(step, txHash), + * }); + * console.log(result); + * ``` + */ +export async function issueXudtWithSUS( + params: IssueXudtSusParams, +): Promise { + return new XudtSusIssuer(signer).issue(params); +} + +// Example usage in the playground: +const result = await issueXudtWithSUS({ + decimals: 8, + symbol: "SPARK", + totalSupply: 100_000_000, + onProgress: (step, txHash) => console.log(step, txHash), +}); +console.log(result); \ No newline at end of file From d3c3234aeb4705407ce88e6fa6c6ab2ad24afaeb Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 22:27:55 +0800 Subject: [PATCH 05/15] skills: fix @ckb-ccc/connector import pattern in fundamentals - Update package selection table to show import statement instead of Web Component tag - Clarifies that @ckb-ccc/connector uses standard import pattern like other packages --- skills/ckb-ccc-fundamentals/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/ckb-ccc-fundamentals/SKILL.md b/skills/ckb-ccc-fundamentals/SKILL.md index f18f716a..0ba9eae6 100644 --- a/skills/ckb-ccc-fundamentals/SKILL.md +++ b/skills/ckb-ccc-fundamentals/SKILL.md @@ -49,7 +49,7 @@ CKB uses the **Cell model** — a generalized UTXO model. This is the #1 source | React/Next.js dApp | `@ckb-ccc/connector-react` | `import { ccc } from "@ckb-ccc/connector-react"` | | Node.js backend / scripts | `@ckb-ccc/shell` | `import { ccc } from "@ckb-ccc/shell"` | | Custom wallet UI (non-React) | `@ckb-ccc/ccc` | `import { ccc } from "@ckb-ccc/ccc"` | -| Framework-agnostic Web Component | `@ckb-ccc/connector` | Web Component `` | +| Framework-agnostic Web Component | `@ckb-ccc/connector` | `import { ccc } from "@ckb-ccc/connector"` | | Library authoring (minimal deps) | `@ckb-ccc/core` | `import { ccc } from "@ckb-ccc/core"` | **Rule**: `@ckb-ccc/shell` and `@ckb-ccc/connector-react` already re-export everything from `@ckb-ccc/core` — do not install core separately unless authoring a library. From f6c27ede38f64a9f6863203bcfe3ff2a9cac6710 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 23:34:27 +0800 Subject: [PATCH 06/15] lint(examples): fix prettier formatting in issueUdtSus.ts --- packages/examples/src/issueUdtSus.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/examples/src/issueUdtSus.ts b/packages/examples/src/issueUdtSus.ts index 359c10de..679e9cbe 100644 --- a/packages/examples/src/issueUdtSus.ts +++ b/packages/examples/src/issueUdtSus.ts @@ -178,7 +178,8 @@ class XudtSusIssuer { ownerLock: ccc.Script; metadata: IssueXudtSusParams; }): Promise<{ mintTxHash: ccc.Hex; typeScriptHash: ccc.Hex }> { - const { sealTxHash, ownerTxHash, singleUseLock, ownerLock, metadata } = args; + const { sealTxHash, ownerTxHash, singleUseLock, ownerLock, metadata } = + args; const { decimals, symbol, name, totalSupply } = metadata; const xudtType = await ccc.Script.fromKnownScript( @@ -219,7 +220,9 @@ class XudtSusIssuer { // UniqueType's args must be a TypeId derived from the first input + output // index, which is only known once inputs are finalized above. if (!tx.outputs[1].type) { - throw new Error("UniqueType output unexpectedly missing before TypeId patch"); + throw new Error( + "UniqueType output unexpectedly missing before TypeId patch", + ); } tx.outputs[1].type.args = ccc.hexFrom( ccc.bytesFrom(ccc.hashTypeId(tx.inputs[0], 1)).slice(0, 20), @@ -267,4 +270,4 @@ const result = await issueXudtWithSUS({ totalSupply: 100_000_000, onProgress: (step, txHash) => console.log(step, txHash), }); -console.log(result); \ No newline at end of file +console.log(result); From d68f32db520e00953ca3017bcdac1799970eb154 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Wed, 15 Jul 2026 23:58:27 +0800 Subject: [PATCH 07/15] fix: address code review feedback in skills and docs --- packages/docs/content/docs/guides/playground.mdx | 2 +- packages/docs/content/docs/guides/playground.zh.mdx | 2 +- skills/ckb-ccc-playground/SKILL.md | 2 +- skills/ckb-ccc-udt/SKILL.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/docs/content/docs/guides/playground.mdx b/packages/docs/content/docs/guides/playground.mdx index 70a359a7..c06a5599 100644 --- a/packages/docs/content/docs/guides/playground.mdx +++ b/packages/docs/content/docs/guides/playground.mdx @@ -123,7 +123,7 @@ This example shows how to create a Spore — a non-fungible token on CKB: ```typescript import { ccc } from "@ckb-ccc/ccc"; -import { render, signer, client } from "@ckb-ccc/playground"; +import { render, signer } from "@ckb-ccc/playground"; const { tx, id: sporeId } = await ccc.spore.createSpore({ signer, diff --git a/packages/docs/content/docs/guides/playground.zh.mdx b/packages/docs/content/docs/guides/playground.zh.mdx index 1aa1eb80..e690f6f5 100644 --- a/packages/docs/content/docs/guides/playground.zh.mdx +++ b/packages/docs/content/docs/guides/playground.zh.mdx @@ -121,7 +121,7 @@ await render(tx); ```typescript import { ccc } from "@ckb-ccc/ccc"; -import { render, signer, client } from "@ckb-ccc/playground"; +import { render, signer } from "@ckb-ccc/playground"; const { tx, id: sporeId } = await ccc.spore.createSpore({ signer, diff --git a/skills/ckb-ccc-playground/SKILL.md b/skills/ckb-ccc-playground/SKILL.md index cb84d117..9069d38b 100644 --- a/skills/ckb-ccc-playground/SKILL.md +++ b/skills/ckb-ccc-playground/SKILL.md @@ -30,7 +30,7 @@ import { render, signer, client } from "@ckb-ccc/playground"; - `render` / `signer` / `client` — playground-provided helpers (`@ckb-ccc/playground`); `signer` is pre-connected, `client` is its underlying `ClientPublicTestnet`/`ClientPublicMainnet` instance — import `client` whenever a call needs the client directly instead of going through `signer.client`. ```typescript -// signer` is already connected — Testnet by default, no setup needed +// signer is already connected — Testnet by default, no setup needed console.log(await signer.getRecommendedAddress()); // replace with the recipient address diff --git a/skills/ckb-ccc-udt/SKILL.md b/skills/ckb-ccc-udt/SKILL.md index 378ae493..8667a19b 100644 --- a/skills/ckb-ccc-udt/SKILL.md +++ b/skills/ckb-ccc-udt/SKILL.md @@ -7,7 +7,7 @@ metadata: role: spoke depends-on: "ckb-ccc-fundamentals, ckb-ccc-transactions" priority: normal - status: "current API — @ckb-ccc/coin expected within weeks; @ckb-ccc/udt will then be removed/unmaintained. This file becomes a redirect stub for one release cycle (new content goes in ckb-ccc-coin/, this directory is deleted afterward" + status: "current API — @ckb-ccc/coin expected within weeks; @ckb-ccc/udt will then be removed/unmaintained. This file becomes a redirect stub for one release cycle (new content goes in ckb-ccc-coin/, this directory is deleted afterward)" --- # CKB CCC — UDT Tokens From 6986ff51b8321a0e5c1137575bf7f8ab5e162393 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Thu, 16 Jul 2026 01:51:35 +0800 Subject: [PATCH 08/15] docs: improve playground examples and skill installation command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `--all` flag to npx skills add command in skill.md route for non-interactive installation - Add console.log to display Spore ID after creation in playground example (both EN and ZH) - Fix Chinese playground example description from "发行代币" to "创建 Spore" for accuracy --- packages/docs/app/skill.md/route.ts | 2 +- packages/docs/content/docs/guides/playground.mdx | 2 ++ packages/docs/content/docs/guides/playground.zh.mdx | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/docs/app/skill.md/route.ts b/packages/docs/app/skill.md/route.ts index 6ec3c948..6146def0 100644 --- a/packages/docs/app/skill.md/route.ts +++ b/packages/docs/app/skill.md/route.ts @@ -69,7 +69,7 @@ const preamble = `# CCC Agent Skills (CKB / CCC development) If your tool/agent can run a shell command, install with the [\`skills\` CLI](https://github.com/vercel-labs/skills) (supports Cursor, Claude Code, GitHub Copilot, Windsurf, Codex, and 60+ other agents — auto-discovers the \`skills/\` directory and writes each skill to the right path for your tool): \`\`\`bash -npx skills add ${repoSlug} +npx skills add ${repoSlug} --all \`\`\` If your tool/agent can only fetch URLs (no shell access — browser-based agents, plain chat models, etc.), fetch the raw \`SKILL.md\` files directly from the table below. Always fetch \`ckb-ccc-fundamentals\` first, then the spoke skill matching the task. diff --git a/packages/docs/content/docs/guides/playground.mdx b/packages/docs/content/docs/guides/playground.mdx index c06a5599..dbcaa662 100644 --- a/packages/docs/content/docs/guides/playground.mdx +++ b/packages/docs/content/docs/guides/playground.mdx @@ -132,6 +132,8 @@ const { tx, id: sporeId } = await ccc.spore.createSpore({ content: new TextEncoder().encode("Hello, Spore!"), }, }); + +console.log("Spore ID:", sporeId); await render(tx); await tx.completeInputsByCapacity(signer); diff --git a/packages/docs/content/docs/guides/playground.zh.mdx b/packages/docs/content/docs/guides/playground.zh.mdx index e690f6f5..6051d03a 100644 --- a/packages/docs/content/docs/guides/playground.zh.mdx +++ b/packages/docs/content/docs/guides/playground.zh.mdx @@ -113,7 +113,7 @@ await render(tx); 点击 **Step** 而非 **Run**,可在每个 `render()` 调用处暂停。点击 **Continue** 进入下一个断点,非常适合逐步理解 CCC 构建交易的过程。 -掌握了基本的转账流程后,来看一个更复杂的场景——在链上发行代币。 +掌握了基本的转账流程后,来看一个更复杂的场景——在链上创建 Spore。 ## 示例二:创建 Spore @@ -130,6 +130,8 @@ const { tx, id: sporeId } = await ccc.spore.createSpore({ content: new TextEncoder().encode("Hello, Spore!"), }, }); + +console.log("Spore ID:", sporeId); await render(tx); await tx.completeInputsByCapacity(signer); From c0955f291004d9a1b4ecf2ebf775d26cb0780aec Mon Sep 17 00:00:00 2001 From: aabbcc Date: Thu, 16 Jul 2026 08:27:30 +0800 Subject: [PATCH 09/15] docs: align skill dependency field name in README with SKILL.md - Change `dependsOn` to `depends-on` in skills/README.md to match the actual YAML frontmatter field name used in SKILL.md files - Add descriptive comment to issueUdtSus.ts example --- packages/examples/src/issueUdtSus.ts | 2 ++ skills/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/examples/src/issueUdtSus.ts b/packages/examples/src/issueUdtSus.ts index 679e9cbe..82ce3983 100644 --- a/packages/examples/src/issueUdtSus.ts +++ b/packages/examples/src/issueUdtSus.ts @@ -1,3 +1,5 @@ +// Example: Issue an xUDT token using the Single-Use-Seal (SUS) pattern. + import { ccc } from "@ckb-ccc/ccc"; import { signer } from "@ckb-ccc/playground"; diff --git a/skills/README.md b/skills/README.md index 6409c728..dab371f9 100644 --- a/skills/README.md +++ b/skills/README.md @@ -14,7 +14,7 @@ Both paths mean **anyone with an existing install only sees your changes after t - **patch** (`1.0.0` → `1.0.1`): wording/clarity fixes, no behavior change. - **minor** (`1.0.0` → `1.1.0`): new guidance added, existing guidance unchanged. - **major** (`1.0.0` → `2.0.0`): a previous instruction was wrong and is being corrected/reversed — this is the case most worth calling out in the PR description, since an agent that already loaded the old version may be acting on bad guidance. -2. **Keep `docs.ckbccc.com/skill.md`'s skill table in sync** if you add, remove, or rename a skill, or change its `role`/`dependsOn` — it's maintained by hand in `packages/docs/app/skill.md/route.ts`, not generated from this directory. +2. **Keep `docs.ckbccc.com/skill.md`'s skill table in sync** if you add, remove, or rename a skill, or change its `role`/`depends-on` — it's maintained by hand in `packages/docs/app/skill.md/route.ts`, not generated from this directory. 3. **Don't rely on `npx skills update` alone to signal urgency.** It's a pull-based content-hash diff, not a version check, and has known reliability gaps (e.g. [vercel-labs/skills#484](https://github.com/vercel-labs/skills/issues/484) — sometimes reports "up to date" when the remote has changed). For a correction significant enough that stale guidance would actively mislead an agent (a major bump), call it out in the PR/release notes so it's not silently missed. ## Verifying a change From aec811975449ce26ec47cbf74b869a62874d8871 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Sun, 19 Jul 2026 01:38:24 +0800 Subject: [PATCH 10/15] fix(skill): correct UDT supply scaling documentation in ckb-ccc-udt skill Updated the checklist item to accurately reflect that totalSupply accepts human-readable numbers (e.g., 100_000_000) which are converted to on-chain integers via ccc.fixedPointFrom(totalSupply, decimals). --- skills/ckb-ccc-udt/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/ckb-ccc-udt/SKILL.md b/skills/ckb-ccc-udt/SKILL.md index 8667a19b..b50436ce 100644 --- a/skills/ckb-ccc-udt/SKILL.md +++ b/skills/ckb-ccc-udt/SKILL.md @@ -358,7 +358,7 @@ A: You can view it in the CKB explorer: ## Checklist (UDT-specific) - [ ] **UDT change** — `udt.completeBy()` called before `completeInputsByCapacity` for token transfers -- [ ] **Supply scaling** — `totalSupply` (and any amount) is the on-chain integer (`display * 10^decimals`), not the human-facing number +- [ ] **Supply scaling** — `totalSupply` (and any amount) is the human-readable number (e.g., `100_000_000`), which `ccc.fixedPointFrom(totalSupply, decimals)` converts to the on-chain integer by applying `10^decimals` - [ ] **Tested on testnet first** — for real issuance, run the full flow on `ClientPublicTestnet` and verify in an explorer before switching to `ClientPublicMainnet` - [ ] **symbol vs name** — `symbol` holds the ticker/display identifier; `name` is supplementary description From b7f97b1b52e95385eb2461cd6ccd679ecbf03aaf Mon Sep 17 00:00:00 2001 From: aabbcc Date: Sun, 19 Jul 2026 20:10:46 +0800 Subject: [PATCH 11/15] fix(udt-skill):validate mint inputs before SUS broadcast - Add integer check for decimals (0-255) in encodeTokenInfo - Precompute tokenInfo and supply in issue() before createSealCell - Validate supply is non-negative uint128 before any transaction - Pass precomputed values to mintToken instead of recomputing - Mirror changes in ckb-ccc-udt skill documentation This prevents broadcasting seal/owner transactions with invalid parameters that would only fail at the mint step. --- packages/examples/src/issueUdtSus.ts | 40 +++++++++++++++++++--------- skills/ckb-ccc-udt/SKILL.md | 37 ++++++++++++++++++------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/packages/examples/src/issueUdtSus.ts b/packages/examples/src/issueUdtSus.ts index 82ce3983..6ad817bc 100644 --- a/packages/examples/src/issueUdtSus.ts +++ b/packages/examples/src/issueUdtSus.ts @@ -70,8 +70,8 @@ function encodeTokenInfo({ if (!symbol) { throw new Error("symbol is required"); } - if (decimals < 0 || decimals > 255) { - throw new Error("decimals must be within 0-255 (1-byte field)"); + if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) { + throw new Error("decimals must be an integer within 0-255 (1-byte field)"); } const symbolBytes = ccc.bytesFrom(symbol, "utf8"); @@ -123,6 +123,18 @@ class XudtSusIssuer { * ``` */ async issue(params: IssueXudtSusParams): Promise { + const { decimals, totalSupply } = params; + if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) { + throw new Error( + "decimals must be an integer within 0-255 (1-byte field)", + ); + } + const tokenInfo = encodeTokenInfo(params); + const supply = ccc.fixedPointFrom(totalSupply, decimals); + if (supply < 0n || supply >= 1n << 128n) { + throw new Error("totalSupply must encode to a non-negative uint128"); + } + const { script: ownerLock } = await this.signer.getRecommendedAddressObj(); const sealTxHash = await this.createSealCell(ownerLock); @@ -142,7 +154,8 @@ class XudtSusIssuer { ownerTxHash, singleUseLock, ownerLock, - metadata: params, + supply, + tokenInfo, }); params.onProgress?.("mint", mintTxHash); @@ -178,11 +191,17 @@ class XudtSusIssuer { ownerTxHash: ccc.Hex; singleUseLock: ccc.Script; ownerLock: ccc.Script; - metadata: IssueXudtSusParams; + supply: bigint; + tokenInfo: Uint8Array; }): Promise<{ mintTxHash: ccc.Hex; typeScriptHash: ccc.Hex }> { - const { sealTxHash, ownerTxHash, singleUseLock, ownerLock, metadata } = - args; - const { decimals, symbol, name, totalSupply } = metadata; + const { + sealTxHash, + ownerTxHash, + singleUseLock, + ownerLock, + supply, + tokenInfo, + } = args; const xudtType = await ccc.Script.fromKnownScript( this.signer.client, @@ -204,10 +223,7 @@ class XudtSusIssuer { { lock: ownerLock, type: xudtType }, { lock: ownerLock, type: uniqueType }, ], - outputsData: [ - ccc.numLeToBytes(ccc.fixedPointFrom(totalSupply, decimals), 16), - encodeTokenInfo({ decimals, symbol, name }), - ], + outputsData: [ccc.numLeToBytes(supply, 16), tokenInfo], }); await tx.addCellDepsOfKnownScripts( @@ -259,7 +275,7 @@ class XudtSusIssuer { * console.log(result); * ``` */ -export async function issueXudtWithSUS( +async function issueXudtWithSUS( params: IssueXudtSusParams, ): Promise { return new XudtSusIssuer(signer).issue(params); diff --git a/skills/ckb-ccc-udt/SKILL.md b/skills/ckb-ccc-udt/SKILL.md index b50436ce..c3272cd2 100644 --- a/skills/ckb-ccc-udt/SKILL.md +++ b/skills/ckb-ccc-udt/SKILL.md @@ -91,8 +91,8 @@ function encodeTokenInfo({ if (!symbol) { throw new Error("symbol is required"); } - if (decimals < 0 || decimals > 255) { - throw new Error("decimals must be within 0-255 (1-byte field)"); + if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) { + throw new Error("decimals must be an integer within 0-255 (1-byte field)"); } const symbolBytes = ccc.bytesFrom(symbol, "utf8"); @@ -144,6 +144,18 @@ class XudtSusIssuer { * ``` */ async issue(params: IssueXudtSusParams): Promise { + const { decimals, totalSupply } = params; + if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) { + throw new Error( + "decimals must be an integer within 0-255 (1-byte field)", + ); + } + const tokenInfo = encodeTokenInfo(params); + const supply = ccc.fixedPointFrom(totalSupply, decimals); + if (supply < 0n || supply >= 1n << 128n) { + throw new Error("totalSupply must encode to a non-negative uint128"); + } + const { script: ownerLock } = await this.signer.getRecommendedAddressObj(); const sealTxHash = await this.createSealCell(ownerLock); @@ -163,7 +175,8 @@ class XudtSusIssuer { ownerTxHash, singleUseLock, ownerLock, - metadata: params, + supply, + tokenInfo, }); params.onProgress?.("mint", mintTxHash); @@ -199,10 +212,17 @@ class XudtSusIssuer { ownerTxHash: ccc.Hex; singleUseLock: ccc.Script; ownerLock: ccc.Script; - metadata: IssueXudtSusParams; + supply: bigint; + tokenInfo: Uint8Array; }): Promise<{ mintTxHash: ccc.Hex; typeScriptHash: ccc.Hex }> { - const { sealTxHash, ownerTxHash, singleUseLock, ownerLock, metadata } = args; - const { decimals, symbol, name, totalSupply } = metadata; + const { + sealTxHash, + ownerTxHash, + singleUseLock, + ownerLock, + supply, + tokenInfo, + } = args; const xudtType = await ccc.Script.fromKnownScript( this.signer.client, @@ -224,10 +244,7 @@ class XudtSusIssuer { { lock: ownerLock, type: xudtType }, { lock: ownerLock, type: uniqueType }, ], - outputsData: [ - ccc.numLeToBytes(ccc.fixedPointFrom(totalSupply, decimals), 16), - encodeTokenInfo({ decimals, symbol, name }), - ], + outputsData: [ccc.numLeToBytes(supply, 16), tokenInfo], }); await tx.addCellDepsOfKnownScripts( From 5ab8ba6b18b4d4d5d34e6ac188ad17598810c0e7 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Sun, 19 Jul 2026 20:20:04 +0800 Subject: [PATCH 12/15] fix: address coderabbit review feedback in skills - Add environment variable validation in signer-setup example - Use ccc.spore namespace consistently in spore skill - Clarify fee rate guidance and error messages in transactions - Recommend waitTransaction for confirmation checks - Improve Chinese documentation accuracy --- .../ai-resources/verify-and-troubleshoot.zh.mdx | 2 +- skills/ckb-ccc-playground/SKILL.md | 2 +- skills/ckb-ccc-signer-setup/SKILL.md | 8 ++++++-- skills/ckb-ccc-spore/SKILL.md | 17 ++++++++++------- skills/ckb-ccc-transactions/SKILL.md | 9 ++++----- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx b/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx index 46d129f2..f9458229 100644 --- a/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx +++ b/packages/docs/content/docs/ai-resources/verify-and-troubleshoot.zh.mdx @@ -30,7 +30,7 @@ icon: ShieldCheck 2. **规则是否在正确的作用域内?** 项目级规则仅适用于该项目。如果你从另一个仓库或项目外的临时文件进行测试,项目级规则不会生效——请改用全局/个人路径。 3. **工具是否真的将规则附加到了当前会话?** 对于没有基于文件的规则机制的纯聊天工具(如普通 ChatGPT 等),你需要为每次会话粘贴系统提示片段,而非只粘贴一次——详见[配置 AI 工具](./set-up-ai-tools)。 4. **上下文窗口是否已满?** 在长时间的会话中,早期加载的规则可能被挤出或降低优先级。开启一个新会话,重新问一个金丝雀问题,再下结论说配置有问题。 -5. **助手是否拉取了一份过期的副本?** 已安装的 skill 文件是某个时间点的副本,并非实时拉取。运行 `npx skills update` 来更新——参见[保持 skill 最新](./set-up-ai-tools#keep-skills-up-to-date)。 +5. **助手是否拉取了一份过期的副本?** 已安装的 skill 文件是某个时间点的副本,并非实时拉取。运行 `npx skills update` 来更新——参见[保持 skill 最新](./set-up-ai-tools#保持-skills-最新)。 6. **要求助手展示依据。** 直接问:"请引用 `ckb-ccc-fundamentals` skill 中回答这个问题的原话。" 如果它无法引用任何内容,说明它没有在读取文件——它是在猜测并碰巧蒙对了,或者答错的原因并非缺少上下文。 ## 配置没问题但答案仍然不对时 diff --git a/skills/ckb-ccc-playground/SKILL.md b/skills/ckb-ccc-playground/SKILL.md index 9069d38b..8f43b78c 100644 --- a/skills/ckb-ccc-playground/SKILL.md +++ b/skills/ckb-ccc-playground/SKILL.md @@ -94,7 +94,7 @@ Both resolve to the same implementation — pick whichever keeps the script's im 1. **Share button (Nostr)** — fast, no repo needed, good for quick back-and-forth with a colleague. **Not guaranteed permanent** — Nostr relay nodes don't guarantee long-term data retention, so these links can expire. 2. **`?src=` query parameter (raw URL)** — loads code from any publicly reachable raw file URL: `https://live.ckbccc.com/?src=`. -For **stable, long-lived links**, host the script in a GitHub repo and point `?src=` at the `raw.githubusercontent.com` URL — this is exactly how every example in the official `code-examples` gallery is linked (see `ckb-ccc-examples-finder`). The link stays valid as long as the file exists in the repo. +For **stable, long-lived links**, host the script in a GitHub repo and point `?src=` at a raw URL pinned to an immutable commit. Treat every `?src=` source as executable code: inspect it before **Run**, and never run an unreviewed source while the signer is connected to mainnet. --- diff --git a/skills/ckb-ccc-signer-setup/SKILL.md b/skills/ckb-ccc-signer-setup/SKILL.md index 1c87314c..386a99a0 100644 --- a/skills/ckb-ccc-signer-setup/SKILL.md +++ b/skills/ckb-ccc-signer-setup/SKILL.md @@ -65,14 +65,18 @@ function ConnectButton() { 3. **Check connection** — Some signers require `await signer.connect()`. Check with `await signer.isConnected()` if operations fail unexpectedly. 4. **Query data** — `await signer.getRecommendedAddress()`, `await signer.getBalance()`, `for await (const cell of client.findCellsByLock(...))`. 5. **Build and send** — Follow the transaction-composition pattern in `ckb-ccc-transactions`; the pattern is identical regardless of how the signer was created. -6. **Verify** — Log `txHash`; use `client.getTransaction(txHash)` to poll for confirmation. +6. **Verify** — Log `txHash`; use `await client.waitTransaction(txHash, 1)` for confirmation checks. ```typescript import { ccc } from "@ckb-ccc/shell"; // Always load private key from environment — never hardcode const client = new ccc.ClientPublicTestnet(); // or ClientPublicMainnet -const signer = new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!); + +const privateKey = process.env.CKB_PRIVATE_KEY; +if (!privateKey) throw new Error("CKB_PRIVATE_KEY is required"); +const signer = new ccc.SignerCkbPrivateKey(client, privateKey); + await signer.connect(); const address = await signer.getRecommendedAddress(); // "ckt1q..." or "ckb1q..." diff --git a/skills/ckb-ccc-spore/SKILL.md b/skills/ckb-ccc-spore/SKILL.md index 2b53ba73..685bacc1 100644 --- a/skills/ckb-ccc-spore/SKILL.md +++ b/skills/ckb-ccc-spore/SKILL.md @@ -155,18 +155,21 @@ More DOB/0 patterns (image-linked traits via BTCFS/IPFS, programmatic images) an ## Creating and managing Spore NFTs -1. **Create Spore** — `const { tx, id: sporeId } = await spore.createSpore({ signer, data: { contentType: "text/plain", content: bytes } })`. +1. **Create Spore** — `const { tx, id: sporeId } = await ccc.spore.createSpore({ signer, data: { contentType: "text/plain", content: bytes } })`. 2. **If using a Cluster** — Specify `clusterMode: "lockProxy" | "clusterCell" | "skip"` in `createSpore()`. Omitting it when `clusterId` is set throws. 3. **Complete and send** — `await tx.completeInputsByCapacity(signer)`, `await tx.completeFeeBy(signer)`, `await signer.sendTransaction(tx)`. 4. **Save `sporeId`** — It is the Type script args; required for all subsequent transfer/melt operations. -5. **Transfer** — `const { tx } = await spore.transferSpore({ signer, id: sporeId, to: newOwnerLock })`. Then `completeFeeBy` and `sendTransaction`. -6. **Melt (destroy)** — `const { tx } = await spore.meltSpore({ signer, id: sporeId })`. **Irreversible** — permanently destroys the NFT and all on-chain content. +5. **Transfer** — `const { tx } = await ccc.spore.transferSpore({ signer, id: sporeId, to: newOwnerLock })`. Then `completeFeeBy` and `sendTransaction`. +6. **Melt (destroy)** — `const { tx } = await ccc.spore.meltSpore({ signer, id: sporeId })`. **Irreversible** — permanently destroys the NFT and all on-chain content. ```typescript -import { spore } from "@ckb-ccc/spore"; // or ccc.spore from @ckb-ccc/shell +import { ccc } from "`@ckb-ccc/ccc`"; +import { signer } from "`@ckb-ccc/playground`"; // replace with an application signer outside Playground + +const recipientAddr = await signer.getRecommendedAddress(); // replace with the intended recipient // Create a Spore (stores content permanently on-chain) -const { tx, id: sporeId } = await spore.createSpore({ +const { tx, id: sporeId } = await ccc.spore.createSpore({ signer, data: { contentType: "text/plain", @@ -180,12 +183,12 @@ const txHash = await signer.sendTransaction(tx); // Transfer a Spore const { script: newOwner } = await ccc.Address.fromString(recipientAddr, signer.client); -const { tx: transferTx } = await spore.transferSpore({ signer, id: sporeId, to: newOwner }); +const { tx: transferTx } = await ccc.spore.transferSpore({ signer, id: sporeId, to: newOwner }); await transferTx.completeFeeBy(signer); await signer.sendTransaction(transferTx); // Melt (destroy) a Spore — irreversible -const { tx: meltTx } = await spore.meltSpore({ signer, id: sporeId }); +const { tx: meltTx } = await ccc.spore.meltSpore({ signer, id: sporeId }); await meltTx.completeFeeBy(signer); await signer.sendTransaction(meltTx); ``` diff --git a/skills/ckb-ccc-transactions/SKILL.md b/skills/ckb-ccc-transactions/SKILL.md index ba7970d4..00764f25 100644 --- a/skills/ckb-ccc-transactions/SKILL.md +++ b/skills/ckb-ccc-transactions/SKILL.md @@ -67,9 +67,8 @@ const balance = await signer.getBalance(); // total Shannon across all addresses | Symptom / Error | Cause | Fix | |---|---|---| -| `"not enough capacity"` | Insufficient CKB | Fund the address; each output cell needs ≥ 61 CKB | -| `"InsufficientCellCapacity"` | Output cell below minimum | Each basic output needs ≥ 61 CKB capacity; use CCC helpers, not manual calculation | -| Transaction rejected by node | Fee too low or missing | Always call `completeFeeBy` after `completeInputsByCapacity`; omit fee rate arg for automatic rate | +| `"not enough capacity"` | Total input capacity doesn't cover outputs + fee | Fund the address, or confirm `completeInputsByCapacity` is called before `completeFeeBy` so CCC collects enough inputs automatically | +| Transaction rejected by node | Fee too low or missing | Call `completeFeeBy` after `completeInputsByCapacity`; if a second argument (explicit `feeRate`) is passed, check it's not below `1000n` (the node's default `min_fee_rate`) — bump to `2000n`/`4000n` if rejections persist. Omit the argument entirely to let CCC calculate the network rate automatically. | | `addCellDepsOfKnownScripts is not a function` | Wrong import or outdated version | Use `@ckb-ccc/shell` not `@ckb-ccc/core` for backend; update CCC to latest | --- @@ -87,8 +86,8 @@ const balance = await signer.getBalance(); // total Shannon across all addresses ## Checklist (transaction-specific) - [ ] **Transaction order** — `outputs declared → completeInputsByCapacity → completeFeeBy → sendTransaction` -- [ ] **Capacity** — Output cells have ≥ 61 CKB (CCC helpers enforce this; manual calc may not) -- [ ] **Fee rate** — `completeFeeBy` called without explicit rate (automatic) unless custom rate needed +- [ ] **Capacity** — Output capacity is calculated from each output's lock, type script, and data (use CCC helpers, not manual calculation) +- [ ] **Fee rate** — `completeFeeBy` called without explicit rate (automatic) unless a custom rate is needed; if custom, ensure it's ≥ `1000n` (node's `min_fee_rate`), recommend `2000n`. - [ ] **Error handling** — `try/catch` around `sendTransaction` Also check the cross-cutting checklist in `ckb-ccc-fundamentals`. From 0f9e6f99e2fa2da91b72c1e4724c87ecc0821ae4 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Sun, 19 Jul 2026 20:40:55 +0800 Subject: [PATCH 13/15] docs: add validation reminder for CKB_PRIVATE_KEY in signer-setup Add parenthetical note in step 2 to remind readers to validate the environment variable exists before creating the signer, referencing the complete validation logic in the code example. --- skills/ckb-ccc-signer-setup/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/ckb-ccc-signer-setup/SKILL.md b/skills/ckb-ccc-signer-setup/SKILL.md index 386a99a0..ea644c70 100644 --- a/skills/ckb-ccc-signer-setup/SKILL.md +++ b/skills/ckb-ccc-signer-setup/SKILL.md @@ -61,7 +61,7 @@ function ConnectButton() { ## Building a Node.js backend script 1. **Import and connect** — Install `@ckb-ccc/shell`. Create client: `new ccc.ClientPublicTestnet()` or `ClientPublicMainnet()`. -2. **Create signer** — `new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!)`. Never hardcode keys. +2. **Create signer** — `new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!)`. Never hardcode keys. (Validate environment variable exists first—see code example below) 3. **Check connection** — Some signers require `await signer.connect()`. Check with `await signer.isConnected()` if operations fail unexpectedly. 4. **Query data** — `await signer.getRecommendedAddress()`, `await signer.getBalance()`, `for await (const cell of client.findCellsByLock(...))`. 5. **Build and send** — Follow the transaction-composition pattern in `ckb-ccc-transactions`; the pattern is identical regardless of how the signer was created. From 6b3a6eb4c3a56a8b98f0de8b22681c5907c88846 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Sun, 19 Jul 2026 21:03:27 +0800 Subject: [PATCH 14/15] fix: remove invalid backticks from import statements in spore skill Remove Markdown backticks from module specifiers in the code example to fix compilation errors. --- skills/ckb-ccc-spore/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/ckb-ccc-spore/SKILL.md b/skills/ckb-ccc-spore/SKILL.md index 685bacc1..7fb7158b 100644 --- a/skills/ckb-ccc-spore/SKILL.md +++ b/skills/ckb-ccc-spore/SKILL.md @@ -163,8 +163,8 @@ More DOB/0 patterns (image-linked traits via BTCFS/IPFS, programmatic images) an 6. **Melt (destroy)** — `const { tx } = await ccc.spore.meltSpore({ signer, id: sporeId })`. **Irreversible** — permanently destroys the NFT and all on-chain content. ```typescript -import { ccc } from "`@ckb-ccc/ccc`"; -import { signer } from "`@ckb-ccc/playground`"; // replace with an application signer outside Playground +import { ccc } from "@ckb-ccc/ccc"; +import { signer } from "@ckb-ccc/playground"; // replace with an application signer outside Playground const recipientAddr = await signer.getRecommendedAddress(); // replace with the intended recipient From e47572819e92cdc31b8b004fe3f1621ba21aeb68 Mon Sep 17 00:00:00 2001 From: aabbcc Date: Sun, 19 Jul 2026 21:17:42 +0800 Subject: [PATCH 15/15] docs: add language tags to code blocks for MD040 compliance Add or language tags to fenced code blocks in ckb-ccc-fundamentals and ckb-ccc-examples-finder skills to satisfy markdownlint MD040 rule. --- skills/ckb-ccc-examples-finder/SKILL.md | 2 +- skills/ckb-ccc-fundamentals/SKILL.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/skills/ckb-ccc-examples-finder/SKILL.md b/skills/ckb-ccc-examples-finder/SKILL.md index fd849ef8..5a36b375 100644 --- a/skills/ckb-ccc-examples-finder/SKILL.md +++ b/skills/ckb-ccc-examples-finder/SKILL.md @@ -19,7 +19,7 @@ Before writing example code from memory, check whether a working, tested example `docs.ckbccc.com` maintains a tagged gallery of runnable examples, each with a raw source URL and a direct Playground link: -``` +```http GET https://docs.ckbccc.com/en/docs/code-examples.md ``` diff --git a/skills/ckb-ccc-fundamentals/SKILL.md b/skills/ckb-ccc-fundamentals/SKILL.md index 0ba9eae6..cde973e3 100644 --- a/skills/ckb-ccc-fundamentals/SKILL.md +++ b/skills/ckb-ccc-fundamentals/SKILL.md @@ -174,7 +174,7 @@ CCC's source is indexed on DeepWiki (`ckb-devrel/ccc`) and Context7 (`ckb-devrel ### Step 1 — Start with llms.txt for navigation -``` +```http GET https://docs.ckbccc.com/llms.txt ``` @@ -184,7 +184,7 @@ Returns a structured index of all documentation pages with titles and URLs. Use Append `.md` to any docs URL to get clean Markdown without HTML boilerplate: -``` +```http # Example: fetch the Cell model concept page GET https://docs.ckbccc.com/en/docs/concepts/cell-model.md @@ -196,7 +196,7 @@ Alternatively, send `Accept: text/markdown` and the server will serve Markdown a ### Step 3 — Use llms-full.txt for broad questions -``` +```http GET https://docs.ckbccc.com/llms-full.txt ``` @@ -218,13 +218,13 @@ Contains the full text of all English documentation pages concatenated. Use when ### For API signatures and types — use the API reference (fallback only) -``` +```text https://api.ckbccc.com ``` TypeDoc-generated reference for all `@ckb-ccc/*` packages. Only reach for this when no DeepWiki or Context7 MCP tool is available this session — see "Step 0" above, which is the preferred path for any exact method signature, interface field, or enum value. When you do use this fallback, search by class or method name directly in the URL: -``` +```text https://api.ckbccc.com/modules/_ckb-ccc_connector-react https://api.ckbccc.com/functions/_ckb-ccc_connector-react.index.useccc https://api.ckbccc.com/classes/_ckb-ccc_core.index.transaction