diff --git a/.changeset/fruity-drinks-kick.md b/.changeset/fruity-drinks-kick.md new file mode 100644 index 000000000..abb409ec7 --- /dev/null +++ b/.changeset/fruity-drinks-kick.md @@ -0,0 +1,7 @@ +--- +"@ckb-ccc/shell": minor +"@ckb-ccc/did-ckb": patch +--- + +feat(did-ckb): add did-ckb package for basic did operations + \ No newline at end of file diff --git a/.changeset/poor-days-guess.md b/.changeset/poor-days-guess.md new file mode 100644 index 000000000..d6269460c --- /dev/null +++ b/.changeset/poor-days-guess.md @@ -0,0 +1,6 @@ +--- +"@ckb-ccc/core": minor +--- + +feat(core): add known script did ckb + \ No newline at end of file diff --git a/packages/core/src/client/clientPublicMainnet.advanced.ts b/packages/core/src/client/clientPublicMainnet.advanced.ts index 979b0955c..bdcec4cc1 100644 --- a/packages/core/src/client/clientPublicMainnet.advanced.ts +++ b/packages/core/src/client/clientPublicMainnet.advanced.ts @@ -321,6 +321,29 @@ export const MAINNET_SCRIPTS: Record = }, ], }, + [KnownScript.DidCkb]: { + codeHash: + "0x4a06164dc34dccade5afe3e847a97b6db743e79f5477fa3295acf02849c5984a", + hashType: "type", + cellDeps: [ + { + cellDep: { + outPoint: { + txHash: + "0xe2f74c56cdc610d2b9fe898a96a80118845f5278605d7f9ad535dad69ae015bf", + index: 0, + }, + depType: "code", + }, + type: { + codeHash: + "0x00000000000000000000000000000000000000000000000000545950455f4944", + args: "0x55573ef6d78e3ca75170ff476176732309a8b31efe94320a954ded3d75c2cb18", + hashType: "type", + }, + }, + ], + }, [KnownScript.AlwaysSuccess]: { codeHash: "0x3b521cc4b552f109d092d8cc468a8048acb53c5952dbe769d2b2f9cf6e47f7f1", diff --git a/packages/core/src/client/clientPublicTestnet.advanced.ts b/packages/core/src/client/clientPublicTestnet.advanced.ts index be811c7eb..9453d2523 100644 --- a/packages/core/src/client/clientPublicTestnet.advanced.ts +++ b/packages/core/src/client/clientPublicTestnet.advanced.ts @@ -333,6 +333,29 @@ export const TESTNET_SCRIPTS: Record = }, ], }, + [KnownScript.DidCkb]: { + codeHash: + "0x510150477b10d6ab551a509b71265f3164e9fd4137fcb5a4322f49f03092c7c5", + hashType: "type", + cellDeps: [ + { + cellDep: { + outPoint: { + txHash: + "0x0e7a830e2d5ebd05cd45a55f93f94559edea0ef1237b7233f49f7facfb3d6a6c", + index: 0, + }, + depType: "code", + }, + type: { + codeHash: + "0x00000000000000000000000000000000000000000000000000545950455f4944", + args: "0x3c27695173b888ed44ddf36f901789014384ad6c05a9137f3db9a0779c141c35", + hashType: "type", + }, + }, + ], + }, [KnownScript.AlwaysSuccess]: { codeHash: "0x3b521cc4b552f109d092d8cc468a8048acb53c5952dbe769d2b2f9cf6e47f7f1", diff --git a/packages/core/src/client/knownScript.ts b/packages/core/src/client/knownScript.ts index 2171b90b4..90a1546fe 100644 --- a/packages/core/src/client/knownScript.ts +++ b/packages/core/src/client/knownScript.ts @@ -15,6 +15,7 @@ export enum KnownScript { OmniLock = "OmniLock", NostrLock = "NostrLock", UniqueType = "UniqueType", + DidCkb = "DidCkb", // ckb-proxy-locks https://github.com/ckb-devrel/ckb-proxy-locks AlwaysSuccess = "AlwaysSuccess", diff --git a/packages/did-ckb/.npmignore b/packages/did-ckb/.npmignore new file mode 100644 index 000000000..7a88408aa --- /dev/null +++ b/packages/did-ckb/.npmignore @@ -0,0 +1,21 @@ +node_modules/ +misc/ + +*test.js +*test.ts +*test.d.ts +*test.d.ts.map +*spec.js +*spec.ts +*spec.d.ts +*spec.d.ts.map + +tsconfig.json +tsconfig.*.json +eslint.config.mjs +.prettierrc +.prettierignore + +tsconfig.tsbuildinfo +tsconfig.*.tsbuildinfo +.github/ diff --git a/packages/did-ckb/.prettierignore b/packages/did-ckb/.prettierignore new file mode 100644 index 000000000..aef5d239c --- /dev/null +++ b/packages/did-ckb/.prettierignore @@ -0,0 +1,15 @@ +node_modules/ + +dist/ +dist.commonjs/ + +.npmignore +.prettierrc +tsconfig.json +eslint.config.mjs +prettier.config.* + +tsconfig.tsbuildinfo +.github/ + +CHANGELOG.md diff --git a/packages/did-ckb/README.md b/packages/did-ckb/README.md new file mode 100644 index 000000000..1423faaf1 --- /dev/null +++ b/packages/did-ckb/README.md @@ -0,0 +1,41 @@ +

+ + Logo + +

+ +

+ CCC's Support for DID CKB +

+ +

+ NPM Version + GitHub commit activity + GitHub last commit + GitHub branch check runs + Playground + App + Docs +

+ +

+ CCC - CKBers' Codebase is a one-stop solution for your CKB JS/TS ecosystem development. +
+ Empower yourself with CCC to discover the unlimited potential of CKB. +
+ Interoperate with wallets from different chain ecosystems. +
+ Fully enabling CKB's Turing completeness and cryptographic freedom power. +

+ +

+ Read more about CCC on our website or GitHub Repo. +

diff --git a/packages/did-ckb/eslint.config.mjs b/packages/did-ckb/eslint.config.mjs new file mode 100644 index 000000000..b6132c277 --- /dev/null +++ b/packages/did-ckb/eslint.config.mjs @@ -0,0 +1,62 @@ +// @ts-check + +import eslint from "@eslint/js"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import tseslint from "typescript-eslint"; + +import { dirname } from "path"; +import { fileURLToPath } from "url"; + +export default [ + ...tseslint.config({ + files: ["**/*.ts"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ], + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }], + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/only-throw-error": [ + "error", + { + allowThrowingAny: true, + allowThrowingUnknown: true, + allowRethrowing: true, + }, + ], + "@typescript-eslint/prefer-promise-reject-errors": [ + "error", + { + allowThrowingAny: true, + allowThrowingUnknown: true, + }, + ], + "no-empty": "off", + "prefer-const": [ + "error", + { ignoreReadBeforeAssign: true, destructuring: "all" }, + ], + }, + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: dirname(fileURLToPath(import.meta.url)), + }, + }, + }), + eslintPluginPrettierRecommended, +]; diff --git a/packages/did-ckb/misc/basedirs/dist.commonjs/package.json b/packages/did-ckb/misc/basedirs/dist.commonjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/packages/did-ckb/misc/basedirs/dist.commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/did-ckb/misc/basedirs/dist/package.json b/packages/did-ckb/misc/basedirs/dist/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/packages/did-ckb/misc/basedirs/dist/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/did-ckb/package.json b/packages/did-ckb/package.json new file mode 100644 index 000000000..61799e5b8 --- /dev/null +++ b/packages/did-ckb/package.json @@ -0,0 +1,52 @@ +{ + "name": "@ckb-ccc/did-ckb", + "version": "0.0.0", + "description": "UDT", + "author": "Hanssen0 ", + "license": "MIT", + "private": false, + "homepage": "https://github.com/ckb-devrel/ccc", + "repository": { + "type": "git", + "url": "git://github.com/ckb-devrel/ccc.git" + }, + "main": "./dist.commonjs/index.js", + "module": "./dist/index.js", + "exports": { + ".": { + "import": "./dist/index.js", + "default": "./dist.commonjs/index.js" + }, + "./advanced": { + "import": "./dist/advanced.js", + "default": "./dist.commonjs/advanced.js" + } + }, + "scripts": { + "test": "jest", + "build": "rimraf ./dist && rimraf ./dist.commonjs && tsc && tsc --project tsconfig.commonjs.json && copyfiles -u 2 misc/basedirs/**/* .", + "lint": "eslint ./src", + "format": "prettier --write . && eslint --fix ./src" + }, + "devDependencies": { + "@eslint/js": "^9.34.0", + "@types/node": "^24.3.0", + "copyfiles": "^2.4.1", + "eslint": "^9.34.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "prettier": "^3.6.2", + "prettier-plugin-organize-imports": "^4.2.0", + "rimraf": "^6.0.1", + "typescript": "^5.9.2", + "typescript-eslint": "^8.41.0" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@ckb-ccc/core": "workspace:*", + "@ipld/dag-cbor": "^9.2.5" + }, + "packageManager": "pnpm@10.8.1" +} diff --git a/packages/did-ckb/prettier.config.cjs b/packages/did-ckb/prettier.config.cjs new file mode 100644 index 000000000..5e1810363 --- /dev/null +++ b/packages/did-ckb/prettier.config.cjs @@ -0,0 +1,11 @@ +/** + * @see https://prettier.io/docs/configuration + * @type {import("prettier").Config} + */ +const config = { + singleQuote: false, + trailingComma: "all", + plugins: [require.resolve("prettier-plugin-organize-imports")], +}; + +module.exports = config; diff --git a/packages/did-ckb/src/barrel.ts b/packages/did-ckb/src/barrel.ts new file mode 100644 index 000000000..224fa636d --- /dev/null +++ b/packages/did-ckb/src/barrel.ts @@ -0,0 +1,4 @@ +export * from "./codec.js"; +export * from "./create.js"; +export * from "./destroy.js"; +export * from "./transfer.js"; diff --git a/packages/did-ckb/src/codec.ts b/packages/did-ckb/src/codec.ts new file mode 100644 index 000000000..865a62d05 --- /dev/null +++ b/packages/did-ckb/src/codec.ts @@ -0,0 +1,149 @@ +import { ccc } from "@ckb-ccc/core"; +import { decode as cborDecode, encode as cborEncode } from "@ipld/dag-cbor"; + +export type DidCkbDataV1Like = { + document: object; + localId?: string | null; +}; +@ccc.mol.codec( + ccc.mol + .table({ + document: ccc.mol.Bytes, + localId: ccc.mol.StringOpt, + }) + .map({ + inMap: (data: DidCkbDataV1Like) => ({ + ...data, + document: ccc.hexFrom(cborEncode(data.document)), + }), + outMap: (data) => ({ + ...data, + document: cborDecode(ccc.bytesFrom(data.document)), + }), + }), +) +export class DidCkbDataV1 extends ccc.mol.Entity.Base< + DidCkbDataV1Like, + DidCkbDataV1 +>() { + constructor( + public document: object, + public localId?: string, + ) { + super(); + } + + static from(data: DidCkbDataV1Like): DidCkbDataV1 { + if (data instanceof DidCkbDataV1) { + return data; + } + + return new DidCkbDataV1(data.document, data.localId ?? undefined); + } +} + +export type DidCkbDataLike = { + type?: "v1" | null; + value: DidCkbDataV1Like; +}; +@ccc.mol.codec( + ccc.mol.union({ + v1: DidCkbDataV1, + }), +) +export class DidCkbData extends ccc.mol.Entity.Base< + DidCkbDataLike, + DidCkbData +>() { + constructor( + public type: "v1", + public value: DidCkbDataV1, + ) { + super(); + } + + static from(data: DidCkbDataLike): DidCkbData { + if (data instanceof DidCkbData) { + return data; + } + return new DidCkbData(data.type ?? "v1", DidCkbDataV1.from(data.value)); + } + + static fromV1( + data: DidCkbDataV1Like, + ): DidCkbData & { type: "v1"; value: DidCkbDataV1 } { + return new DidCkbData("v1", DidCkbDataV1.from(data)); + } +} + +export type PlcAuthorizationLike = { + history: object[]; + sig: ccc.HexLike; + rotationKeyIndices: ccc.NumLike[]; +}; +@ccc.mol.codec( + ccc.mol + .table({ + history: ccc.mol.BytesVec, + sig: ccc.mol.Bytes, + rotationKeyIndices: ccc.mol.Uint8Vec, + }) + + .map({ + inMap: (data: PlcAuthorizationLike) => ({ + ...data, + history: data.history.map((h) => ccc.hexFrom(cborEncode(h))), + }), + outMap: (data) => ({ + ...data, + history: data.history.map((h) => cborDecode(ccc.bytesFrom(h))), + }), + }), +) +export class PlcAuthorization extends ccc.mol.Entity.Base< + PlcAuthorizationLike, + PlcAuthorization +>() { + constructor( + public history: object[], + public sig: ccc.Hex, + public rotationKeyIndices: ccc.Num[], + ) { + super(); + } + + static from(data: PlcAuthorizationLike): PlcAuthorization { + if (data instanceof PlcAuthorization) { + return data; + } + return new PlcAuthorization( + data.history, + ccc.hexFrom(data.sig), + data.rotationKeyIndices.map(ccc.numFrom), + ); + } +} + +export type DidCkbWitnessLike = { + localIdAuthorization: PlcAuthorizationLike; +}; +@ccc.mol.codec( + ccc.mol.table({ + localIdAuthorization: PlcAuthorization, + }), +) +export class DidCkbWitness extends ccc.mol.Entity.Base< + DidCkbWitnessLike, + DidCkbWitness +>() { + constructor(public localIdAuthorization: PlcAuthorization) { + super(); + } + + static from(data: DidCkbWitnessLike): DidCkbWitness { + if (data instanceof DidCkbWitness) { + return data; + } + return new DidCkbWitness(PlcAuthorization.from(data.localIdAuthorization)); + } +} diff --git a/packages/did-ckb/src/create.ts b/packages/did-ckb/src/create.ts new file mode 100644 index 000000000..3cc0c3966 --- /dev/null +++ b/packages/did-ckb/src/create.ts @@ -0,0 +1,40 @@ +import { ccc } from "@ckb-ccc/core"; +import { DidCkbData, DidCkbDataLike } from "./codec"; + +export async function createDid({ + signer, + data, + tx: txLike, +}: { + signer: ccc.Signer; + data: DidCkbDataLike; + tx?: ccc.TransactionLike | null; +}): Promise<{ + tx: ccc.Transaction; + didId: ccc.Hex; + index: number; +}> { + const tx = ccc.Transaction.from(txLike ?? {}); + + await tx.completeInputsAtLeastOne(signer); + const didId = ccc.hashTypeId(tx.inputs[0], tx.outputs.length); + + const len = tx.addOutput({ + cellOutput: { + type: await ccc.Script.fromKnownScript( + signer.client, + ccc.KnownScript.DidCkb, + didId, + ), + lock: (await signer.getRecommendedAddressObj()).script, + }, + outputData: DidCkbData.from(data).toBytes(), + }); + + await tx.addCellDepsOfKnownScripts(signer.client, ccc.KnownScript.DidCkb); + return { + tx, + didId, + index: len - 1, + }; +} diff --git a/packages/did-ckb/src/destroy.ts b/packages/did-ckb/src/destroy.ts new file mode 100644 index 000000000..4b290dac9 --- /dev/null +++ b/packages/did-ckb/src/destroy.ts @@ -0,0 +1,25 @@ +import { ccc } from "@ckb-ccc/core"; + +export async function destroyDid({ + client, + didId, + tx: txLike, +}: { + client: ccc.Client; + didId: ccc.HexLike; + tx?: ccc.TransactionLike | null; +}): Promise<{ tx: ccc.Transaction; index: number }> { + const tx = ccc.Transaction.from(txLike ?? {}); + + const didCell = await client.findSingletonCellByType( + await ccc.Script.fromKnownScript(client, ccc.KnownScript.DidCkb, didId), + ); + if (!didCell) { + throw new Error(`DID CKB ${ccc.hexFrom(didId)} not found`); + } + + const len = tx.addInput(didCell); + + await tx.addCellDepsOfKnownScripts(client, ccc.KnownScript.DidCkb); + return { tx, index: len - 1 }; +} diff --git a/packages/did-ckb/src/index.ts b/packages/did-ckb/src/index.ts new file mode 100644 index 000000000..6798ac0ea --- /dev/null +++ b/packages/did-ckb/src/index.ts @@ -0,0 +1 @@ +export * as didCkb from "./barrel.js"; diff --git a/packages/did-ckb/src/transfer.ts b/packages/did-ckb/src/transfer.ts new file mode 100644 index 000000000..289a4347a --- /dev/null +++ b/packages/did-ckb/src/transfer.ts @@ -0,0 +1,42 @@ +import { ccc } from "@ckb-ccc/core"; + +export async function transferDid({ + client, + didId, + receiver, + tx: txLike, +}: { + client: ccc.Client; + didId: ccc.HexLike; + receiver: ccc.ScriptLike; + tx?: ccc.TransactionLike | null; +}): Promise<{ + tx: ccc.Transaction; + inIndex: number; + outIndex: number; +}> { + const tx = ccc.Transaction.from(txLike ?? {}); + + const didCell = await client.findSingletonCellByType( + await ccc.Script.fromKnownScript(client, ccc.KnownScript.DidCkb, didId), + ); + if (!didCell) { + throw new Error(`DID CKB ${ccc.hexFrom(didId)} not found`); + } + + const inLen = tx.addInput(didCell); + const outLen = tx.addOutput({ + ...didCell, + cellOutput: { + ...didCell.cellOutput, + lock: ccc.Script.from(receiver), + }, + }); + + await tx.addCellDepsOfKnownScripts(client, ccc.KnownScript.DidCkb); + return { + tx, + inIndex: inLen - 1, + outIndex: outLen - 1, + }; +} diff --git a/packages/did-ckb/tsconfig.base.json b/packages/did-ckb/tsconfig.base.json new file mode 100644 index 000000000..7e5ac952b --- /dev/null +++ b/packages/did-ckb/tsconfig.base.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es2020", + "incremental": true, + "allowJs": true, + "importHelpers": false, + "declaration": true, + "declarationMap": true, + "experimentalDecorators": true, + "useDefineForClassFields": false, + "esModuleInterop": true, + "strict": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "strictNullChecks": true, + "alwaysStrict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/packages/did-ckb/tsconfig.commonjs.json b/packages/did-ckb/tsconfig.commonjs.json new file mode 100644 index 000000000..76a25e98b --- /dev/null +++ b/packages/did-ckb/tsconfig.commonjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist.commonjs" + } +} diff --git a/packages/did-ckb/tsconfig.json b/packages/did-ckb/tsconfig.json new file mode 100644 index 000000000..df22faeca --- /dev/null +++ b/packages/did-ckb/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "./dist", + } +} diff --git a/packages/did-ckb/typedoc.json b/packages/did-ckb/typedoc.json new file mode 100644 index 000000000..5cdb8befe --- /dev/null +++ b/packages/did-ckb/typedoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts"], + "extends": ["../../typedoc.base.json"], + "name": "@ckb-ccc did-ckb" +} diff --git a/packages/examples/package.json b/packages/examples/package.json index 756bf2d1d..bac19e45d 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -31,7 +31,9 @@ }, "dependencies": { "@ckb-ccc/ccc": "workspace:*", - "@ckb-ccc/playground": "file:src/playground" + "@ckb-ccc/playground": "file:src/playground", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" }, "packageManager": "pnpm@10.8.1" } diff --git a/packages/examples/src/createDid.ts b/packages/examples/src/createDid.ts new file mode 100644 index 000000000..98f57e5cd --- /dev/null +++ b/packages/examples/src/createDid.ts @@ -0,0 +1,20 @@ +import { ccc } from "@ckb-ccc/ccc"; +import { render, signer } from "@ckb-ccc/playground"; + +// Construct create did tx +const { tx } = await ccc.didCkb.createDid({ + signer, + data: { value: { document: {} } }, +}); + +// Complete missing parts: Fill inputs +await tx.completeInputsByCapacity(signer); +await render(tx); + +// Complete missing parts: Pay fee +await tx.completeFeeBy(signer); +await render(tx); + +// Sign and send the transaction +const txHash = await signer.sendTransaction(tx); +console.log(`Transaction ${txHash} sent`); diff --git a/packages/examples/src/createDidWithLocalId.ts b/packages/examples/src/createDidWithLocalId.ts new file mode 100644 index 000000000..fb01c61c9 --- /dev/null +++ b/packages/examples/src/createDidWithLocalId.ts @@ -0,0 +1,63 @@ +import { ccc } from "@ckb-ccc/ccc"; +import { render, signer } from "@ckb-ccc/playground"; +import { secp256k1 } from "@noble/curves/secp256k1"; +import { sha256 } from "@noble/hashes/sha2"; + +// From https://github.com/bluesky-social/atproto/blob/main/packages/crypto +function plcSign(key: ccc.BytesLike, msg: ccc.BytesLike): ccc.Bytes { + const msgHash = sha256(ccc.bytesFrom(msg)); + const sig = secp256k1.sign(msgHash, ccc.bytesFrom(key), { lowS: true }); + return sig.toBytes("compact"); +} + +// Construct create did tx +const { tx } = await ccc.didCkb.createDid({ + signer, + data: { + value: { document: {}, localId: "did:plc:yunkr6vorfgzmvzeoofbkhq5" }, + }, +}); + +// Complete missing parts: Fill inputs +await tx.completeInputsByCapacity(signer); +await render(tx); + +// Complete missing parts: Pay fee +await tx.completeFeeBy(signer); +await render(tx); + +// Authorize the transaction with the rotation key +const rotationKey = + "0x806d1925698097c64bc70f629e25b91b48a15eee4e492bb239402cee85356a10"; +const witness = ccc.didCkb.DidCkbWitness.from({ + localIdAuthorization: { + history: [ + { + type: "plc_operation", + verificationMethods: { + atproto: "did:key:zQ3shn3qejTEyEiokszFc4MWEqbdAwyj2XR1oS2AuXKvEBTuN", + }, + rotationKeys: [ + "did:key:zQ3shqtXEdagupBhLzL2vFUACfdVjDEvciip79uY8iHBuu7FD", + "did:key:zDnaefn5fMKvoZ1n4vyxJ9npjWE5P3D8GkM9zNqaGbLqdDrtX", + ], + alsoKnownAs: ["at://alice.example.com"], + services: { + atproto_pds: { + type: "AtprotoPersonalDataServer", + endpoint: "https://example.com", + }, + }, + prev: null, + sig: "2ySrMKwAQ8j_7HlJlNdE9kXFXG6VAGzy0s4P5O12UuMQqUgDHlAe3PQza5zWxIi6TC9K3K8ghmypfhDyJm8LuQ", + }, + ], + rotationKeyIndices: [0n, 0n], + sig: plcSign(rotationKey, tx.hash()), + }, +}); +tx.setWitnessArgsAt(0, ccc.WitnessArgs.from({ outputType: witness.toBytes() })); + +// Sign and send the transaction +const txHash = await signer.sendTransaction(tx); +console.log(`Transaction ${txHash} sent`); diff --git a/packages/examples/src/destroyDid.ts b/packages/examples/src/destroyDid.ts new file mode 100644 index 000000000..5cad9a3c5 --- /dev/null +++ b/packages/examples/src/destroyDid.ts @@ -0,0 +1,31 @@ +import { ccc } from "@ckb-ccc/ccc"; +import { render, signer } from "@ckb-ccc/playground"; + +// === Create a did first === +// Check https://live.ckbccc.com/?src=https://raw.githubusercontent.com/ckb-devrel/ccc/refs/heads/master/packages/examples/src/createDid.ts for the full example +const { tx: createTx, didId } = await ccc.didCkb.createDid({ + signer, + data: { + value: { document: {} }, + }, +}); +await createTx.completeFeeBy(signer); +await render(createTx); +const createTxHash = await signer.sendTransaction(createTx); +console.log(`Transaction ${createTxHash} sent`); +// === Create a did first === + +// Construct destroy did tx +const { tx } = await ccc.didCkb.destroyDid({ client: signer.client, didId }); + +// Complete missing parts: Fill inputs +await tx.completeInputsByCapacity(signer); +await render(tx); + +// Complete missing parts: Pay fee +await tx.completeFeeBy(signer); +await render(tx); + +// Sign and send the transaction +const txHash = await signer.sendTransaction(tx); +console.log(`Transaction ${txHash} sent`); diff --git a/packages/examples/src/transferDid.ts b/packages/examples/src/transferDid.ts new file mode 100644 index 000000000..31189e6f5 --- /dev/null +++ b/packages/examples/src/transferDid.ts @@ -0,0 +1,39 @@ +import { ccc } from "@ckb-ccc/ccc"; +import { render, signer } from "@ckb-ccc/playground"; + +// === Create a did first === +// Check https://live.ckbccc.com/?src=https://raw.githubusercontent.com/ckb-devrel/ccc/refs/heads/master/packages/examples/src/createDid.ts for the full example +const { tx: createTx, didId } = await ccc.didCkb.createDid({ + signer, + data: { + value: { document: {} }, + }, +}); +await createTx.completeFeeBy(signer); +await render(createTx); +const createTxHash = await signer.sendTransaction(createTx); +console.log(`Transaction ${createTxHash} sent`); +// === Create a did first === + +// The receiver is the signer itself on mainnet +const receiver = (await signer.getRecommendedAddressObj()).script; +console.log(receiver); + +// Construct transfer did tx +const { tx } = await ccc.didCkb.transferDid({ + client: signer.client, + didId, + receiver, +}); + +// Complete missing parts: Fill inputs +await tx.completeInputsByCapacity(signer); +await render(tx); + +// Complete missing parts: Pay fee +await tx.completeFeeBy(signer); +await render(tx); + +// Sign and send the transaction +const txHash = await signer.sendTransaction(tx); +console.log(`Transaction ${txHash} sent`); diff --git a/packages/playground/package.json b/packages/playground/package.json index ecbfedab8..1959e46a0 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -16,6 +16,8 @@ "@monaco-editor/react": "^4.7.0", "@nervina-labs/dob-render": "^0.2.5", "@next/third-parties": "^15.5.2", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", "@shikijs/monaco": "^3.12.0", "axios": "^1.11.0", "bech32": "^2.0.0", diff --git a/packages/playground/src/app/components/Editor.tsx b/packages/playground/src/app/components/Editor.tsx index be4502c4f..d0aaca742 100644 --- a/packages/playground/src/app/components/Editor.tsx +++ b/packages/playground/src/app/components/Editor.tsx @@ -5,23 +5,48 @@ import { editor } from "monaco-editor"; import { useEffect, useRef, useState } from "react"; import { createHighlighter } from "shiki"; -const ReactSource = require.context( - "!!raw-loader!../../../node_modules/@types/react", - true, - /^\.\/(.*\.d\.ts|package.json)$/, -); - -const CCCSource = require.context( - "!!raw-loader!../../../../", - true, - /^\.\/[^\/]*\/(dist\.commonjs\/.*\.d\.ts|package.json)$/, -); - -const DobRenderSource = require.context( - "!!raw-loader!../../../node_modules/@nervina-labs/dob-render/dist", - true, - /^\.\/.*\.d\.ts$/, -); +const EXTRA_SOURCES = [ + { + files: require.context( + "!!raw-loader!../../../node_modules/@types/react", + true, + /^\.\/(.*\.d\.ts|package.json)$/, + ), + name: "@types/react", + }, + { + files: require.context( + "!!raw-loader!../../../../", + true, + /^\.\/[^\/]*\/(dist\.commonjs\/.*\.d\.ts|package.json)$/, + ), + name: "@ckb-ccc", + }, + { + files: require.context( + "!!raw-loader!../../../node_modules/@nervina-labs/dob-render/dist", + true, + /^\.\/(.*\.d\.ts|package.json)$/, + ), + name: "@nervina-labs/dob-render", + }, + { + files: require.context( + "!!raw-loader!../../../node_modules/@noble/hashes", + true, + /^\.\/(.*\.d\.ts|package.json)$/, + ), + name: "@noble/hashes", + }, + { + files: require.context( + "!!raw-loader!../../../node_modules/@noble/curves", + true, + /^\.\/(.*\.d\.ts|package.json)$/, + ), + name: "@noble/curves", + }, +]; export function Editor({ value, @@ -120,25 +145,13 @@ export function Editor({ ], }); - ReactSource.keys().forEach((key: string) => { - monaco.languages.typescript.typescriptDefaults.addExtraLib( - ReactSource(key).default, - "file:///node_modules/@types/react/" + key.replace("./", ""), - ); - }); - - CCCSource.keys().forEach((key: string) => { - monaco.languages.typescript.typescriptDefaults.addExtraLib( - CCCSource(key).default, - "file:///node_modules/@ckb-ccc/" + key.replace("./", ""), - ); - }); - - DobRenderSource.keys().forEach((key: string) => { - monaco.languages.typescript.typescriptDefaults.addExtraLib( - DobRenderSource(key).default, - `file:///node_modules/@nervina-labs/dob-render/${key.replace("./", "")}`, - ); + EXTRA_SOURCES.forEach(({ files, name }) => { + files.keys().forEach((key: string) => { + monaco.languages.typescript.typescriptDefaults.addExtraLib( + files(key).default, + `file:///node_modules/${name}/${key.replace("./", "")}`, + ); + }); }); monaco.languages.typescript.typescriptDefaults.addExtraLib( diff --git a/packages/playground/src/app/execute/index.tsx b/packages/playground/src/app/execute/index.tsx index 8f517f269..81cced3a7 100644 --- a/packages/playground/src/app/execute/index.tsx +++ b/packages/playground/src/app/execute/index.tsx @@ -2,6 +2,8 @@ import * as cccLib from "@ckb-ccc/ccc"; import * as cccAdvancedLib from "@ckb-ccc/ccc/advanced"; import { ccc } from "@ckb-ccc/connector-react"; import * as dobRenderLib from "@nervina-labs/dob-render"; +import * as secp256k1Lib from "@noble/curves/secp256k1"; +import * as sha2Lib from "@noble/hashes/sha2"; import * as React from "react"; import ts from "typescript"; import { formatTimestamp } from "../utils"; @@ -59,6 +61,8 @@ export async function execute( const exports = {}; const require = (path: string) => { const lib = { + "@noble/curves/secp256k1": secp256k1Lib, + "@noble/hashes/sha2": sha2Lib, "@ckb-ccc/core": cccLib, "@ckb-ccc/core/advanced": cccAdvancedLib, "@ckb-ccc/ccc": cccLib, diff --git a/packages/shell/package.json b/packages/shell/package.json index c8f9e8bae..805f21c35 100644 --- a/packages/shell/package.json +++ b/packages/shell/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "@ckb-ccc/core": "workspace:*", + "@ckb-ccc/did-ckb": "workspace:*", "@ckb-ccc/spore": "workspace:*", "@ckb-ccc/ssri": "workspace:*", "@ckb-ccc/udt": "workspace:*" diff --git a/packages/shell/src/barrel.ts b/packages/shell/src/barrel.ts index 9cca15d65..c0ad87ed2 100644 --- a/packages/shell/src/barrel.ts +++ b/packages/shell/src/barrel.ts @@ -1,4 +1,5 @@ export * from "@ckb-ccc/core/barrel"; +export { didCkb } from "@ckb-ccc/did-ckb"; export { spore } from "@ckb-ccc/spore"; export { ssri } from "@ckb-ccc/ssri"; export { udt } from "@ckb-ccc/udt"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa2ac90a..6245774f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -404,6 +404,49 @@ importers: specifier: ^5.9.2 version: 5.9.2 + packages/did-ckb: + dependencies: + '@ckb-ccc/core': + specifier: workspace:* + version: link:../core + '@ipld/dag-cbor': + specifier: ^9.2.5 + version: 9.2.5 + devDependencies: + '@eslint/js': + specifier: ^9.34.0 + version: 9.34.0 + '@types/node': + specifier: ^24.3.0 + version: 24.3.0 + copyfiles: + specifier: ^2.4.1 + version: 2.4.1 + eslint: + specifier: ^9.34.0 + version: 9.34.0(jiti@2.5.1) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.34.0(jiti@2.5.1)) + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1))(prettier@3.6.2) + prettier: + specifier: ^3.6.2 + version: 3.6.2 + prettier-plugin-organize-imports: + specifier: ^4.2.0 + version: 4.2.0(prettier@3.6.2)(typescript@5.9.2) + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + typescript-eslint: + specifier: ^8.41.0 + version: 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + packages/docs: dependencies: '@docusaurus/core': @@ -486,6 +529,12 @@ importers: '@ckb-ccc/playground': specifier: file:src/playground version: playground@file:packages/examples/src/playground + '@noble/curves': + specifier: ^1.9.7 + version: 1.9.7 + '@noble/hashes': + specifier: ^1.8.0 + version: 1.8.0 devDependencies: '@eslint/js': specifier: ^9.34.0 @@ -828,6 +877,12 @@ importers: '@next/third-parties': specifier: ^15.5.2 version: 15.5.2(next@16.0.10(@babel/core@7.28.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + '@noble/curves': + specifier: ^1.9.7 + version: 1.9.7 + '@noble/hashes': + specifier: ^1.8.0 + version: 1.8.0 '@shikijs/monaco': specifier: ^3.12.0 version: 3.12.0 @@ -953,6 +1008,9 @@ importers: '@ckb-ccc/core': specifier: workspace:* version: link:../core + '@ckb-ccc/did-ckb': + specifier: workspace:* + version: link:../did-ckb '@ckb-ccc/spore': specifier: workspace:* version: link:../spore @@ -3181,6 +3239,10 @@ packages: '@types/node': optional: true + '@ipld/dag-cbor@9.2.5': + resolution: {integrity: sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==} + engines: {node: '>=16.0.0', npm: '>=7.0.0'} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -5142,6 +5204,10 @@ packages: caniuse-lite@1.0.30001737: resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==} + cborg@4.3.2: + resolution: {integrity: sha512-l+QzebEAG0vb09YKkaOrMi2zmm80UNjmbvocMIeW5hO7JOXWdrQ/H49yOKfYX0MBgrj/KWgatBnEgRXyNyKD+A==} + hasBin: true + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -7998,6 +8064,9 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + multiformats@13.4.2: + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} + mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -13436,6 +13505,11 @@ snapshots: optionalDependencies: '@types/node': 24.3.0 + '@ipld/dag-cbor@9.2.5': + dependencies: + cborg: 4.3.2 + multiformats: 13.4.2 + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -15790,6 +15864,8 @@ snapshots: caniuse-lite@1.0.30001737: {} + cborg@4.3.2: {} + ccount@2.0.1: {} chai@5.3.3: @@ -19544,6 +19620,8 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 + multiformats@13.4.2: {} + mute-stream@2.0.0: {} nanoassert@2.0.0: {} diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 515df9321..45425d8f4 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -7,6 +7,7 @@ const config = { "packages/ssri", "packages/udt", "packages/spore", + "packages/did-ckb", "packages/shell", "packages/ccc", "packages/connector",