From 87b1d04e1cbd4e7503ca6ff72cd3920b5d59d009 Mon Sep 17 00:00:00 2001 From: jc Date: Tue, 18 Nov 2025 13:27:54 +0800 Subject: [PATCH 1/5] feat: add superise wallet --- packages/ccc/package.json | 3 +- packages/ccc/src/assets/superise.svg.ts | 85 +++++++ packages/ccc/src/barrel.ts | 1 + packages/ccc/src/signersController.ts | 10 + packages/superise/.npmignore | 21 ++ packages/superise/.prettierignore | 15 ++ packages/superise/README.md | 41 ++++ packages/superise/eslint.config.mjs | 62 +++++ .../misc/basedirs/dist.commonjs/package.json | 3 + .../superise/misc/basedirs/dist/package.json | 3 + packages/superise/package.json | 63 ++++++ packages/superise/prettier.config.cjs | 11 + packages/superise/src/advanced.ts | 1 + packages/superise/src/advancedBarrel.ts | 5 + packages/superise/src/barrel.ts | 2 + packages/superise/src/ckb/index.ts | 212 ++++++++++++++++++ packages/superise/src/index.ts | 1 + packages/superise/src/signersFactory.ts | 24 ++ packages/superise/tsconfig.base.json | 22 ++ packages/superise/tsconfig.commonjs.json | 8 + packages/superise/tsconfig.json | 8 + packages/superise/typedoc.json | 6 + pnpm-lock.yaml | 52 +++++ 23 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 packages/ccc/src/assets/superise.svg.ts create mode 100644 packages/superise/.npmignore create mode 100644 packages/superise/.prettierignore create mode 100644 packages/superise/README.md create mode 100644 packages/superise/eslint.config.mjs create mode 100644 packages/superise/misc/basedirs/dist.commonjs/package.json create mode 100644 packages/superise/misc/basedirs/dist/package.json create mode 100644 packages/superise/package.json create mode 100644 packages/superise/prettier.config.cjs create mode 100644 packages/superise/src/advanced.ts create mode 100644 packages/superise/src/advancedBarrel.ts create mode 100644 packages/superise/src/barrel.ts create mode 100644 packages/superise/src/ckb/index.ts create mode 100644 packages/superise/src/index.ts create mode 100644 packages/superise/src/signersFactory.ts create mode 100644 packages/superise/tsconfig.base.json create mode 100644 packages/superise/tsconfig.commonjs.json create mode 100644 packages/superise/tsconfig.json create mode 100644 packages/superise/typedoc.json diff --git a/packages/ccc/package.json b/packages/ccc/package.json index 61219eb9e..bece98a8f 100644 --- a/packages/ccc/package.json +++ b/packages/ccc/package.json @@ -64,7 +64,8 @@ "@ckb-ccc/shell": "workspace:*", "@ckb-ccc/uni-sat": "workspace:*", "@ckb-ccc/utxo-global": "workspace:*", - "@ckb-ccc/xverse": "workspace:*" + "@ckb-ccc/xverse": "workspace:*", + "@ckb-ccc/superise": "workspace:*" }, "packageManager": "pnpm@10.8.1" } diff --git a/packages/ccc/src/assets/superise.svg.ts b/packages/ccc/src/assets/superise.svg.ts new file mode 100644 index 000000000..3d6f493d6 --- /dev/null +++ b/packages/ccc/src/assets/superise.svg.ts @@ -0,0 +1,85 @@ +import { encodeSvgToImgSrc } from "./utils.js"; + +export const SUPERISE_SVG = encodeSvgToImgSrc(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`); diff --git a/packages/ccc/src/barrel.ts b/packages/ccc/src/barrel.ts index 8b46c112d..a47eae2df 100644 --- a/packages/ccc/src/barrel.ts +++ b/packages/ccc/src/barrel.ts @@ -4,6 +4,7 @@ export * from "@ckb-ccc/nip07"; export * from "@ckb-ccc/okx"; export * from "@ckb-ccc/rei"; export * from "@ckb-ccc/shell/barrel"; +export * from "@ckb-ccc/superise"; export * from "@ckb-ccc/uni-sat"; export * from "@ckb-ccc/utxo-global"; export * from "@ckb-ccc/xverse"; diff --git a/packages/ccc/src/signersController.ts b/packages/ccc/src/signersController.ts index 05e58deaa..51c7ded41 100644 --- a/packages/ccc/src/signersController.ts +++ b/packages/ccc/src/signersController.ts @@ -4,6 +4,7 @@ import { Nip07 } from "@ckb-ccc/nip07"; import { Okx } from "@ckb-ccc/okx"; import { Rei } from "@ckb-ccc/rei"; import { ccc } from "@ckb-ccc/shell"; +import { SupeRISE } from "@ckb-ccc/superise"; import { UniSat } from "@ckb-ccc/uni-sat"; import { UtxoGlobal } from "@ckb-ccc/utxo-global"; import { Xverse } from "@ckb-ccc/xverse"; @@ -13,6 +14,7 @@ import { METAMASK_SVG } from "./assets/metamask.svg.js"; import { NOSTR_SVG } from "./assets/nostr.svg.js"; import { OKX_SVG } from "./assets/okx.svg.js"; import { REI_SVG } from "./assets/rei.svg.js"; +import { SUPERISE_SVG } from "./assets/superise.svg"; import { UNI_SAT_SVG } from "./assets/uni-sat.svg.js"; import { UTXO_GLOBAL_SVG } from "./assets/utxo-global.svg.js"; @@ -124,6 +126,14 @@ export class SignersController { async addRealSigners(context: SignersControllerRefreshContext) { const { appName, appIcon, client, preferredNetworks } = context; + + await this.addSigners( + "SupeRISE", + SUPERISE_SVG, + SupeRISE.getSupeRISESigners(client, preferredNetworks), + context, + ); + await this.addSigners( "UTXO Global Wallet", UTXO_GLOBAL_SVG, diff --git a/packages/superise/.npmignore b/packages/superise/.npmignore new file mode 100644 index 000000000..7a88408aa --- /dev/null +++ b/packages/superise/.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/superise/.prettierignore b/packages/superise/.prettierignore new file mode 100644 index 000000000..aef5d239c --- /dev/null +++ b/packages/superise/.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/superise/README.md b/packages/superise/README.md new file mode 100644 index 000000000..d4fb74223 --- /dev/null +++ b/packages/superise/README.md @@ -0,0 +1,41 @@ +

+ + Logo + +

+ +

+ CCC's support for SupeRISE +

+ +

+ 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/superise/eslint.config.mjs b/packages/superise/eslint.config.mjs new file mode 100644 index 000000000..b6132c277 --- /dev/null +++ b/packages/superise/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/superise/misc/basedirs/dist.commonjs/package.json b/packages/superise/misc/basedirs/dist.commonjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/packages/superise/misc/basedirs/dist.commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/superise/misc/basedirs/dist/package.json b/packages/superise/misc/basedirs/dist/package.json new file mode 100644 index 000000000..aead43de3 --- /dev/null +++ b/packages/superise/misc/basedirs/dist/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/packages/superise/package.json b/packages/superise/package.json new file mode 100644 index 000000000..8963b052f --- /dev/null +++ b/packages/superise/package.json @@ -0,0 +1,63 @@ +{ + "name": "@ckb-ccc/superise", + "version": "1.0.29", + "description": "Common Chains Connector's support for SupeRISE", + "author": "rivjc", + "license": "MIT", + "private": false, + "homepage": "https://github.com/ckb-devrel/ccc", + "repository": { + "type": "git", + "url": "git://github.com/ckb-devrel/ccc.git" + }, + "sideEffects": false, + "main": "dist.commonjs/index.js", + "module": "dist/index.js", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist.commonjs/index.js", + "default": "./dist.commonjs/index.js" + }, + "./barrel": { + "import": "./dist/barrel.js", + "require": "./dist.commonjs/barrel.js", + "default": "./dist.commonjs/barrel.js" + }, + "./advancedBarrel": { + "import": "./dist/advancedBarrel.js", + "require": "./dist.commonjs/advancedBarrel.js", + "default": "./dist.commonjs/advancedBarrel.js" + }, + "./advanced": { + "import": "./dist/advanced.js", + "require": "./dist.commonjs/advanced.js", + "default": "./dist.commonjs/advanced.js" + } + }, + "scripts": { + "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", + "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:*", + "@superise/bridge-api-types": "^0.0.5" + }, + "packageManager": "pnpm@10.8.1" +} diff --git a/packages/superise/prettier.config.cjs b/packages/superise/prettier.config.cjs new file mode 100644 index 000000000..5e1810363 --- /dev/null +++ b/packages/superise/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/superise/src/advanced.ts b/packages/superise/src/advanced.ts new file mode 100644 index 000000000..5bf9d080e --- /dev/null +++ b/packages/superise/src/advanced.ts @@ -0,0 +1 @@ +export * as SupeRISEA from "./advancedBarrel.js"; diff --git a/packages/superise/src/advancedBarrel.ts b/packages/superise/src/advancedBarrel.ts new file mode 100644 index 000000000..9fe98bff9 --- /dev/null +++ b/packages/superise/src/advancedBarrel.ts @@ -0,0 +1,5 @@ +import type SupeRISE from "@superise/bridge-api-types"; + +export type Bridge = SupeRISE.Bridge; +export type CkbConnection = SupeRISE.CkbConnection; +export type SignCkbHashAllMetadata = SupeRISE.SignCkbHashAllMetadata; diff --git a/packages/superise/src/barrel.ts b/packages/superise/src/barrel.ts new file mode 100644 index 000000000..81e1f809e --- /dev/null +++ b/packages/superise/src/barrel.ts @@ -0,0 +1,2 @@ +export * from "./ckb"; +export * from "./signersFactory"; diff --git a/packages/superise/src/ckb/index.ts b/packages/superise/src/ckb/index.ts new file mode 100644 index 000000000..d58082e57 --- /dev/null +++ b/packages/superise/src/ckb/index.ts @@ -0,0 +1,212 @@ +import { ccc } from "@ckb-ccc/core"; +import type { + Bridge, + CkbConnection, + SignCkbHashAllMetadata, +} from "../advancedBarrel"; + +export class CkbSigner extends ccc.Signer { + get type() { + return ccc.SignerType.CKB; + } + + get signType() { + return ccc.SignerSignType.CkbSecp256k1; + } + + private connectionStorageKey = "superise-ckb-connection"; + + private connection?: CkbConnection; + + private _uiMetadataMap: Record = {}; + + constructor( + private readonly bridge: Bridge, + client: ccc.Client, + ) { + super(client); + } + + private saveConnection() { + localStorage.setItem( + this.connectionStorageKey, + JSON.stringify(this.connection), + ); + } + + private restoreConnection() { + const connection = localStorage.getItem(this.connectionStorageKey); + if (!connection) return; + try { + this.connection = JSON.parse(connection) as CkbConnection; + } catch {} + } + + private async getConnection() { + if (!this.connection) this.restoreConnection(); + if (!this.connection) throw new Error("Not connected"); + + return this.connection; + } + + async connect() { + this.connection = await this.bridge.connectCkb(); + this.saveConnection(); + } + + async isConnected() { + if (this.connection) return true; + + this.restoreConnection(); + return !!this.connection; + } + + override async disconnect() { + this.connection = undefined; + } + + override async getInternalAddress() { + return (await this.getConnection()).address; + } + + override async getIdentity() { + return (await this.getConnection()).publicKey; + } + + async getAddressObj() { + return await ccc.Address.fromString( + await this.getInternalAddress(), + this.client, + ); + } + + override async getAddressObjs() { + return [await this.getAddressObj()]; + } + + override async signMessageRaw(message: string) { + const signMessage = `Nervos Message:${message}`; + const sign = await this.bridge.signCkbMessage(signMessage); + return ccc.hexFrom(sign.signature); + } + + async getRelatedScripts( + txLike: ccc.TransactionLike, + ): Promise<{ script: ccc.Script; cellDeps: ccc.CellDepInfo[] }[]> { + const tx = ccc.Transaction.from(txLike); + + const addressObj = await this.getAddressObj(); + const acp = await ccc.Script.fromKnownScript( + this.client, + ccc.KnownScript.AnyoneCanPay, + addressObj.script.args, + ); + + const scripts: { script: ccc.Script; cellDeps: ccc.CellDepInfo[] }[] = []; + for (const input of tx.inputs) { + const { + cellOutput: { lock }, + } = await input.getCell(this.client); + + if (scripts.some(({ script }) => script.eq(lock))) { + continue; + } + + if (lock.eq(addressObj.script)) { + scripts.push({ + script: lock, + cellDeps: ( + await this.client.getKnownScript(ccc.KnownScript.Secp256k1Blake160) + ).cellDeps, + }); + } else if ( + lock.codeHash === acp.codeHash && + lock.hashType === acp.hashType && + lock.args.startsWith(acp.args) + ) { + scripts.push({ + script: lock, + cellDeps: ( + await this.client.getKnownScript(ccc.KnownScript.AnyoneCanPay) + ).cellDeps, + }); + } + } + + return scripts; + } + + public setUiMetadataForTx( + tx: ccc.TransactionLike, + metadata: SignCkbHashAllMetadata, + ) { + const txHash = ccc.Transaction.from(tx).hash(); + this._uiMetadataMap = { [txHash]: metadata }; + } + + private getUiMetadataFromTx(tx: ccc.TransactionLike) { + const txHash = ccc.Transaction.from(tx).hash(); + return this._uiMetadataMap[txHash]; + } + + override async prepareTransaction(txLike: ccc.TransactionLike) { + const tx = ccc.Transaction.from(txLike); + + const scripts = await this.getRelatedScripts(tx); + + await Promise.all( + scripts.map(async ({ script, cellDeps }) => { + await tx.prepareSighashAllWitness(script, 65, this.client); + await tx.addCellDepInfos(this.client, cellDeps); + }), + ); + + return tx; + } + + override async signOnlyTransaction(txLike: ccc.TransactionLike) { + const tx = ccc.Transaction.from(txLike); + const metadata = this.getUiMetadataFromTx(tx); + + const signatureCache = new Map(); + + for (const { script } of await this.getRelatedScripts(tx)) { + const info = await tx.getSignHashInfo(script, this.client); + if (!info) { + return tx; + } + + const { message, position } = info; + + let signature!: string; + if (signatureCache.has(message)) { + signature = signatureCache.get(message)!; + } else { + const sign = await this.bridge.signCkbHashAll( + message.replace(/^0x/, ""), + metadata, + ); + signature = sign.signature; + signatureCache.set(message, signature); + } + + const witness = + tx.getWitnessArgsAt(info.position) ?? ccc.WitnessArgs.from({}); + witness.lock = signature as ccc.Hex; + tx.setWitnessArgsAt(position, witness); + } + + return tx; + } + + override async signTransaction( + tx: ccc.TransactionLike, + ): Promise { + const preparedTx = await this.prepareTransaction(tx); + return this.signOnlyTransaction(preparedTx); + } + + override async sendTransaction(tx: ccc.TransactionLike): Promise { + return this.client.sendTransaction(await this.signTransaction(tx)); + } +} diff --git a/packages/superise/src/index.ts b/packages/superise/src/index.ts new file mode 100644 index 000000000..46a02e3c3 --- /dev/null +++ b/packages/superise/src/index.ts @@ -0,0 +1 @@ +export * as SupeRISE from "./barrel.js"; diff --git a/packages/superise/src/signersFactory.ts b/packages/superise/src/signersFactory.ts new file mode 100644 index 000000000..5f2cd154e --- /dev/null +++ b/packages/superise/src/signersFactory.ts @@ -0,0 +1,24 @@ +import { ccc } from "@ckb-ccc/core"; +import "@superise/bridge-api-types"; +import { CkbSigner } from "./ckb"; + +/** + * @public + */ +export function getSupeRISESigners( + client: ccc.Client, + _preferredNetworks?: ccc.NetworkPreference[], +): ccc.SignerInfo[] { + const windowRef = window; + + if (typeof windowRef.superise === "undefined") { + return []; + } + + return [ + { + name: "CKB", + signer: new CkbSigner(windowRef.superise, client), + }, + ]; +} diff --git a/packages/superise/tsconfig.base.json b/packages/superise/tsconfig.base.json new file mode 100644 index 000000000..7e5ac952b --- /dev/null +++ b/packages/superise/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/superise/tsconfig.commonjs.json b/packages/superise/tsconfig.commonjs.json new file mode 100644 index 000000000..76a25e98b --- /dev/null +++ b/packages/superise/tsconfig.commonjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist.commonjs" + } +} diff --git a/packages/superise/tsconfig.json b/packages/superise/tsconfig.json new file mode 100644 index 000000000..df22faeca --- /dev/null +++ b/packages/superise/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "./dist", + } +} diff --git a/packages/superise/typedoc.json b/packages/superise/typedoc.json new file mode 100644 index 000000000..b663440e9 --- /dev/null +++ b/packages/superise/typedoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts", "./src/advanced.ts"], + "extends": ["../../typedoc.base.json"], + "name": "@ckb-ccc superise" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78369fd1c..0b68651bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,6 +70,9 @@ importers: '@ckb-ccc/shell': specifier: workspace:* version: link:../shell + '@ckb-ccc/superise': + specifier: workspace:* + version: link:../superise '@ckb-ccc/uni-sat': specifier: workspace:* version: link:../uni-sat @@ -1083,6 +1086,46 @@ importers: specifier: ^8.41.0 version: 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + packages/superise: + dependencies: + '@ckb-ccc/core': + specifier: workspace:* + version: link:../core + '@superise/bridge-api-types': + specifier: ^0.0.5 + version: 0.0.5(typescript@5.9.2) + devDependencies: + '@eslint/js': + specifier: ^9.34.0 + version: 9.34.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/tests: devDependencies: '@ckb-ccc/ccc': @@ -3790,6 +3833,11 @@ packages: '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} + '@superise/bridge-api-types@0.0.5': + resolution: {integrity: sha512-DPvLepID27N7LoSSKqsrPOAtR3GBDRb2dm3O3rRczUT+CiogawfGRd4CAPyIus5VXgRdqgtqnNzVFl7a/EXMMA==} + peerDependencies: + typescript: ^5 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -14001,6 +14049,10 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 + '@superise/bridge-api-types@0.0.5(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 From c77501d2d7653fef141a72d9bd85c66a4b1d9905 Mon Sep 17 00:00:00 2001 From: jc Date: Tue, 18 Nov 2025 13:47:58 +0800 Subject: [PATCH 2/5] fix: gemini CR issues --- packages/ccc/src/signersController.ts | 2 +- packages/superise/src/barrel.ts | 4 ++-- packages/superise/src/ckb/index.ts | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/ccc/src/signersController.ts b/packages/ccc/src/signersController.ts index 51c7ded41..b92b17161 100644 --- a/packages/ccc/src/signersController.ts +++ b/packages/ccc/src/signersController.ts @@ -14,7 +14,7 @@ import { METAMASK_SVG } from "./assets/metamask.svg.js"; import { NOSTR_SVG } from "./assets/nostr.svg.js"; import { OKX_SVG } from "./assets/okx.svg.js"; import { REI_SVG } from "./assets/rei.svg.js"; -import { SUPERISE_SVG } from "./assets/superise.svg"; +import { SUPERISE_SVG } from "./assets/superise.svg.js"; import { UNI_SAT_SVG } from "./assets/uni-sat.svg.js"; import { UTXO_GLOBAL_SVG } from "./assets/utxo-global.svg.js"; diff --git a/packages/superise/src/barrel.ts b/packages/superise/src/barrel.ts index 81e1f809e..b60ec7b8e 100644 --- a/packages/superise/src/barrel.ts +++ b/packages/superise/src/barrel.ts @@ -1,2 +1,2 @@ -export * from "./ckb"; -export * from "./signersFactory"; +export * from "./ckb/index.js"; +export * from "./signersFactory.js"; diff --git a/packages/superise/src/ckb/index.ts b/packages/superise/src/ckb/index.ts index d58082e57..4f8261ffe 100644 --- a/packages/superise/src/ckb/index.ts +++ b/packages/superise/src/ckb/index.ts @@ -39,7 +39,9 @@ export class CkbSigner extends ccc.Signer { if (!connection) return; try { this.connection = JSON.parse(connection) as CkbConnection; - } catch {} + } catch (error) { + console.error("Failed to restore superise connection:", error); + } } private async getConnection() { @@ -141,12 +143,14 @@ export class CkbSigner extends ccc.Signer { metadata: SignCkbHashAllMetadata, ) { const txHash = ccc.Transaction.from(tx).hash(); - this._uiMetadataMap = { [txHash]: metadata }; + this._uiMetadataMap[txHash] = metadata; } private getUiMetadataFromTx(tx: ccc.TransactionLike) { const txHash = ccc.Transaction.from(tx).hash(); - return this._uiMetadataMap[txHash]; + const metadata = this._uiMetadataMap[txHash]; + delete this._uiMetadataMap[txHash]; + return metadata; } override async prepareTransaction(txLike: ccc.TransactionLike) { From e5b73f18535acbdb0e34feeb476a03008e58aedf Mon Sep 17 00:00:00 2001 From: jc Date: Wed, 19 Nov 2025 09:43:42 +0800 Subject: [PATCH 3/5] fix(superise): use type-only imports for bridge API types --- packages/superise/package.json | 10 ---------- packages/superise/src/advanced.ts | 1 - packages/superise/src/advancedBarrel.ts | 5 ----- packages/superise/src/ckb/index.ts | 16 ++++++---------- packages/superise/src/signersFactory.ts | 8 +++++--- 5 files changed, 11 insertions(+), 29 deletions(-) delete mode 100644 packages/superise/src/advanced.ts delete mode 100644 packages/superise/src/advancedBarrel.ts diff --git a/packages/superise/package.json b/packages/superise/package.json index 8963b052f..90971581c 100644 --- a/packages/superise/package.json +++ b/packages/superise/package.json @@ -23,16 +23,6 @@ "import": "./dist/barrel.js", "require": "./dist.commonjs/barrel.js", "default": "./dist.commonjs/barrel.js" - }, - "./advancedBarrel": { - "import": "./dist/advancedBarrel.js", - "require": "./dist.commonjs/advancedBarrel.js", - "default": "./dist.commonjs/advancedBarrel.js" - }, - "./advanced": { - "import": "./dist/advanced.js", - "require": "./dist.commonjs/advanced.js", - "default": "./dist.commonjs/advanced.js" } }, "scripts": { diff --git a/packages/superise/src/advanced.ts b/packages/superise/src/advanced.ts deleted file mode 100644 index 5bf9d080e..000000000 --- a/packages/superise/src/advanced.ts +++ /dev/null @@ -1 +0,0 @@ -export * as SupeRISEA from "./advancedBarrel.js"; diff --git a/packages/superise/src/advancedBarrel.ts b/packages/superise/src/advancedBarrel.ts deleted file mode 100644 index 9fe98bff9..000000000 --- a/packages/superise/src/advancedBarrel.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type SupeRISE from "@superise/bridge-api-types"; - -export type Bridge = SupeRISE.Bridge; -export type CkbConnection = SupeRISE.CkbConnection; -export type SignCkbHashAllMetadata = SupeRISE.SignCkbHashAllMetadata; diff --git a/packages/superise/src/ckb/index.ts b/packages/superise/src/ckb/index.ts index 4f8261ffe..77c970c6e 100644 --- a/packages/superise/src/ckb/index.ts +++ b/packages/superise/src/ckb/index.ts @@ -1,9 +1,5 @@ import { ccc } from "@ckb-ccc/core"; -import type { - Bridge, - CkbConnection, - SignCkbHashAllMetadata, -} from "../advancedBarrel"; +import type SupeRISE from '@superise/bridge-api-types' export class CkbSigner extends ccc.Signer { get type() { @@ -16,12 +12,12 @@ export class CkbSigner extends ccc.Signer { private connectionStorageKey = "superise-ckb-connection"; - private connection?: CkbConnection; + private connection?: SupeRISE.CkbConnection; - private _uiMetadataMap: Record = {}; + private _uiMetadataMap: Record = {}; constructor( - private readonly bridge: Bridge, + private readonly bridge: SupeRISE.Bridge, client: ccc.Client, ) { super(client); @@ -38,7 +34,7 @@ export class CkbSigner extends ccc.Signer { const connection = localStorage.getItem(this.connectionStorageKey); if (!connection) return; try { - this.connection = JSON.parse(connection) as CkbConnection; + this.connection = JSON.parse(connection) as SupeRISE.CkbConnection; } catch (error) { console.error("Failed to restore superise connection:", error); } @@ -140,7 +136,7 @@ export class CkbSigner extends ccc.Signer { public setUiMetadataForTx( tx: ccc.TransactionLike, - metadata: SignCkbHashAllMetadata, + metadata: SupeRISE.SignCkbHashAllMetadata, ) { const txHash = ccc.Transaction.from(tx).hash(); this._uiMetadataMap[txHash] = metadata; diff --git a/packages/superise/src/signersFactory.ts b/packages/superise/src/signersFactory.ts index 5f2cd154e..adceb0ce1 100644 --- a/packages/superise/src/signersFactory.ts +++ b/packages/superise/src/signersFactory.ts @@ -1,6 +1,6 @@ import { ccc } from "@ckb-ccc/core"; -import "@superise/bridge-api-types"; -import { CkbSigner } from "./ckb"; +import type SupeRISE from "@superise/bridge-api-types"; +import { CkbSigner } from "./ckb/index.js"; /** * @public @@ -9,7 +9,9 @@ export function getSupeRISESigners( client: ccc.Client, _preferredNetworks?: ccc.NetworkPreference[], ): ccc.SignerInfo[] { - const windowRef = window; + const windowRef = window as { + superise?: SupeRISE.Bridge; + }; if (typeof windowRef.superise === "undefined") { return []; From d03cb33270d408298f12dbd9ba985609dba31fbf Mon Sep 17 00:00:00 2001 From: jc Date: Fri, 5 Dec 2025 13:27:28 +0800 Subject: [PATCH 4/5] fix(superise): implement SupeRISE-specific message signing and verification --- packages/ccc/package.json | 4 +- .../core/src/signer/ckb/verifySuperise.ts | 40 +++++++++ packages/core/src/signer/signer/index.ts | 8 ++ packages/superise/package.json | 3 +- packages/superise/src/advanced.ts | 1 + packages/superise/src/advancedBarrel.ts | 17 ++++ packages/superise/src/ckb/index.ts | 86 ++++++------------- packages/superise/src/signersFactory.ts | 4 +- pnpm-lock.yaml | 12 --- 9 files changed, 96 insertions(+), 79 deletions(-) create mode 100644 packages/core/src/signer/ckb/verifySuperise.ts create mode 100644 packages/superise/src/advanced.ts create mode 100644 packages/superise/src/advancedBarrel.ts diff --git a/packages/ccc/package.json b/packages/ccc/package.json index bece98a8f..e4750d899 100644 --- a/packages/ccc/package.json +++ b/packages/ccc/package.json @@ -62,10 +62,10 @@ "@ckb-ccc/okx": "workspace:*", "@ckb-ccc/rei": "workspace:*", "@ckb-ccc/shell": "workspace:*", + "@ckb-ccc/superise": "workspace:*", "@ckb-ccc/uni-sat": "workspace:*", "@ckb-ccc/utxo-global": "workspace:*", - "@ckb-ccc/xverse": "workspace:*", - "@ckb-ccc/superise": "workspace:*" + "@ckb-ccc/xverse": "workspace:*" }, "packageManager": "pnpm@10.8.1" } diff --git a/packages/core/src/signer/ckb/verifySuperise.ts b/packages/core/src/signer/ckb/verifySuperise.ts new file mode 100644 index 000000000..46e726187 --- /dev/null +++ b/packages/core/src/signer/ckb/verifySuperise.ts @@ -0,0 +1,40 @@ +import { secp256k1 } from "@noble/curves/secp256k1"; +import { BytesLike, bytesFrom } from "../../bytes/index.js"; +import { hashCkb } from "../../hasher/index.js"; +import { Hex, hexFrom } from "../../hex/index.js"; +import { numFrom } from "../../num/index.js"; + +const SUPERISE_MESSAGE_PREFIX = "\x19SupeRISE Message:\n"; + +/** + * @public + */ +function messageHashCkbSuperise(message: string | BytesLike): Hex { + const msg = typeof message === "string" ? message : hexFrom(message); + const buffer = bytesFrom( + `${SUPERISE_MESSAGE_PREFIX}${msg.length}${msg}`, + "utf8", + ); + return hashCkb(buffer); +} + +/** + * @public + */ +export function verifyMessageSuperise( + message: string | BytesLike, + signature: string, + publicKey: string, +): boolean { + const signatureBytes = bytesFrom(signature); + return secp256k1.verify( + new secp256k1.Signature( + numFrom(signatureBytes.slice(0, 32)), + numFrom(signatureBytes.slice(32, 64)), + ) + .addRecoveryBit(Number(numFrom(signatureBytes.slice(64, 65)))) + .toBytes(), + bytesFrom(messageHashCkbSuperise(message)), + bytesFrom(publicKey), + ); +} diff --git a/packages/core/src/signer/signer/index.ts b/packages/core/src/signer/signer/index.ts index 1522b3353..e985a2684 100644 --- a/packages/core/src/signer/signer/index.ts +++ b/packages/core/src/signer/signer/index.ts @@ -13,6 +13,7 @@ import { Num } from "../../num/index.js"; import { verifyMessageBtcEcdsa } from "../btc/index.js"; import { verifyMessageCkbSecp256k1 } from "../ckb/verifyCkbSecp256k1.js"; import { verifyMessageJoyId } from "../ckb/verifyJoyId.js"; +import { verifyMessageSuperise } from "../ckb/verifySuperise.js"; import { verifyMessageDogeEcdsa } from "../doge/verify.js"; import { verifyMessageEvmPersonal } from "../evm/verify.js"; import { verifyMessageNostrEvent } from "../nostr/verify.js"; @@ -28,6 +29,7 @@ export enum SignerSignType { NostrEvent = "NostrEvent", CkbSecp256k1 = "CkbSecp256k1", DogeEcdsa = "DogeEcdsa", + SupeRISE = "SupeRISE", } /** @@ -152,6 +154,12 @@ export abstract class Signer { signature.signature, signature.identity, ); + case SignerSignType.SupeRISE: + return verifyMessageSuperise( + message, + signature.signature, + signature.identity, + ); case SignerSignType.Unknown: throw new Error("Unknown signer sign type"); } diff --git a/packages/superise/package.json b/packages/superise/package.json index 90971581c..df6492def 100644 --- a/packages/superise/package.json +++ b/packages/superise/package.json @@ -46,8 +46,7 @@ "access": "public" }, "dependencies": { - "@ckb-ccc/core": "workspace:*", - "@superise/bridge-api-types": "^0.0.5" + "@ckb-ccc/core": "workspace:*" }, "packageManager": "pnpm@10.8.1" } diff --git a/packages/superise/src/advanced.ts b/packages/superise/src/advanced.ts new file mode 100644 index 000000000..5bf9d080e --- /dev/null +++ b/packages/superise/src/advanced.ts @@ -0,0 +1 @@ +export * as SupeRISEA from "./advancedBarrel.js"; diff --git a/packages/superise/src/advancedBarrel.ts b/packages/superise/src/advancedBarrel.ts new file mode 100644 index 000000000..300bc39b4 --- /dev/null +++ b/packages/superise/src/advancedBarrel.ts @@ -0,0 +1,17 @@ +export interface CkbConnection { + publicKey: string; + address: string; +} + +export interface Bridge { + version: string; + + connectCkb: () => Promise; + + signCkbMessage: (message: string) => Promise<{ signature: string }>; + + signCkbTransaction: ( + transaction: string, + witnessIndexes: number[], + ) => Promise<{ signedTransaction: string }>; +} diff --git a/packages/superise/src/ckb/index.ts b/packages/superise/src/ckb/index.ts index 77c970c6e..7480d3a0d 100644 --- a/packages/superise/src/ckb/index.ts +++ b/packages/superise/src/ckb/index.ts @@ -1,5 +1,5 @@ import { ccc } from "@ckb-ccc/core"; -import type SupeRISE from '@superise/bridge-api-types' +import type { Bridge, CkbConnection } from "../advancedBarrel"; export class CkbSigner extends ccc.Signer { get type() { @@ -12,12 +12,10 @@ export class CkbSigner extends ccc.Signer { private connectionStorageKey = "superise-ckb-connection"; - private connection?: SupeRISE.CkbConnection; - - private _uiMetadataMap: Record = {}; + private connection?: CkbConnection; constructor( - private readonly bridge: SupeRISE.Bridge, + private readonly bridge: Bridge, client: ccc.Client, ) { super(client); @@ -34,7 +32,7 @@ export class CkbSigner extends ccc.Signer { const connection = localStorage.getItem(this.connectionStorageKey); if (!connection) return; try { - this.connection = JSON.parse(connection) as SupeRISE.CkbConnection; + this.connection = JSON.parse(connection) as CkbConnection; } catch (error) { console.error("Failed to restore superise connection:", error); } @@ -61,6 +59,7 @@ export class CkbSigner extends ccc.Signer { override async disconnect() { this.connection = undefined; + localStorage.removeItem(this.connectionStorageKey); } override async getInternalAddress() { @@ -83,8 +82,7 @@ export class CkbSigner extends ccc.Signer { } override async signMessageRaw(message: string) { - const signMessage = `Nervos Message:${message}`; - const sign = await this.bridge.signCkbMessage(signMessage); + const sign = await this.bridge.signCkbMessage(message); return ccc.hexFrom(sign.signature); } @@ -134,21 +132,6 @@ export class CkbSigner extends ccc.Signer { return scripts; } - public setUiMetadataForTx( - tx: ccc.TransactionLike, - metadata: SupeRISE.SignCkbHashAllMetadata, - ) { - const txHash = ccc.Transaction.from(tx).hash(); - this._uiMetadataMap[txHash] = metadata; - } - - private getUiMetadataFromTx(tx: ccc.TransactionLike) { - const txHash = ccc.Transaction.from(tx).hash(); - const metadata = this._uiMetadataMap[txHash]; - delete this._uiMetadataMap[txHash]; - return metadata; - } - override async prepareTransaction(txLike: ccc.TransactionLike) { const tx = ccc.Transaction.from(txLike); @@ -166,47 +149,28 @@ export class CkbSigner extends ccc.Signer { override async signOnlyTransaction(txLike: ccc.TransactionLike) { const tx = ccc.Transaction.from(txLike); - const metadata = this.getUiMetadataFromTx(tx); - - const signatureCache = new Map(); - - for (const { script } of await this.getRelatedScripts(tx)) { - const info = await tx.getSignHashInfo(script, this.client); - if (!info) { - return tx; - } - - const { message, position } = info; - let signature!: string; - if (signatureCache.has(message)) { - signature = signatureCache.get(message)!; - } else { - const sign = await this.bridge.signCkbHashAll( - message.replace(/^0x/, ""), - metadata, + const witnessIndexes = await ccc.reduceAsync( + await this.getRelatedScripts(tx), + async (indexes, scriptInfo) => { + const index = await tx.findInputIndexByLock( + scriptInfo.script, + this.client, ); - signature = sign.signature; - signatureCache.set(message, signature); - } - - const witness = - tx.getWitnessArgsAt(info.position) ?? ccc.WitnessArgs.from({}); - witness.lock = signature as ccc.Hex; - tx.setWitnessArgsAt(position, witness); - } + if (!index) return; - return tx; - } - - override async signTransaction( - tx: ccc.TransactionLike, - ): Promise { - const preparedTx = await this.prepareTransaction(tx); - return this.signOnlyTransaction(preparedTx); - } + indexes.push(index); + }, + [] as number[], + ); - override async sendTransaction(tx: ccc.TransactionLike): Promise { - return this.client.sendTransaction(await this.signTransaction(tx)); + const result = await this.bridge.signCkbTransaction( + ccc.stringify(tx), + witnessIndexes, + ); + const signedTx = JSON.parse( + result.signedTransaction, + ) as ccc.TransactionLike; + return ccc.Transaction.from(signedTx); } } diff --git a/packages/superise/src/signersFactory.ts b/packages/superise/src/signersFactory.ts index adceb0ce1..dc16a5436 100644 --- a/packages/superise/src/signersFactory.ts +++ b/packages/superise/src/signersFactory.ts @@ -1,5 +1,5 @@ import { ccc } from "@ckb-ccc/core"; -import type SupeRISE from "@superise/bridge-api-types"; +import type { Bridge } from "./advancedBarrel"; import { CkbSigner } from "./ckb/index.js"; /** @@ -10,7 +10,7 @@ export function getSupeRISESigners( _preferredNetworks?: ccc.NetworkPreference[], ): ccc.SignerInfo[] { const windowRef = window as { - superise?: SupeRISE.Bridge; + superise?: Bridge; }; if (typeof windowRef.superise === "undefined") { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b68651bc..3d01d660c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1091,9 +1091,6 @@ importers: '@ckb-ccc/core': specifier: workspace:* version: link:../core - '@superise/bridge-api-types': - specifier: ^0.0.5 - version: 0.0.5(typescript@5.9.2) devDependencies: '@eslint/js': specifier: ^9.34.0 @@ -3833,11 +3830,6 @@ packages: '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} - '@superise/bridge-api-types@0.0.5': - resolution: {integrity: sha512-DPvLepID27N7LoSSKqsrPOAtR3GBDRb2dm3O3rRczUT+CiogawfGRd4CAPyIus5VXgRdqgtqnNzVFl7a/EXMMA==} - peerDependencies: - typescript: ^5 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -14049,10 +14041,6 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 - '@superise/bridge-api-types@0.0.5(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 From b5699bde2a5e293c6047724d50abde5b2c2a2275 Mon Sep 17 00:00:00 2001 From: jc Date: Fri, 5 Dec 2025 15:21:01 +0800 Subject: [PATCH 5/5] fix(superise): correct falsy check for witness index --- packages/superise/src/ckb/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superise/src/ckb/index.ts b/packages/superise/src/ckb/index.ts index 7480d3a0d..858ef3ae5 100644 --- a/packages/superise/src/ckb/index.ts +++ b/packages/superise/src/ckb/index.ts @@ -157,7 +157,7 @@ export class CkbSigner extends ccc.Signer { scriptInfo.script, this.client, ); - if (!index) return; + if (typeof index !== "number") return; indexes.push(index); },