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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/ccc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @ckb-ccc/ccc

## 1.1.22
### Patch Changes

- Updated dependencies []:
- @ckb-ccc/shell@1.1.22

## 1.1.21
### Patch Changes

Expand Down
2 changes: 1 addition & 1 deletion packages/ccc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ckb-ccc/ccc",
"version": "1.1.21",
"version": "1.1.22",
"description": "CCC - CKBer's Codebase. Common Chains Connector.",
"author": "Hanssen0 <hanssen0@hanssen0.com>",
"license": "MIT",
Expand Down
6 changes: 6 additions & 0 deletions packages/ckb-ccc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# ckb-ccc

## 1.0.30
### Patch Changes

- Updated dependencies []:
- @ckb-ccc/ccc@1.1.22

## 1.0.29
### Patch Changes

Expand Down
2 changes: 1 addition & 1 deletion packages/ckb-ccc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ckb-ccc",
"version": "1.0.29",
"version": "1.0.30",
"description": "CCC - CKBer's Codebase. Common Chains Connector.",
"author": "Hanssen0 <hanssen0@hanssen0.com>",
"license": "MIT",
Expand Down
13 changes: 13 additions & 0 deletions packages/connector-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# @ckb-ccc/connector-react

## 1.0.31
### Patch Changes



- [#335](https://github.com/ckb-devrel/ccc/pull/335) [`ea7e626`](https://github.com/ckb-devrel/ccc/commit/ea7e626a81ad4fb78142f0d948843de84478debf) Thanks [@Hanssen0](https://github.com/Hanssen0)! - chore: bump version of react

## 1.0.30
### Patch Changes

- Updated dependencies []:
- @ckb-ccc/connector@1.0.30

## 1.0.29
### Patch Changes

Expand Down
4 changes: 2 additions & 2 deletions packages/connector-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ckb-ccc/connector-react",
"version": "1.0.29",
"version": "1.0.31",
"description": "CCC - CKBer's Codebase. Common Chains Connector UI Component for React",
"author": "Hanssen0 <hanssen0@hanssen0.com>",
"license": "MIT",
Expand All @@ -25,7 +25,7 @@
},
"devDependencies": {
"@eslint/js": "^9.34.0",
"@types/react": "^19.1.12",
"@types/react": "^19.2.7",
"eslint": "^9.34.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
Expand Down
6 changes: 6 additions & 0 deletions packages/connector/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @ckb-ccc/connector

## 1.0.30
### Patch Changes

- Updated dependencies []:
- @ckb-ccc/ccc@1.1.22

## 1.0.29
### Patch Changes

Expand Down
2 changes: 1 addition & 1 deletion packages/connector/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ckb-ccc/connector",
"version": "1.0.29",
"version": "1.0.30",
"description": "CCC - CKBer's Codebase. Common Chains Connector UI",
"author": "Hanssen0 <hanssen0@hanssen0.com>",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/signer/btc/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./psbt.js";
export * from "./signerBtc.js";
export * from "./signerBtcPublicKeyReadonly.js";
export * from "./verify.js";
56 changes: 56 additions & 0 deletions packages/core/src/signer/btc/psbt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Options for signing a PSBT (Partially Signed Bitcoin Transaction)
*/
export type SignPsbtOptions = {
/**
* Whether to finalize the PSBT after signing.
* Default is true.
*/
autoFinalized?: boolean;
/**
* Array of inputs to sign
*/
toSignInputs?: ToSignInput[];
};

/**
* Specification for an input to sign in a PSBT.
* Must specify at least one of: address or pubkey.
*/
export type ToSignInput = {
/**
* Which input to sign (index in the PSBT inputs array)
*/
index: number;
/**
* (Optional) Sighash types to use for signing.
*/
sighashTypes?: number[];
/**
* (Optional) When signing and unlocking Taproot addresses, the tweakSigner is used by default
* for signature generation. Setting this to true allows for signing with the original private key.
* Default value is false.
*/
disableTweakSigner?: boolean;
} & (
| {
/**
* The address whose corresponding private key to use for signing.
*/
address: string;
/**
* The public key whose corresponding private key to use for signing.
*/
publicKey?: string;
}
| {
/**
* The address whose corresponding private key to use for signing.
*/
address?: string;
/**
* The public key whose corresponding private key to use for signing.
*/
publicKey: string;
}
);
Comment on lines +35 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The ToSignInput type has duplicated properties and comments for address and publicKey. This can be refactored to improve readability and maintainability by defining the properties once and then expressing the constraint that at least one of them must be present.

} & {
  /**
   * The address whose corresponding private key to use for signing.
   */
  address?: string;
  /**
   * The public key whose corresponding private key to use for signing.
   */
  publicKey?: string;
} & ({ address: string } | { publicKey: string });

