From 81ca3713edd3c74968e391e48e9a36978435c45f Mon Sep 17 00:00:00 2001 From: Hanssen0 <0@hanssen0.com> Date: Wed, 1 Jul 2026 08:10:24 +0800 Subject: [PATCH] feat(co-build): new CoBuild package --- .changeset/empty-schools-return.md | 6 + .changeset/odd-experts-win.md | 6 + packages/co-build/.npmignore | 21 + packages/co-build/.prettierignore | 15 + packages/co-build/README.md | 44 ++ packages/co-build/eslint.config.mjs | 62 +++ .../misc/basedirs/dist.commonjs/package.json | 3 + .../co-build/misc/basedirs/dist/package.json | 3 + packages/co-build/package.json | 54 +++ packages/co-build/prettier.config.cjs | 11 + packages/co-build/src/barrel.ts | 2 + packages/co-build/src/coBuild.test.ts | 168 ++++++++ packages/co-build/src/coBuild.ts | 387 ++++++++++++++++++ packages/co-build/src/codec/buildingPacket.ts | 166 ++++++++ packages/co-build/src/codec/index.ts | 2 + packages/co-build/src/codec/witnessLayout.ts | 66 +++ packages/co-build/src/index.ts | 2 + packages/co-build/tsconfig.json | 25 ++ packages/co-build/tsdown.config.mts | 41 ++ packages/co-build/typedoc.json | 6 + packages/core/src/ckb/transaction.test.ts | 90 ++++ packages/core/src/ckb/transaction.ts | 12 +- packages/shell/package.json | 1 + packages/shell/src/barrel.ts | 1 + pnpm-lock.yaml | 187 +++------ vitest.config.mts | 2 +- 26 files changed, 1246 insertions(+), 137 deletions(-) create mode 100644 .changeset/empty-schools-return.md create mode 100644 .changeset/odd-experts-win.md create mode 100644 packages/co-build/.npmignore create mode 100644 packages/co-build/.prettierignore create mode 100644 packages/co-build/README.md create mode 100644 packages/co-build/eslint.config.mjs create mode 100644 packages/co-build/misc/basedirs/dist.commonjs/package.json create mode 100644 packages/co-build/misc/basedirs/dist/package.json create mode 100644 packages/co-build/package.json create mode 100644 packages/co-build/prettier.config.cjs create mode 100644 packages/co-build/src/barrel.ts create mode 100644 packages/co-build/src/coBuild.test.ts create mode 100644 packages/co-build/src/coBuild.ts create mode 100644 packages/co-build/src/codec/buildingPacket.ts create mode 100644 packages/co-build/src/codec/index.ts create mode 100644 packages/co-build/src/codec/witnessLayout.ts create mode 100644 packages/co-build/src/index.ts create mode 100644 packages/co-build/tsconfig.json create mode 100644 packages/co-build/tsdown.config.mts create mode 100644 packages/co-build/typedoc.json diff --git a/.changeset/empty-schools-return.md b/.changeset/empty-schools-return.md new file mode 100644 index 000000000..a742ead2c --- /dev/null +++ b/.changeset/empty-schools-return.md @@ -0,0 +1,6 @@ +--- +"@ckb-ccc/core": patch +--- + +fix(core): occupy witness while adding output + \ No newline at end of file diff --git a/.changeset/odd-experts-win.md b/.changeset/odd-experts-win.md new file mode 100644 index 000000000..c2893bce6 --- /dev/null +++ b/.changeset/odd-experts-win.md @@ -0,0 +1,6 @@ +--- +"@ckb-ccc/co-build": minor +--- + +feat(co-build): new CoBuild package + \ No newline at end of file diff --git a/packages/co-build/.npmignore b/packages/co-build/.npmignore new file mode 100644 index 000000000..7a88408aa --- /dev/null +++ b/packages/co-build/.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/co-build/.prettierignore b/packages/co-build/.prettierignore new file mode 100644 index 000000000..aef5d239c --- /dev/null +++ b/packages/co-build/.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/co-build/README.md b/packages/co-build/README.md new file mode 100644 index 000000000..0075a73ae --- /dev/null +++ b/packages/co-build/README.md @@ -0,0 +1,44 @@ +

+ + Logo + +

+ +

+ @ckb-ccc/co-build +

+ +

+ CCC - CKBers' Codebase is a one-stop solution for your CKB JS/TS ecosystem development. +