55 changes: 55 additions & 0 deletions packages/core/src/signer/btc/signerBtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { KnownScript } from "../../client/index.js";
import { HexLike, hexFrom } from "../../hex/index.js";
import { numToBytes } from "../../num/index.js";
import { Signer, SignerSignType, SignerType } from "../signer/index.js";
import { SignPsbtOptions } from "./psbt.js";
import { btcEcdsaPublicKeyHash } from "./verify.js";

/**
Expand All @@ -22,6 +23,32 @@ export abstract class SignerBtc extends Signer {
return SignerSignType.BtcEcdsa;
}

/**
* Whether the wallet supports a single call to sign + broadcast (combined flow).
* Default false; override in implementations like Xverse/JoyID.
*/
get supportsSingleCallSignAndBroadcast(): boolean {
return false;
}

/**
* Sign and broadcast a PSBT in one call when supported, otherwise falls back
* to sign then push. Prefer this over manual sign+push to avoid double popups.
*/
async signAndPushPsbt(
psbtHex: string,
options?: SignPsbtOptions,
): Promise<string> {
if (this.supportsSingleCallSignAndBroadcast) {
// Wallet handles sign+broadcast internally (e.g., Xverse/JoyID)
return this.pushPsbt(psbtHex, options);
}

// Split-mode wallets: sign first, then broadcast
const signedPsbt = await this.signPsbt(psbtHex, options);
return this.pushPsbt(signedPsbt, options);
}
Comment on lines +38 to +50
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve type safety and consistency across the codebase, I recommend changing the return types of signAndPushPsbt, signPsbt, and pushPsbt from Promise<string> to Promise<Hex>.

The Hex type ensures that hexadecimal strings are 0x-prefixed, which is a convention used elsewhere in the project (e.g., hexFrom). This change would make the interfaces more explicit about the expected format of return values (signed PSBT hex and transaction ID).

For example, signPsbt would become:

abstract signPsbt(
  psbtHex: string,
  options?: SignPsbtOptions,
): Promise<Hex>;

This would require updating the implementing classes. For instance, in the Xverse signer, you could then remove .slice(2) from the return statement in signPsbt, making the code cleaner and less prone to errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bitcoin tx IDs and PSBTs are bare hex strings with no 0x prefix by Bitcoin ecosystem convention.


/**
* Gets the Bitcoin account associated with the signer.
*
Expand Down Expand Up @@ -123,4 +150,32 @@ export abstract class SignerBtc extends Signer {
tx.setWitnessArgsAt(info.position, witness);
return tx;
}

/**
* Signs a Partially Signed Bitcoin Transaction (PSBT).
*
* @param psbtHex - The hex string of PSBT to sign
* @param options - Options for signing the PSBT
* @returns A promise that resolves to the signed PSBT hex string
*/
abstract signPsbt(
psbtHex: string,
options?: SignPsbtOptions,
): Promise<string>;

/**
* Pushes a PSBT to the Bitcoin network.
*
* For wallets that support a single call for signing and broadcasting (where `supportsSingleCallSignAndBroadcast` is true),
* this method takes an **unsigned** PSBT, signs it, and broadcasts it.
* For other wallets, this method takes a **signed** PSBT and only broadcasts it.
*
* @param psbtHex - The hex string of the PSBT to push. Can be signed or unsigned depending on the wallet's capabilities.
* @param options - Options for signing the PSBT. Only used by wallets that perform signing in this step.
* @returns A promise that resolves to the transaction ID.
*/
abstract pushPsbt(
psbtHex: string,
options?: SignPsbtOptions,
): Promise<string>;
}
9 changes: 9 additions & 0 deletions packages/core/src/signer/btc/signerBtcPublicKeyReadonly.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Client } from "../../client/index.js";
import { Hex, HexLike, hexFrom } from "../../hex/index.js";
import { SignPsbtOptions } from "./psbt.js";
import { SignerBtc } from "./signerBtc.js";

/**
Expand Down Expand Up @@ -70,4 +71,12 @@ export class SignerBtcPublicKeyReadonly extends SignerBtc {
async getBtcPublicKey(): Promise<Hex> {
return this.publicKey;
}

async signPsbt(_: string, __?: SignPsbtOptions): Promise<string> {
throw new Error("Read-only signer does not support signPsbt");
}

async pushPsbt(_: string, __?: SignPsbtOptions): Promise<string> {
throw new Error("Read-only signer does not support pushPsbt");
}
}
7 changes: 5 additions & 2 deletions packages/demo/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -11,7 +13,8 @@ const compat = new FlatCompat({
});

export default [
...compat.extends("next/core-web-vitals", "next/typescript"),
...nextVitals,
...nextTs,
{
ignores: [
"node_modules/**",
Expand All @@ -36,4 +39,4 @@ export default [
},
},
eslintPluginPrettierRecommended,
];
];
14 changes: 7 additions & 7 deletions packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
},
"dependencies": {
"@lit/react": "^1.0.8",
"@next/third-parties": "^15.5.2",
"@next/third-parties": "^16.0.10",
"@uiw/react-json-view": "2.0.0-alpha.37",
"lucide-react": "^0.542.0",
"next": "15.5.2",
"react": "^19.1.1",
"react-dom": "^19.1.1"
"next": "16.0.10",
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@ckb-ccc/connector-react": "workspace:*",
Expand All @@ -40,10 +40,10 @@
"@scure/bip39": "^2.0.0",
"@tailwindcss/postcss": "^4.1.12",
"@types/node": "^24.3.0",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.8",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"eslint": "^9.34.0",
"eslint-config-next": "15.5.2",
"eslint-config-next": "16.0.10",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"postcss": "^8.5.6",
Expand Down
42 changes: 31 additions & 11 deletions packages/demo/src/app/connected/(tools)/Sign/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ButtonsPanel } from "@/src/components/ButtonsPanel";
import { Textarea } from "@/src/components/Textarea";
import { useApp } from "@/src/context";
import { ccc } from "@ckb-ccc/connector-react";
import { CopyIcon } from "lucide-react";
import { useState } from "react";

export default function Sign() {
Expand All @@ -21,6 +22,24 @@ export default function Sign() {
placeholder="Message to sign and verify"
state={[messageToSign, setMessageToSign]}
/>
<Textarea
label={
<>
Signature
<button
className="px-2 py-1"
onClick={() => {
window.navigator.clipboard.writeText(signature);
log("Signature copied");
}}
>
<CopyIcon className="text-gray-600" size="0.8em" />
</button>
</>
}
placeholder="Signature to verify"
state={[signature, setSignature]}
/>
<ButtonsPanel>
<Button
onClick={async () => {
Expand All @@ -29,24 +48,25 @@ export default function Sign() {
}
const sig = JSON.stringify(await signer.signMessage(messageToSign));
setSignature(sig);
log("Signature:", sig);
}}
>
Sign
</Button>
<Button
className="ml-2"
onClick={async () => {
if (
!(await ccc.Signer.verifyMessage(
messageToSign,
JSON.parse(signature),
))
) {
error("Invalid");
return;
}
log("Valid");
try {
if (
await ccc.Signer.verifyMessage(
messageToSign,
JSON.parse(signature),
)
) {
log("Valid");
return;
}
} catch (_e) {}
error("Invalid");
}}
>
Verify
Expand Down
6 changes: 4 additions & 2 deletions packages/demo/src/components/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from "react";

export function Textarea(
props: React.ComponentPropsWithoutRef<"textarea"> & {
state: [string, (v: string) => void];
label?: string;
label?: React.ReactNode;
},
) {
return (
<div className={`relative bg-white/75 p-4 ${props.className ?? ""}`}>
{props.label ? (
<label className="text-sm">{props.label}</label>
<label className="flex items-center text-sm">{props.label}</label>
) : undefined}
<textarea
{...props}
Expand Down
Loading