+ +## Overview + +`@ckb-ccc/co-build` provides tools and Molecule codecs to support the CKB Co-build Protocol, a standard for coordinating multi-party signers and complex script operations (such as Spore actions) within a single transaction. + +## Quick Example + +```typescript +import { ccc } from "@ckb-ccc/core"; +import { CoBuild, ScriptInfo } from "@ckb-ccc/co-build"; + +const coBuild = new CoBuild( + { + codeHash: "0x...", + hashType: "type", + args: "0x...", + }, + { + name: "MyScript", + url: "https://example.com", + schema: "MySchema", + messageType: "MyMessageType", + }, +); + +// Build action +const action = coBuild.buildAction(ccc.bytesFrom("action payload")); + +// Append to transaction +const { tx } = await coBuild.appendActions(transaction, action); +``` diff --git a/packages/co-build/eslint.config.mjs b/packages/co-build/eslint.config.mjs new file mode 100644 index 000000000..b6132c277 --- /dev/null +++ b/packages/co-build/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/co-build/misc/basedirs/dist.commonjs/package.json b/packages/co-build/misc/basedirs/dist.commonjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/packages/co-build/misc/basedirs/dist.commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/co-build/misc/basedirs/dist/package.json b/packages/co-build/misc/basedirs/dist/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/packages/co-build/misc/basedirs/dist/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/co-build/package.json b/packages/co-build/package.json new file mode 100644 index 000000000..acc6e3704 --- /dev/null +++ b/packages/co-build/package.json @@ -0,0 +1,54 @@ +{ + "name": "@ckb-ccc/co-build", + "version": "0.0.0", + "description": "CoBuild", + "author": "Hanssen <0@hanssen0.com>", + "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.mjs", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist.commonjs/index.js" + }, + "./barrel": { + "import": "./dist/barrel.mjs", + "require": "./dist.commonjs/barrel.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "test": "vitest", + "test:ci": "vitest run", + "build": "tsdown", + "lint": "tsc --noEmit && eslint ./src", + "format": "prettier --write . && eslint --fix ./src" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^26.0.0", + "eslint": "^10.5.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.6", + "prettier": "^3.8.4", + "prettier-plugin-organize-imports": "^4.3.0", + "tsdown": "^0.22.3", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.1", + "vitest": "^4.1.9" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@ckb-ccc/core": "workspace:*" + }, + "packageManager": "pnpm@11.8.0", + "types": "./dist.commonjs/index.d.ts" +} diff --git a/packages/co-build/prettier.config.cjs b/packages/co-build/prettier.config.cjs new file mode 100644 index 000000000..5e1810363 --- /dev/null +++ b/packages/co-build/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/co-build/src/barrel.ts b/packages/co-build/src/barrel.ts new file mode 100644 index 000000000..dd4cbfcd4 --- /dev/null +++ b/packages/co-build/src/barrel.ts @@ -0,0 +1,2 @@ +export * from "./coBuild.js"; +export * from "./codec/index.js"; diff --git a/packages/co-build/src/coBuild.test.ts b/packages/co-build/src/coBuild.test.ts new file mode 100644 index 000000000..393d92027 --- /dev/null +++ b/packages/co-build/src/coBuild.test.ts @@ -0,0 +1,168 @@ +import { ccc } from "@ckb-ccc/core"; +import { describe, expect, test } from "vitest"; +import { CoBuild } from "./coBuild.js"; +import { Action, ScriptInfo } from "./codec/index.js"; + +describe("CoBuild", () => { + const script = ccc.Script.from({ + codeHash: "0x" + "22".repeat(32), + hashType: "type", + args: "0x3344", + }); + + const scriptInfo = ScriptInfo.from({ + name: "TestScript", + url: "https://example.com", + scriptHash: script.hash(), + schema: "TestSchema", + messageType: "TestMessageType", + }); + + const coBuild = new CoBuild(script, scriptInfo); + + test("buildAction should prefill script context", () => { + const payload = "0x556677"; + const action = coBuild.buildAction(payload); + + expect(action.scriptInfoHash).toBe(scriptInfo.hash()); + expect(action.scriptHash).toBe(script.hash()); + expect(action.data).toBe(payload); + }); + + test("buildAction with ccc.Entity", () => { + const entity = ccc.Script.from({ + codeHash: "0x" + "44".repeat(32), + hashType: "type", + args: "0x", + }); + const action = coBuild.buildAction(entity); + + expect(action.data).toBe(ccc.hexFrom(entity.toBytes())); + }); + + test("appendActions and parseActions with single action", async () => { + const tx = ccc.Transaction.from({ + version: 0, + cellDeps: [], + headerDeps: [], + inputs: [], + outputs: [], + outputsData: [], + witnesses: [], + }); + + const action = coBuild.buildAction("0xaabb"); + const { tx: txUpdated } = await coBuild.appendActions(tx, action); + + expect(txUpdated.witnesses.length).toBe(1); + + const { actions } = coBuild.parseActions(txUpdated); + expect(actions.length).toBe(1); + expect(actions[0].data).toBe("0xaabb"); + expect(actions[0].scriptHash).toBe(script.hash()); + expect(actions[0].scriptInfoHash).toBe(scriptInfo.hash()); + }); + + test("appendActions with iterable and async iterable", async () => { + const action1 = coBuild.buildAction("0x11"); + const action2 = coBuild.buildAction("0x22"); + + // Test sync iterable (array) + const txSyncBase = ccc.Transaction.from({ + version: 0, + cellDeps: [], + headerDeps: [], + inputs: [], + outputs: [], + outputsData: [], + witnesses: [], + }); + const { tx: txSync } = await coBuild.appendActions(txSyncBase, [ + action1, + action2, + ]); + const { actions: actionsSync } = coBuild.parseActions(txSync); + expect(actionsSync.length).toBe(2); + expect(actionsSync[0].data).toBe("0x11"); + expect(actionsSync[1].data).toBe("0x22"); + + // Test async iterable + const txAsyncBase = ccc.Transaction.from({ + version: 0, + cellDeps: [], + headerDeps: [], + inputs: [], + outputs: [], + outputsData: [], + witnesses: [], + }); + async function* asyncGenerator() { + yield action1; + yield action2; + } + const { tx: txAsync } = await coBuild.appendActions( + txAsyncBase, + asyncGenerator(), + ); + const { actions: actionsAsync } = coBuild.parseActions(txAsync); + expect(actionsAsync.length).toBe(2); + expect(actionsAsync[0].data).toBe("0x11"); + expect(actionsAsync[1].data).toBe("0x22"); + }); + + test("findActions query methods", async () => { + const tx = ccc.Transaction.from({ + version: 0, + cellDeps: [], + headerDeps: [], + inputs: [], + outputs: [], + outputsData: [], + witnesses: [], + }); + + const otherScript = ccc.Script.from({ + codeHash: "0x" + "55".repeat(32), + hashType: "type", + args: "0x", + }); + + const actionCurrent = coBuild.buildAction("0x11"); + const actionOther = Action.from({ + scriptInfoHash: "0x" + "66".repeat(32), + scriptHash: otherScript.hash(), + data: "0x22", + }); + + const { tx: txUpdated } = await coBuild.appendActions(tx, [ + actionCurrent, + actionOther, + ]); + + // findActions matching current script + const foundActions1 = coBuild.findActions(txUpdated); + expect(foundActions1.length).toBe(1); + expect(foundActions1[0].data).toBe("0x11"); + + // findActions matching other script + const foundActions2 = coBuild.findActions(txUpdated, otherScript); + expect(foundActions2.length).toBe(1); + expect(foundActions2[0].data).toBe("0x22"); + + // findActionsByHash matching other script hash + const foundActions3 = coBuild.findActionsByHash( + txUpdated, + otherScript.hash(), + ); + expect(foundActions3.length).toBe(1); + expect(foundActions3[0].data).toBe("0x22"); + + // findActionsByScriptInfo matching current script info + const foundActions4 = coBuild.findActionsByScriptInfo( + txUpdated, + scriptInfo, + ); + expect(foundActions4.length).toBe(1); + expect(foundActions4[0].data).toBe("0x11"); + }); +}); diff --git a/packages/co-build/src/coBuild.ts b/packages/co-build/src/coBuild.ts new file mode 100644 index 000000000..474844047 --- /dev/null +++ b/packages/co-build/src/coBuild.ts @@ -0,0 +1,387 @@ +import { ccc } from "@ckb-ccc/core"; +import { + Action, + ActionLike, + ScriptInfo, + ScriptInfoLike, + WitnessLayout, + WitnessLayoutVariant, +} from "./codec/index.js"; + +/** + * Parses the CoBuild actions from the last witness of a transaction. + * @param txLike The transaction containing the witnesses. + * @param shouldRemoveParsedWitness If true, removes the last witness from the returned transaction. + * @returns An object containing the parsed actions and the transaction. + */ +export function parseActions( + txLike: ccc.TransactionLike, + shouldRemoveParsedWitness: boolean = false, +): { + actions: Action[]; + tx: ccc.Transaction; +} { + const tx = ccc.Transaction.from(txLike); + + if (tx.witnesses.length === 0) { + return { actions: [], tx }; + } + + try { + return WitnessLayout.decode(tx.witnesses[tx.witnesses.length - 1]).match({ + SighashAll: ({ message: { actions } }) => { + if (shouldRemoveParsedWitness) { + tx.witnesses.pop(); + } + return { actions, tx }; + }, + _: () => { + // In the original CoBuild design, the SighashAll can be anywhere in the transaction witnesses. + // I don't think that design will come to reality in a short time, so I will just ignore other possibilities for now. + return { actions: [], tx }; + }, + }); + } catch { + return { actions: [], tx }; + } +} + +/** + * Parses existing actions, executes a manipulator function to modify or add actions, + * and encodes/updates the witness layout at the correct index. + * @param txLike The target transaction. + * @param manipulator A function that takes the current actions and returns updated actions. + * @returns The updated transaction and the index of the modified witness. + */ +export async function manipulateActions( + txLike: ccc.TransactionLike, + manipulator: (actions: Action[]) => Action[] | Promise, +): Promise<{ + tx: ccc.Transaction; + witnessIndex: number; +}> { + let tx = ccc.Transaction.from(txLike); + const parsedRes = parseActions(tx, true); + tx = parsedRes.tx; + + const witnessIndex = Math.max( + tx.witnesses.length, + tx.inputs.length, + tx.outputs.length, + ); + + tx.setWitnessAt( + witnessIndex, + WitnessLayout.encode({ + type: WitnessLayoutVariant.SighashAll, + value: { + seal: "0x", + message: { + actions: await manipulator(parsedRes.actions), + }, + }, + }), + ); + + return { + tx, + witnessIndex, + }; +} + +/** + * Appends actions to the transaction's CoBuild witness. + * Supports single action, iterables, or async iterables of actions. + * @param txLike The target transaction. + * @param actions The action(s) to append. Can be a single ActionLike, or an iterable/async iterable of ActionLike. + * @returns The updated transaction and the index of the witness. + */ +export async function appendActions( + txLike: ccc.TransactionLike, + actions: ActionLike | Iterable | AsyncIterable, +): Promise<{ + tx: ccc.Transaction; + witnessIndex: number; +}> { + const actionList: ActionLike[] = []; + if (typeof actions === "object" && actions !== null) { + if (Symbol.iterator in actions) { + for (const action of actions) { + actionList.push(action); + } + } else if (Symbol.asyncIterator in actions) { + for await (const action of actions) { + actionList.push(action); + } + } else { + actionList.push(actions); + } + } + + return manipulateActions(txLike, (existedActions) => + existedActions.concat(actionList.map(Action.from)), + ); +} + +/** + * Filters actions in a transaction matching the given CKB Script. + * @param txLike The transaction containing the witnesses. + * @param scriptLike The script to match actions against. + * @returns The list of matching actions. + */ +export function findActions( + txLike: ccc.TransactionLike, + scriptLike: ccc.ScriptLike, +): Action[] { + const scriptHash = ccc.Script.from(scriptLike).hash(); + return findActionsByHash(txLike, scriptHash); +} + +/** + * Filters actions in a transaction matching the given script hash. + * @param txLike The transaction containing the witnesses. + * @param scriptHashLike The script hash to match. + * @returns The list of matching actions. + */ +export function findActionsByHash( + txLike: ccc.TransactionLike, + scriptHashLike: ccc.HexLike, +): Action[] { + const scriptHash = ccc.hexFrom(scriptHashLike); + const { actions } = parseActions(txLike); + return actions.filter((action) => action.scriptHash === scriptHash); +} + +/** + * Filters actions in a transaction matching the given ScriptInfo. + * @param txLike The transaction containing the witnesses. + * @param scriptInfoLike The ScriptInfo metadata to match. + * @returns The list of matching actions. + */ +export function findActionsByScriptInfo( + txLike: ccc.TransactionLike, + scriptInfoLike: ScriptInfoLike, +): Action[] { + const scriptInfoHash = ScriptInfo.from(scriptInfoLike).hash(); + return findActionsByScriptInfoHash(txLike, scriptInfoHash); +} + +/** + * Filters actions in a transaction matching the given ScriptInfo hash. + * @param txLike The transaction containing the witnesses. + * @param scriptInfoHashLike The ScriptInfo hash to match. + * @returns The list of matching actions. + */ +export function findActionsByScriptInfoHash( + txLike: ccc.TransactionLike, + scriptInfoHashLike: ccc.HexLike, +): Action[] { + const scriptInfoHash = ccc.hexFrom(scriptInfoHashLike); + const { actions } = parseActions(txLike); + return actions.filter((action) => action.scriptInfoHash === scriptInfoHash); +} + +/** + * CoBuild helper class associated with a specific script and script info, + * delegating actions to top-level helper functions. + */ +export class CoBuild { + /** + * The ScriptInfo metadata used for building actions. + */ + public readonly scriptInfo: ScriptInfo; + /** + * The CKB script executing the actions. + */ + public readonly script: ccc.Script; + /** + * Hex-encoded hash of the ScriptInfo. + */ + public readonly scriptInfoHash: ccc.Hex; + /** + * Hex-encoded script hash. + */ + public readonly scriptHash: ccc.Hex; + + /** + * Constructs a CoBuild instance. + * @param script The target CKB Script. + * @param scriptInfo The ScriptInfo metadata. + */ + constructor( + script: ccc.ScriptLike, + scriptInfo?: Omit | null, + ) { + this.script = ccc.Script.from(script); + this.scriptHash = this.script.hash(); + this.scriptInfo = ScriptInfo.from({ + ...(scriptInfo ?? {}), + scriptHash: this.script.hash(), + }); + this.scriptInfoHash = this.scriptInfo.hash(); + } + + /** + * Helper to construct an Action with prefilled scriptInfoHash and scriptHash. + * @param data The custom action payload data (either raw bytes or a serialized ccc Entity). + * @returns A new Action instance. + */ + buildAction(data: ccc.BytesLike | ccc.Entity): Action { + return Action.from({ + scriptInfoHash: this.scriptInfoHash, + scriptHash: this.scriptHash, + data: data instanceof ccc.Entity ? data.toBytes() : data, + }); + } + + /** + * Parses the CoBuild actions from the last witness of a transaction. + * @param txLike The transaction containing the witnesses. + * @param shouldRemoveParsedWitness If true, removes the last witness from the returned transaction. + * @returns An object containing the parsed actions and the transaction. + */ + parseActions( + txLike: ccc.TransactionLike, + shouldRemoveParsedWitness: boolean = false, + ): { + actions: Action[]; + tx: ccc.Transaction; + } { + return parseActions(txLike, shouldRemoveParsedWitness); + } + + /** + * Parses existing actions, executes a manipulator function to modify or add actions, + * and encodes/updates the witness layout at the correct index. + * @param txLike The target transaction. + * @param manipulator A function that takes the current actions and returns updated actions. + * @returns The updated transaction and the index of the modified witness. + */ + manipulateActions( + txLike: ccc.TransactionLike, + manipulator: (actions: Action[]) => Action[] | Promise, + ): Promise<{ + tx: ccc.Transaction; + witnessIndex: number; + }> { + return manipulateActions(txLike, manipulator); + } + + /** + * Appends actions to the transaction's CoBuild witness using the script context. + * Supports single, iterables, or async iterables of actions or entities. + * @param txLike The target transaction. + * @param actions The action(s) to append. + * @returns The updated transaction and the index of the witness. + */ + async appendActions( + txLike: ccc.TransactionLike, + actions: + | ActionLike + | ccc.Entity + | Iterable + | AsyncIterable, + ): Promise<{ + tx: ccc.Transaction; + witnessIndex: number; + }> { + const actionList: (ActionLike | ccc.Entity)[] = []; + if (typeof actions === "object" && actions !== null) { + if (Symbol.iterator in actions) { + for (const action of actions) { + actionList.push(action); + } + } else if (Symbol.asyncIterator in actions) { + for await (const action of actions) { + actionList.push(action); + } + } else { + actionList.push(actions); + } + } + + return this.manipulateActions(txLike, (existedActions) => + existedActions.concat( + actionList.map((action) => { + if (typeof action === "object" && action !== null) { + if ( + "scriptInfoHash" in action && + "scriptHash" in action && + "data" in action + ) { + return Action.from(action); + } + } + return this.buildAction(action); + }), + ), + ); + } + + /** + * Filters actions in a transaction matching the given CKB Script. + * If omitted or null, defaults to the helper's script. + * @param txLike The transaction containing the witnesses. + * @param scriptLike The script to match actions against. + * @returns The list of matching actions. + */ + findActions( + txLike: ccc.TransactionLike, + scriptLike?: ccc.ScriptLike | null, + ): Action[] { + return findActionsByHash( + txLike, + scriptLike ? ccc.Script.from(scriptLike).hash() : this.scriptHash, + ); + } + + /** + * Filters actions in a transaction matching the given script hash. + * If omitted or null, defaults to the helper's script hash. + * @param txLike The transaction containing the witnesses. + * @param scriptHashLike The script hash to match. + * @returns The list of matching actions. + */ + findActionsByHash( + txLike: ccc.TransactionLike, + scriptHashLike?: ccc.HexLike | null, + ): Action[] { + return findActionsByHash(txLike, scriptHashLike ?? this.scriptHash); + } + + /** + * Filters actions in a transaction matching the given ScriptInfo. + * If omitted or null, defaults to the helper's script info. + * @param txLike The transaction containing the witnesses. + * @param scriptInfoLike The ScriptInfo metadata to match. + * @returns The list of matching actions. + */ + findActionsByScriptInfo( + txLike: ccc.TransactionLike, + scriptInfoLike?: ScriptInfoLike | null, + ): Action[] { + return findActionsByScriptInfoHash( + txLike, + scriptInfoLike + ? ScriptInfo.from(scriptInfoLike).hash() + : this.scriptInfoHash, + ); + } + + /** + * Filters actions in a transaction matching the given ScriptInfo hash. + * If omitted or null, defaults to the helper's script info hash. + * @param txLike The transaction containing the witnesses. + * @param scriptInfoHashLike The ScriptInfo hash to match. + * @returns The list of matching actions. + */ + findActionsByScriptInfoHash( + txLike: ccc.TransactionLike, + scriptInfoHashLike?: ccc.HexLike | null, + ): Action[] { + return findActionsByScriptInfoHash( + txLike, + scriptInfoHashLike ?? this.scriptInfoHash, + ); + } +} diff --git a/packages/co-build/src/codec/buildingPacket.ts b/packages/co-build/src/codec/buildingPacket.ts new file mode 100644 index 000000000..baa0afd94 --- /dev/null +++ b/packages/co-build/src/codec/buildingPacket.ts @@ -0,0 +1,166 @@ +import { ccc, mol } from "@ckb-ccc/core"; + +export const ActionCodec = mol.table({ + scriptInfoHash: mol.Byte32, + scriptHash: mol.Byte32, + data: mol.Bytes, +}); + +/** + * Representation of an Action's properties. + */ +export type ActionLike = ccc.EncodableType; + +/** + * Represents a single Action inside the CoBuild protocol. + */ +@ccc.codec(ActionCodec) +export class Action extends ccc.Entity.Base() { + public scriptInfoHash: ccc.Hex; + public scriptHash: ccc.Hex; + public data: ccc.Hex; + + /** + * Constructs an Action instance. + * @param scriptInfoHash The hex-encoded hash of the ScriptInfo. + * @param scriptHash The hex-encoded script hash. + * @param data The hex-encoded action payload data. + */ + constructor({ + scriptInfoHash, + scriptHash, + data, + }: ccc.DecodedType) { + super(); + + this.scriptInfoHash = scriptInfoHash; + this.scriptHash = scriptHash; + this.data = data; + } +} + +/** + * Molecule vector of Action codecs. + */ +export const ActionVec = mol.vector(Action); + +export const MessageCodec = mol.table({ + actions: ActionVec, +}); + +/** + * Representation of a Message's properties. + */ +export type MessageLike = ccc.EncodableType; + +/** + * Represents a Message containing a list of actions. + */ +@ccc.codec(MessageCodec) +export class Message extends ccc.Entity.Base() { + public actions: Action[]; + + /** + * Constructs a Message instance. + * @param actions The list of Action instances. + */ + constructor({ actions }: ccc.DecodedType) { + super(); + + this.actions = actions; + } +} + +export const ScriptInfoCodec = mol.table({ + name: mol.String, + url: mol.String, + scriptHash: mol.Byte32, + schema: mol.String, + messageType: mol.String, +}); + +/** + * Representation of a ScriptInfo's properties. + */ +export type ScriptInfoLike = { + /** + * The developer/dapp name. + */ + name?: string | null; + /** + * The url associated with the script or dapp. + */ + url?: string | null; + /** + * The script hash. + */ + scriptHash: ccc.BytesLike; + /** + * The action schema description. + */ + schema?: string | null; + /** + * The entry action type name used in the WitnessLayout. + */ + messageType?: string | null; +}; + +/** + * Represents script metadata information (ScriptInfo). + */ +@ccc.codec(ScriptInfoCodec) +export class ScriptInfo extends ccc.Entity.Base() { + public name: string; + public url: string; + public scriptHash: ccc.Hex; + public schema: string; + public messageType: string; + + /** + * Constructs a ScriptInfo instance. + * @param name The developer/dapp name. + * @param url The url associated with the script or dapp. + * @param scriptHash The hex-encoded script hash. + * @param schema The action schema. + * @param messageType The message type. + */ + constructor({ + name, + url, + scriptHash, + schema, + messageType, + }: ccc.DecodedType) { + super(); + + this.name = name; + this.url = url; + this.scriptHash = scriptHash; + this.schema = schema; + this.messageType = messageType; + } + + /** + * Creates a ScriptInfo instance from a ScriptInfoLike object. + * @param scriptInfoLike The source ScriptInfoLike object or ScriptInfo instance. + * @returns A new or existing ScriptInfo instance. + */ + static from(scriptInfoLike: ScriptInfoLike): ScriptInfo { + if (scriptInfoLike instanceof ScriptInfo) { + return scriptInfoLike; + } + + return super.from({ + name: scriptInfoLike.name ?? "", + url: scriptInfoLike.url ?? "", + scriptHash: scriptInfoLike.scriptHash, + schema: scriptInfoLike.schema ?? "", + messageType: scriptInfoLike.messageType ?? "", + }); + } +} + +/** + * Molecule vector of ScriptInfo codecs. + */ +export const ScriptInfoVec = mol.vector(ScriptInfo); diff --git a/packages/co-build/src/codec/index.ts b/packages/co-build/src/codec/index.ts new file mode 100644 index 000000000..d157535a7 --- /dev/null +++ b/packages/co-build/src/codec/index.ts @@ -0,0 +1,2 @@ +export * from "./buildingPacket.js"; +export * from "./witnessLayout.js"; diff --git a/packages/co-build/src/codec/witnessLayout.ts b/packages/co-build/src/codec/witnessLayout.ts new file mode 100644 index 000000000..735ad3cc3 --- /dev/null +++ b/packages/co-build/src/codec/witnessLayout.ts @@ -0,0 +1,66 @@ +import { ccc, mol } from "@ckb-ccc/core"; +import { Message } from "./buildingPacket.js"; + +export const SighashAllCodec = mol.table({ + seal: mol.Bytes, + message: Message, +}); + +/** + * Representation of a SighashAll's properties. + */ +export type SighashAllLike = ccc.EncodableType; + +/** + * Represents a SighashAll witness structure. + */ +@ccc.codec(SighashAllCodec) +export class SighashAll extends ccc.Entity.Base() { + public seal: ccc.Hex; + public message: Message; + + /** + * Constructs a SighashAll instance. + * @param seal The hex-encoded signature or seal. + * @param message The Message instance. + */ + constructor({ seal, message }: ccc.DecodedType) { + super(); + + this.seal = seal; + this.message = message; + } +} + +/** + * WitnessLayout union variant names. + */ +export const WitnessLayoutVariant = { + SighashAll: "SighashAll", +} as const; + +/** + * WitnessLayout union variant numeric identifiers. + */ +export const WitnessLayoutVariantId = { + SighashAll: 4278190081, +}; + +/** + * Molecule union representing the layout of the transaction witness. + */ +export const WitnessLayoutCodec = mol.union( + { + SighashAll, + }, + WitnessLayoutVariantId, +); + +type WitnessLayoutLike = ccc.EncodableType; + +@ccc.codec(WitnessLayoutCodec) +export class WitnessLayout extends ccc.Entity.BaseUnion< + typeof WitnessLayoutCodec, + WitnessLayoutLike, + WitnessLayout +>() {} diff --git a/packages/co-build/src/index.ts b/packages/co-build/src/index.ts new file mode 100644 index 000000000..cb3c22bcd --- /dev/null +++ b/packages/co-build/src/index.ts @@ -0,0 +1,2 @@ +export * from "./barrel.js"; +export * as coBuild from "./barrel.js"; diff --git a/packages/co-build/tsconfig.json b/packages/co-build/tsconfig.json new file mode 100644 index 000000000..3399c6f67 --- /dev/null +++ b/packages/co-build/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2022", + "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, + "module": "ESNext", + "moduleResolution": "Bundler", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/co-build/tsdown.config.mts b/packages/co-build/tsdown.config.mts new file mode 100644 index 000000000..9453d8b6d --- /dev/null +++ b/packages/co-build/tsdown.config.mts @@ -0,0 +1,41 @@ +import { defineConfig } from "tsdown"; + +const common = { + minify: true, + dts: true, + platform: "neutral" as const, + sourcemap: true, + exports: true, +}; + +const entry = { + index: "src/index.ts", + barrel: "src/barrel.ts", +} as const; + +const bundleDeps: string[] = []; + +export default defineConfig( + ( + [ + { + entry, + deps: { + onlyBundle: [] as string[], + }, + format: "esm", + copy: "./misc/basedirs/dist/*", + }, + { + entry, + deps: { + alwaysBundle: bundleDeps, + onlyBundle: bundleDeps, + }, + format: "cjs", + outDir: "dist.commonjs", + copy: "./misc/basedirs/dist.commonjs/*", + }, + ] as const + ).map((c) => ({ ...c, ...common })), +); diff --git a/packages/co-build/typedoc.json b/packages/co-build/typedoc.json new file mode 100644 index 000000000..9ca821498 --- /dev/null +++ b/packages/co-build/typedoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts"], + "extends": ["../../typedoc.base.json"], + "name": "@ckb-ccc co-build" +} diff --git a/packages/core/src/ckb/transaction.test.ts b/packages/core/src/ckb/transaction.test.ts index c6a3a9d46..b9981bdb5 100644 --- a/packages/core/src/ckb/transaction.test.ts +++ b/packages/core/src/ckb/transaction.test.ts @@ -1153,5 +1153,95 @@ describe("Transaction", () => { expect(cell.capacityFree).toBe(expectedFreeCapacity); }); }); + + describe("Witness Alignment", () => { + it("should not misalign witnesses when adding input with more outputs than inputs", () => { + const tx = ccc.Transaction.default(); + tx.addInput({ + previousOutput: { + txHash: "0x" + "0".repeat(64), + index: 0, + }, + since: 0n, + }); + tx.addOutput({ lock }); + tx.addOutput({ lock }); + + // Set witness for output 1 at index 1 + tx.setWitnessAt(1, "0x2222"); + expect(tx.witnesses).toEqual(["0x", "0x2222"]); + + // Add input 1. Since outputs (2) > inputs (1), it should not splice "0x" at index 1. + tx.addInput({ + previousOutput: { + txHash: "0x" + "0".repeat(64), + index: 1, + }, + since: 0n, + }); + + // The witness at index 1 should remain "0x2222", aligning with both input 1 and output 1 + expect(tx.witnesses).toEqual(["0x", "0x2222"]); + }); + + it("should not misalign witnesses when adding output with more inputs than outputs", () => { + const tx = ccc.Transaction.default(); + tx.addInput({ + previousOutput: { + txHash: "0x" + "0".repeat(64), + index: 0, + }, + since: 0n, + }); + tx.addInput({ + previousOutput: { + txHash: "0x" + "0".repeat(64), + index: 1, + }, + since: 0n, + }); + tx.addOutput({ lock }); + + // Set witness for input 1 at index 1 + tx.setWitnessAt(1, "0x1111"); + expect(tx.witnesses).toEqual(["0x", "0x1111"]); + + // Add output 1. Since inputs (2) > outputs (1), it should not splice "0x" at index 1. + tx.addOutput({ lock }); + + // The witness at index 1 should remain "0x1111", aligning with both input 1 and output 1 + expect(tx.witnesses).toEqual(["0x", "0x1111"]); + }); + + it("should splice dummy witness when witnesses length is greater than both inputs and outputs", () => { + const tx = ccc.Transaction.default(); + tx.addInput({ + previousOutput: { + txHash: "0x" + "0".repeat(64), + index: 0, + }, + since: 0n, + }); + tx.addOutput({ lock }); + + // Set an extra witness (e.g., CoBuild SighashAll) at index 1 + tx.setWitnessAt(1, "0x3333"); + expect(tx.witnesses).toEqual(["0x", "0x3333"]); + + // Add input 1. Since inputs.length (1) >= outputs.length (1), it splices "0x" to shift the extra witness. + tx.addInput({ + previousOutput: { + txHash: "0x" + "0".repeat(64), + index: 1, + }, + since: 0n, + }); + expect(tx.witnesses).toEqual(["0x", "0x", "0x3333"]); + + // Add output 1. Since outputs.length (1) >= inputs.length (2) is false, it does not splice. + tx.addOutput({ lock }); + expect(tx.witnesses).toEqual(["0x", "0x", "0x3333"]); + }); + }); }); }); diff --git a/packages/core/src/ckb/transaction.ts b/packages/core/src/ckb/transaction.ts index 7f07f0460..6b52537cb 100644 --- a/packages/core/src/ckb/transaction.ts +++ b/packages/core/src/ckb/transaction.ts @@ -1654,7 +1654,10 @@ export class Transaction extends Entity.Base() { * ``` */ addInput(inputLike: CellInputLike): number { - if (this.witnesses.length > this.inputs.length) { + if ( + this.witnesses.length > this.inputs.length && + this.inputs.length >= this.outputs.length + ) { this.witnesses.splice(this.inputs.length, 0, "0x"); } @@ -1755,6 +1758,13 @@ export class Transaction extends Entity.Base() { outputData: outputDataLike, }); + if ( + this.witnesses.length > this.outputs.length && + this.outputs.length >= this.inputs.length + ) { + this.witnesses.splice(this.outputs.length, 0, "0x"); + } + const len = this.outputs.push(cell.cellOutput); this.setOutputDataAt(len - 1, cell.outputData); diff --git a/packages/shell/package.json b/packages/shell/package.json index cd8d90328..cc69dbcb2 100644 --- a/packages/shell/package.json +++ b/packages/shell/package.json @@ -56,6 +56,7 @@ }, "dependencies": { "@ckb-ccc/core": "workspace:*", + "@ckb-ccc/co-build": "workspace:*", "@ckb-ccc/did-ckb": "workspace:*", "@ckb-ccc/spore": "workspace:*", "@ckb-ccc/ssri": "workspace:*", diff --git a/packages/shell/src/barrel.ts b/packages/shell/src/barrel.ts index e9d7adb7a..0be10ba5c 100644 --- a/packages/shell/src/barrel.ts +++ b/packages/shell/src/barrel.ts @@ -1,3 +1,4 @@ +export { coBuild } from "@ckb-ccc/co-build"; export * from "@ckb-ccc/core/barrel"; export { didCkb } from "@ckb-ccc/did-ckb"; export { spore } from "@ckb-ccc/spore"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 024ac367a..0d80ce240 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 0.7.0 '@changesets/cli': specifier: ^2.31.0 - version: 2.31.0(@types/node@26.0.0) + version: 2.31.0(@types/node@26.0.1) '@types/jest': specifier: ^30.0.0 version: 30.0.0 @@ -22,10 +22,10 @@ importers: version: 4.1.9(vitest@4.1.9) jest: specifier: 30.4.2 - version: 30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) + version: 30.4.2(@types/node@26.0.1)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) ts-jest: specifier: ^29.4.11 - version: 29.4.11(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)))(typescript@6.0.3) + version: 29.4.11(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@26.0.1)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)))(typescript@6.0.3) typedoc: specifier: 0.28.19 version: 0.28.19(typescript@6.0.3) @@ -43,7 +43,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.9 - version: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.9(@types/node@26.0.1)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) packages/ccc: dependencies: @@ -137,6 +137,46 @@ importers: specifier: ^8.61.1 version: 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + packages/co-build: + dependencies: + '@ckb-ccc/core': + specifier: workspace:* + version: link:../core + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.6.0(jiti@2.7.0)) + '@types/node': + specifier: ^26.0.0 + version: 26.0.1 + eslint: + specifier: ^10.5.0 + version: 10.6.0(jiti@2.7.0) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.6.0(jiti@2.7.0)) + eslint-plugin-prettier: + specifier: ^5.5.6 + version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.6.0(jiti@2.7.0)))(eslint@10.6.0(jiti@2.7.0))(prettier@3.9.4) + prettier: + specifier: ^3.8.4 + version: 3.9.4 + prettier-plugin-organize-imports: + specifier: ^4.3.0 + version: 4.3.0(prettier@3.9.4)(typescript@6.0.3) + tsdown: + specifier: ^0.22.3 + version: 0.22.3(tsx@4.22.4)(typescript@6.0.3)(unrun@0.2.22(synckit@0.11.13)) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.61.1 + version: 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + vitest: + specifier: ^4.1.9 + version: 4.1.9(@types/node@26.0.1)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + packages/connector: dependencies: '@ckb-ccc/ccc': @@ -276,7 +316,7 @@ importers: version: 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) vitest: specifier: ^4.1.9 - version: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.9(@types/node@26.0.1)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) packages/demo: dependencies: @@ -1003,6 +1043,9 @@ importers: packages/shell: dependencies: + '@ckb-ccc/co-build': + specifier: workspace:* + version: link:../co-build '@ckb-ccc/core': specifier: workspace:* version: link:../core @@ -4083,9 +4126,6 @@ packages: '@types/node@22.7.5': resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} - '@types/node@26.0.0': - resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} - '@types/node@26.0.1': resolution: {integrity: sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==} @@ -9714,7 +9754,7 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.31.0(@types/node@26.0.0)': + '@changesets/cli@2.31.0(@types/node@26.0.1)': dependencies: '@changesets/apply-release-plan': 7.1.1 '@changesets/assemble-release-plan': 6.0.10 @@ -9730,7 +9770,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@26.0.0) + '@inquirer/external-editor': 1.0.3(@types/node@26.0.1) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -10444,13 +10484,6 @@ snapshots: optionalDependencies: '@types/node': 26.0.1 - '@inquirer/external-editor@1.0.3(@types/node@26.0.0)': - dependencies: - chardet: 2.2.0 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 26.0.0 - '@inquirer/external-editor@1.0.3(@types/node@26.0.1)': dependencies: chardet: 2.2.0 @@ -12135,10 +12168,6 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@26.0.0': - dependencies: - undici-types: 8.3.0 - '@types/node@26.0.1': dependencies: undici-types: 8.3.0 @@ -12194,7 +12223,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.0.1 '@types/yargs-parser@21.0.3': {} @@ -12476,7 +12505,7 @@ snapshots: obug: 2.1.3 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.9(@types/node@26.0.1)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/expect@4.1.9': dependencies: @@ -15232,25 +15261,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)): - dependencies: - '@jest/core': 30.4.2(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) - '@jest/test-result': 30.4.1 - '@jest/types': 30.4.1 - chalk: 4.1.2 - exit-x: 0.2.2 - import-local: 3.2.0 - jest-config: 30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) - jest-util: 30.4.1 - jest-validate: 30.4.1 - yargs: 17.7.3 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - esbuild-register - - supports-color - - ts-node - jest-cli@30.4.2(@types/node@26.0.1)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)): dependencies: '@jest/core': 30.4.2(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) @@ -15270,38 +15280,6 @@ snapshots: - supports-color - ts-node - jest-config@30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)): - dependencies: - '@babel/core': 7.29.7 - '@jest/get-type': 30.1.0 - '@jest/pattern': 30.4.0 - '@jest/test-sequencer': 30.4.1 - '@jest/types': 30.4.1 - babel-jest: 30.4.1(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 4.4.0 - deepmerge: 4.3.1 - glob: 10.5.0 - graceful-fs: 4.2.11 - jest-circus: 30.4.2 - jest-docblock: 30.4.0 - jest-environment-node: 30.4.1 - jest-regex-util: 30.4.0 - jest-resolve: 30.4.1 - jest-runner: 30.4.2 - jest-util: 30.4.1 - jest-validate: 30.4.1 - parse-json: 5.2.0 - pretty-format: 30.4.1 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 26.0.0 - ts-node: 10.9.2(@types/node@26.0.1)(typescript@6.0.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-config@30.4.2(@types/node@26.0.1)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)): dependencies: '@babel/core': 7.29.7 @@ -15599,19 +15577,6 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)): - dependencies: - '@jest/core': 30.4.2(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) - '@jest/types': 30.4.1 - import-local: 3.2.0 - jest-cli: 30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - esbuild-register - - supports-color - - ts-node - jest@30.4.2(@types/node@26.0.1)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)): dependencies: '@jest/core': 30.4.2(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) @@ -17797,26 +17762,6 @@ snapshots: ts-dedent@2.3.0: {} - ts-jest@29.4.11(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)))(typescript@6.0.3): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.9 - jest: 30.4.2(@types/node@26.0.0)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.8.5 - type-fest: 4.41.0 - typescript: 6.0.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.29.7 - '@jest/transform': 30.4.1 - '@jest/types': 30.4.1 - babel-jest: 30.4.1(@babel/core@7.29.7) - jest-util: 30.4.1 - ts-jest@29.4.11(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@26.0.1)(ts-node@10.9.2(@types/node@26.0.1)(typescript@6.0.3)))(typescript@6.0.3): dependencies: bs-logger: 0.2.6 @@ -18245,34 +18190,6 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitest@4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.9 - '@vitest/runner': 4.1.9 - '@vitest/snapshot': 4.1.9 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.3 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 26.0.0 - '@vitest/coverage-v8': 4.1.9(vitest@4.1.9) - transitivePeerDependencies: - - msw - vitest@4.1.9(@types/node@26.0.1)(@vitest/coverage-v8@4.1.9)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 diff --git a/vitest.config.mts b/vitest.config.mts index 9b64c1358..31436983b 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,6 +1,6 @@ import { defineConfig, coverageConfigDefaults } from "vitest/config"; -const packages = ["packages/core", "packages/did-ckb", "packages/type-id"]; +const packages = ["packages/core", "packages/did-ckb", "packages/type-id", "packages/co-build"]; export default defineConfig({ test: {