@ckb-ccc/xverse 1.0.31 → 1.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @ckb-ccc/xverse
2
2
 
3
+ ## 1.0.33
4
+ ### Patch Changes
5
+
6
+
7
+
8
+ - [#346](https://github.com/ckb-devrel/ccc/pull/346) [`a803d5f`](https://github.com/ckb-devrel/ccc/commit/a803d5fba8d0e082c6aba14db156856025402e72) Thanks [@fghdotio](https://github.com/fghdotio)! - feat(core): add BTC PSBT signing support
9
+
10
+ - Add `SignerBtc.signPsbt()`, `signAndBroadcastPsbt()`, and `broadcastPsbt()` for signing and broadcasting PSBTs
11
+ - Add `SignPsbtOptions` and `InputToSign` for configuring PSBT signing
12
+
13
+
14
+ - [#379](https://github.com/ckb-devrel/ccc/pull/379) [`f01a05b`](https://github.com/ckb-devrel/ccc/commit/f01a05bab332d9f4e0cf7f84aecfd688f8e9f346) Thanks [@Hanssen0](https://github.com/Hanssen0)! - chore: bump pnpm to v11.8.0
15
+
16
+ - Updated dependencies [[`1148a5c`](https://github.com/ckb-devrel/ccc/commit/1148a5c403cde985fb4ba713ccfa0c163d287174), [`bf0f8d8`](https://github.com/ckb-devrel/ccc/commit/bf0f8d8ca011e627821445a10bc38519510e5b9d), [`a803d5f`](https://github.com/ckb-devrel/ccc/commit/a803d5fba8d0e082c6aba14db156856025402e72), [`bf0f8d8`](https://github.com/ckb-devrel/ccc/commit/bf0f8d8ca011e627821445a10bc38519510e5b9d), [`6727ffe`](https://github.com/ckb-devrel/ccc/commit/6727ffe05f60e6bfb2060a565c19acb0fd0f375e), [`f01a05b`](https://github.com/ckb-devrel/ccc/commit/f01a05bab332d9f4e0cf7f84aecfd688f8e9f346), [`a526890`](https://github.com/ckb-devrel/ccc/commit/a5268909ea9d61c4e2f5187a43e2318327b27cae), [`4bb3d9d`](https://github.com/ckb-devrel/ccc/commit/4bb3d9d2ef36b3ee8820036625abd9befb1980c4), [`9f7ecb6`](https://github.com/ckb-devrel/ccc/commit/9f7ecb6ab8db9c6866dad029f2888e1e5cfcbe7d)]:
17
+ - @ckb-ccc/core@1.13.0
18
+
19
+ ## 1.0.32
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [[`a96dec6`](https://github.com/ckb-devrel/ccc/commit/a96dec6d0517113391b0edc510f1af821a45d5a8)]:
23
+ - @ckb-ccc/core@1.12.5
24
+
3
25
  ## 1.0.31
4
26
  ### Patch Changes
5
27
 
package/dist/signer.d.ts CHANGED
@@ -43,5 +43,39 @@ export declare class Signer extends ccc.SignerBtc {
43
43
  * @returns A promise that resolves to the signed message.
44
44
  */
45
45
  signMessageRaw(message: string | ccc.BytesLike): Promise<string>;
46
+ /**
47
+ * Build default inputsToSign for all unsigned inputs
48
+ */
49
+ private buildDefaultinputsToSign;
50
+ private prepareSignPsbtParams;
51
+ /**
52
+ * Signs a PSBT using Xverse wallet.
53
+ *
54
+ * @param psbtHex - The hex string of PSBT to sign.
55
+ * @param options - Options for signing the PSBT
56
+ * @returns A promise that resolves to the signed PSBT as a Hex string
57
+ *
58
+ * @remarks
59
+ * Xverse accepts:
60
+ * - psbt: A string representing the PSBT to sign, encoded in base64
61
+ * - signInputs: A Record<string, number[]> where:
62
+ * - keys are the addresses to use for signing
63
+ * - values are the indexes of the inputs to sign with each address
64
+ *
65
+ * Xverse returns:
66
+ * - psbt: The base64 encoded signed PSBT
67
+ *
68
+ * @see https://docs.xverse.app/sats-connect/bitcoin-methods/signpsbt
69
+ */
70
+ signPsbt(psbtHex: ccc.HexLike, options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex>;
71
+ /**
72
+ * Broadcasts a PSBT to the Bitcoin network.
73
+ *
74
+ * @remarks
75
+ * Xverse does not support broadcasting a signed PSBT directly.
76
+ * It only supports "Sign and Broadcast" as a single atomic operation via `signAndBroadcastPsbt`.
77
+ */
78
+ broadcastPsbt(_psbtHex: ccc.HexLike, _options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex>;
79
+ signAndBroadcastPsbt(psbtHex: ccc.HexLike, options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex>;
46
80
  }
47
81
  //# sourceMappingURL=signer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC,OAAO,EACL,OAAO,EAEP,WAAW,EAQZ,MAAM,qBAAqB,CAAC;AAuB7B;;;GAGG;AACH,qBAAa,MAAO,SAAQ,GAAG,CAAC,SAAS;aAUrB,QAAQ,EAAE,WAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAVpC,OAAO,CAAC,YAAY,CAA2C;IAE/D;;;;OAIG;gBAED,MAAM,EAAE,GAAG,CAAC,MAAM,EACF,QAAQ,EAAE,WAAW,EACpB,iBAAiB,GAAE,GAAG,CAAC,iBAAiB,EAWxD;IAKG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAwBvC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAItC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAIzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAUxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAc5C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;CAcvE"}
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGpC,OAAO,EACL,OAAO,EAEP,WAAW,EAQZ,MAAM,qBAAqB,CAAC;AAuB7B;;;GAGG;AACH,qBAAa,MAAO,SAAQ,GAAG,CAAC,SAAS;aAUrB,QAAQ,EAAE,WAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAVpC,OAAO,CAAC,YAAY,CAA2C;IAE/D;;;;OAIG;gBAED,MAAM,EAAE,GAAG,CAAC,MAAM,EACF,QAAQ,EAAE,WAAW,EACpB,iBAAiB,GAAE,GAAG,CAAC,iBAAiB,EAWxD;IAKG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAwBvC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAItC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAIzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAUxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAc5C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAetE;;OAEG;IACH,OAAO,CAAC,wBAAwB;YAmClB,qBAAqB;IAoCnC;;;;;;;;;;;;;;;;;;OAkBG;IACG,QAAQ,CACZ,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,OAAO,CAAC,EAAE,GAAG,CAAC,mBAAmB,GAChC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAmBnB;;;;;;OAMG;IACG,aAAa,CACjB,QAAQ,EAAE,GAAG,CAAC,OAAO,EACrB,QAAQ,CAAC,EAAE,GAAG,CAAC,mBAAmB,GACjC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAMb,oBAAoB,CACxB,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,OAAO,CAAC,EAAE,GAAG,CAAC,mBAAmB,GAChC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;CAqBpB"}
package/dist/signer.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
+ import { Psbt } from "bitcoinjs-lib";
2
3
  import * as v from "valibot";
3
4
  import { AddressPurpose, MessageSigningProtocols, RpcErrorCode, rpcErrorResponseMessageSchema, rpcSuccessResponseMessageSchema, } from "./advancedBarrel.js";
4
5
  async function checkResponse(response) {
@@ -122,4 +123,103 @@ export class Signer extends ccc.SignerBtc {
122
123
  protocol: MessageSigningProtocols.ECDSA,
123
124
  }))).signature;
124
125
  }
126
+ /**
127
+ * Build default inputsToSign for all unsigned inputs
128
+ */
129
+ buildDefaultinputsToSign(psbtHex, address) {
130
+ const inputsToSign = [];
131
+ try {
132
+ // Collect all unsigned inputs
133
+ const psbt = Psbt.fromHex(psbtHex.slice(2));
134
+ psbt.data.inputs.forEach((input, index) => {
135
+ const isSigned = input.finalScriptSig ||
136
+ input.finalScriptWitness ||
137
+ input.tapKeySig ||
138
+ (input.partialSig && input.partialSig.length > 0) ||
139
+ (input.tapScriptSig && input.tapScriptSig.length > 0);
140
+ if (!isSigned) {
141
+ inputsToSign.push({ index, address });
142
+ }
143
+ });
144
+ // If no unsigned inputs found, the PSBT is already fully signed
145
+ // Let the wallet handle this case (likely a no-op or error)
146
+ }
147
+ catch (error) {
148
+ const errorMessage = error instanceof Error ? error.message : String(error);
149
+ throw new Error(`Failed to parse PSBT hex. Please provide inputsToSign explicitly in options. Original error: ${errorMessage}`);
150
+ }
151
+ return inputsToSign;
152
+ }
153
+ async prepareSignPsbtParams(psbtHex, options) {
154
+ let inputsToSign = options?.inputsToSign;
155
+ if (!inputsToSign || !inputsToSign.length) {
156
+ const address = await this.getBtcAccount();
157
+ inputsToSign = this.buildDefaultinputsToSign(psbtHex, address);
158
+ }
159
+ const psbtBase64 = ccc.bytesTo(psbtHex, "base64");
160
+ const signInputs = inputsToSign.reduce((acc, input) => {
161
+ if (!input.address) {
162
+ throw new Error("Xverse only supports signing with address. Please provide 'address' in inputsToSign.");
163
+ }
164
+ if (acc[input.address]) {
165
+ acc[input.address].push(input.index);
166
+ }
167
+ else {
168
+ acc[input.address] = [input.index];
169
+ }
170
+ return acc;
171
+ }, {});
172
+ return { psbtBase64, signInputs };
173
+ }
174
+ /**
175
+ * Signs a PSBT using Xverse wallet.
176
+ *
177
+ * @param psbtHex - The hex string of PSBT to sign.
178
+ * @param options - Options for signing the PSBT
179
+ * @returns A promise that resolves to the signed PSBT as a Hex string
180
+ *
181
+ * @remarks
182
+ * Xverse accepts:
183
+ * - psbt: A string representing the PSBT to sign, encoded in base64
184
+ * - signInputs: A Record<string, number[]> where:
185
+ * - keys are the addresses to use for signing
186
+ * - values are the indexes of the inputs to sign with each address
187
+ *
188
+ * Xverse returns:
189
+ * - psbt: The base64 encoded signed PSBT
190
+ *
191
+ * @see https://docs.xverse.app/sats-connect/bitcoin-methods/signpsbt
192
+ */
193
+ async signPsbt(psbtHex, options) {
194
+ const { psbtBase64, signInputs } = await this.prepareSignPsbtParams(ccc.hexFrom(psbtHex), options);
195
+ const signedPsbtBase64 = (await checkResponse(this.provider.request("signPsbt", {
196
+ psbt: psbtBase64,
197
+ signInputs,
198
+ broadcast: false,
199
+ }))).psbt;
200
+ return ccc.hexFrom(ccc.bytesFrom(signedPsbtBase64, "base64"));
201
+ }
202
+ /**
203
+ * Broadcasts a PSBT to the Bitcoin network.
204
+ *
205
+ * @remarks
206
+ * Xverse does not support broadcasting a signed PSBT directly.
207
+ * It only supports "Sign and Broadcast" as a single atomic operation via `signAndBroadcastPsbt`.
208
+ */
209
+ async broadcastPsbt(_psbtHex, _options) {
210
+ throw new Error("Xverse does not support broadcasting signed PSBTs directly. Use signAndBroadcastPsbt instead.");
211
+ }
212
+ async signAndBroadcastPsbt(psbtHex, options) {
213
+ // ccc.hexFrom adds 0x prefix, but BTC expects non-0x
214
+ const { psbtBase64, signInputs } = await this.prepareSignPsbtParams(ccc.hexFrom(psbtHex), options);
215
+ const result = await checkResponse(this.provider.request("signPsbt", {
216
+ psbt: psbtBase64,
217
+ signInputs,
218
+ broadcast: true,
219
+ }));
220
+ if (!result.txid) {
221
+ throw new Error("Failed to broadcast PSBT");
222
+ }
223
+ return ccc.hexFrom(result.txid);
224
+ }
125
225
  }
@@ -43,5 +43,39 @@ export declare class Signer extends ccc.SignerBtc {
43
43
  * @returns A promise that resolves to the signed message.
44
44
  */
45
45
  signMessageRaw(message: string | ccc.BytesLike): Promise<string>;
46
+ /**
47
+ * Build default inputsToSign for all unsigned inputs
48
+ */
49
+ private buildDefaultinputsToSign;
50
+ private prepareSignPsbtParams;
51
+ /**
52
+ * Signs a PSBT using Xverse wallet.
53
+ *
54
+ * @param psbtHex - The hex string of PSBT to sign.
55
+ * @param options - Options for signing the PSBT
56
+ * @returns A promise that resolves to the signed PSBT as a Hex string
57
+ *
58
+ * @remarks
59
+ * Xverse accepts:
60
+ * - psbt: A string representing the PSBT to sign, encoded in base64
61
+ * - signInputs: A Record<string, number[]> where:
62
+ * - keys are the addresses to use for signing
63
+ * - values are the indexes of the inputs to sign with each address
64
+ *
65
+ * Xverse returns:
66
+ * - psbt: The base64 encoded signed PSBT
67
+ *
68
+ * @see https://docs.xverse.app/sats-connect/bitcoin-methods/signpsbt
69
+ */
70
+ signPsbt(psbtHex: ccc.HexLike, options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex>;
71
+ /**
72
+ * Broadcasts a PSBT to the Bitcoin network.
73
+ *
74
+ * @remarks
75
+ * Xverse does not support broadcasting a signed PSBT directly.
76
+ * It only supports "Sign and Broadcast" as a single atomic operation via `signAndBroadcastPsbt`.
77
+ */
78
+ broadcastPsbt(_psbtHex: ccc.HexLike, _options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex>;
79
+ signAndBroadcastPsbt(psbtHex: ccc.HexLike, options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex>;
46
80
  }
47
81
  //# sourceMappingURL=signer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC,OAAO,EACL,OAAO,EAEP,WAAW,EAQZ,MAAM,qBAAqB,CAAC;AAuB7B;;;GAGG;AACH,qBAAa,MAAO,SAAQ,GAAG,CAAC,SAAS;aAUrB,QAAQ,EAAE,WAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAVpC,OAAO,CAAC,YAAY,CAA2C;IAE/D;;;;OAIG;gBAED,MAAM,EAAE,GAAG,CAAC,MAAM,EACF,QAAQ,EAAE,WAAW,EACpB,iBAAiB,GAAE,GAAG,CAAC,iBAAiB,EAWxD;IAKG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAwBvC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAItC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAIzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAUxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAc5C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;CAcvE"}
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGpC,OAAO,EACL,OAAO,EAEP,WAAW,EAQZ,MAAM,qBAAqB,CAAC;AAuB7B;;;GAGG;AACH,qBAAa,MAAO,SAAQ,GAAG,CAAC,SAAS;aAUrB,QAAQ,EAAE,WAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAVpC,OAAO,CAAC,YAAY,CAA2C;IAE/D;;;;OAIG;gBAED,MAAM,EAAE,GAAG,CAAC,MAAM,EACF,QAAQ,EAAE,WAAW,EACpB,iBAAiB,GAAE,GAAG,CAAC,iBAAiB,EAWxD;IAKG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAwBvC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAItC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAIzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAUxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAc5C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAetE;;OAEG;IACH,OAAO,CAAC,wBAAwB;YAmClB,qBAAqB;IAoCnC;;;;;;;;;;;;;;;;;;OAkBG;IACG,QAAQ,CACZ,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,OAAO,CAAC,EAAE,GAAG,CAAC,mBAAmB,GAChC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAmBnB;;;;;;OAMG;IACG,aAAa,CACjB,QAAQ,EAAE,GAAG,CAAC,OAAO,EACrB,QAAQ,CAAC,EAAE,GAAG,CAAC,mBAAmB,GACjC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IAMb,oBAAoB,CACxB,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,OAAO,CAAC,EAAE,GAAG,CAAC,mBAAmB,GAChC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;CAqBpB"}
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Signer = void 0;
37
37
  const core_1 = require("@ckb-ccc/core");
38
+ const bitcoinjs_lib_1 = require("bitcoinjs-lib");
38
39
  const v = __importStar(require("valibot"));
39
40
  const advancedBarrel_js_1 = require("./advancedBarrel.js");
40
41
  async function checkResponse(response) {
@@ -158,5 +159,104 @@ class Signer extends core_1.ccc.SignerBtc {
158
159
  protocol: advancedBarrel_js_1.MessageSigningProtocols.ECDSA,
159
160
  }))).signature;
160
161
  }
162
+ /**
163
+ * Build default inputsToSign for all unsigned inputs
164
+ */
165
+ buildDefaultinputsToSign(psbtHex, address) {
166
+ const inputsToSign = [];
167
+ try {
168
+ // Collect all unsigned inputs
169
+ const psbt = bitcoinjs_lib_1.Psbt.fromHex(psbtHex.slice(2));
170
+ psbt.data.inputs.forEach((input, index) => {
171
+ const isSigned = input.finalScriptSig ||
172
+ input.finalScriptWitness ||
173
+ input.tapKeySig ||
174
+ (input.partialSig && input.partialSig.length > 0) ||
175
+ (input.tapScriptSig && input.tapScriptSig.length > 0);
176
+ if (!isSigned) {
177
+ inputsToSign.push({ index, address });
178
+ }
179
+ });
180
+ // If no unsigned inputs found, the PSBT is already fully signed
181
+ // Let the wallet handle this case (likely a no-op or error)
182
+ }
183
+ catch (error) {
184
+ const errorMessage = error instanceof Error ? error.message : String(error);
185
+ throw new Error(`Failed to parse PSBT hex. Please provide inputsToSign explicitly in options. Original error: ${errorMessage}`);
186
+ }
187
+ return inputsToSign;
188
+ }
189
+ async prepareSignPsbtParams(psbtHex, options) {
190
+ let inputsToSign = options?.inputsToSign;
191
+ if (!inputsToSign || !inputsToSign.length) {
192
+ const address = await this.getBtcAccount();
193
+ inputsToSign = this.buildDefaultinputsToSign(psbtHex, address);
194
+ }
195
+ const psbtBase64 = core_1.ccc.bytesTo(psbtHex, "base64");
196
+ const signInputs = inputsToSign.reduce((acc, input) => {
197
+ if (!input.address) {
198
+ throw new Error("Xverse only supports signing with address. Please provide 'address' in inputsToSign.");
199
+ }
200
+ if (acc[input.address]) {
201
+ acc[input.address].push(input.index);
202
+ }
203
+ else {
204
+ acc[input.address] = [input.index];
205
+ }
206
+ return acc;
207
+ }, {});
208
+ return { psbtBase64, signInputs };
209
+ }
210
+ /**
211
+ * Signs a PSBT using Xverse wallet.
212
+ *
213
+ * @param psbtHex - The hex string of PSBT to sign.
214
+ * @param options - Options for signing the PSBT
215
+ * @returns A promise that resolves to the signed PSBT as a Hex string
216
+ *
217
+ * @remarks
218
+ * Xverse accepts:
219
+ * - psbt: A string representing the PSBT to sign, encoded in base64
220
+ * - signInputs: A Record<string, number[]> where:
221
+ * - keys are the addresses to use for signing
222
+ * - values are the indexes of the inputs to sign with each address
223
+ *
224
+ * Xverse returns:
225
+ * - psbt: The base64 encoded signed PSBT
226
+ *
227
+ * @see https://docs.xverse.app/sats-connect/bitcoin-methods/signpsbt
228
+ */
229
+ async signPsbt(psbtHex, options) {
230
+ const { psbtBase64, signInputs } = await this.prepareSignPsbtParams(core_1.ccc.hexFrom(psbtHex), options);
231
+ const signedPsbtBase64 = (await checkResponse(this.provider.request("signPsbt", {
232
+ psbt: psbtBase64,
233
+ signInputs,
234
+ broadcast: false,
235
+ }))).psbt;
236
+ return core_1.ccc.hexFrom(core_1.ccc.bytesFrom(signedPsbtBase64, "base64"));
237
+ }
238
+ /**
239
+ * Broadcasts a PSBT to the Bitcoin network.
240
+ *
241
+ * @remarks
242
+ * Xverse does not support broadcasting a signed PSBT directly.
243
+ * It only supports "Sign and Broadcast" as a single atomic operation via `signAndBroadcastPsbt`.
244
+ */
245
+ async broadcastPsbt(_psbtHex, _options) {
246
+ throw new Error("Xverse does not support broadcasting signed PSBTs directly. Use signAndBroadcastPsbt instead.");
247
+ }
248
+ async signAndBroadcastPsbt(psbtHex, options) {
249
+ // ccc.hexFrom adds 0x prefix, but BTC expects non-0x
250
+ const { psbtBase64, signInputs } = await this.prepareSignPsbtParams(core_1.ccc.hexFrom(psbtHex), options);
251
+ const result = await checkResponse(this.provider.request("signPsbt", {
252
+ psbt: psbtBase64,
253
+ signInputs,
254
+ broadcast: true,
255
+ }));
256
+ if (!result.txid) {
257
+ throw new Error("Failed to broadcast PSBT");
258
+ }
259
+ return core_1.ccc.hexFrom(result.txid);
260
+ }
161
261
  }
162
262
  exports.Signer = Signer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckb-ccc/xverse",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "CCC - CKBer's Codebase. Common Chains Connector's support for Xverse",
5
5
  "author": "Hanssen0 <hanssen0@hanssen0.com>",
6
6
  "license": "MIT",
@@ -51,8 +51,9 @@
51
51
  "access": "public"
52
52
  },
53
53
  "dependencies": {
54
+ "bitcoinjs-lib": "^7.0.0",
54
55
  "valibot": "^1.1.0",
55
- "@ckb-ccc/core": "1.12.4"
56
+ "@ckb-ccc/core": "1.13.0"
56
57
  },
57
58
  "scripts": {
58
59
  "build": "rimraf ./dist && rimraf ./dist.commonjs && tsc && tsc --project tsconfig.commonjs.json && copyfiles -u 2 misc/basedirs/**/* .",
package/src/signer.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
+ import { Psbt } from "bitcoinjs-lib";
2
3
  import * as v from "valibot";
3
4
  import {
4
5
  Address,
@@ -169,4 +170,160 @@ export class Signer extends ccc.SignerBtc {
169
170
  )
170
171
  ).signature;
171
172
  }
173
+
174
+ /**
175
+ * Build default inputsToSign for all unsigned inputs
176
+ */
177
+ private buildDefaultinputsToSign(
178
+ psbtHex: ccc.Hex,
179
+ address: string,
180
+ ): ccc.InputToSignLike[] {
181
+ const inputsToSign: ccc.InputToSignLike[] = [];
182
+
183
+ try {
184
+ // Collect all unsigned inputs
185
+ const psbt = Psbt.fromHex(psbtHex.slice(2));
186
+ psbt.data.inputs.forEach((input, index) => {
187
+ const isSigned =
188
+ input.finalScriptSig ||
189
+ input.finalScriptWitness ||
190
+ input.tapKeySig ||
191
+ (input.partialSig && input.partialSig.length > 0) ||
192
+ (input.tapScriptSig && input.tapScriptSig.length > 0);
193
+
194
+ if (!isSigned) {
195
+ inputsToSign.push({ index, address });
196
+ }
197
+ });
198
+
199
+ // If no unsigned inputs found, the PSBT is already fully signed
200
+ // Let the wallet handle this case (likely a no-op or error)
201
+ } catch (error) {
202
+ const errorMessage =
203
+ error instanceof Error ? error.message : String(error);
204
+ throw new Error(
205
+ `Failed to parse PSBT hex. Please provide inputsToSign explicitly in options. Original error: ${errorMessage}`,
206
+ );
207
+ }
208
+
209
+ return inputsToSign;
210
+ }
211
+
212
+ private async prepareSignPsbtParams(
213
+ psbtHex: ccc.Hex,
214
+ options?: ccc.SignPsbtOptionsLike,
215
+ ): Promise<{
216
+ psbtBase64: string;
217
+ signInputs: Record<string, number[]>;
218
+ }> {
219
+ let inputsToSign = options?.inputsToSign;
220
+
221
+ if (!inputsToSign || !inputsToSign.length) {
222
+ const address = await this.getBtcAccount();
223
+ inputsToSign = this.buildDefaultinputsToSign(psbtHex, address);
224
+ }
225
+
226
+ const psbtBase64 = ccc.bytesTo(psbtHex, "base64");
227
+
228
+ const signInputs = inputsToSign.reduce(
229
+ (acc, input) => {
230
+ if (!input.address) {
231
+ throw new Error(
232
+ "Xverse only supports signing with address. Please provide 'address' in inputsToSign.",
233
+ );
234
+ }
235
+ if (acc[input.address]) {
236
+ acc[input.address].push(input.index);
237
+ } else {
238
+ acc[input.address] = [input.index];
239
+ }
240
+ return acc;
241
+ },
242
+ {} as Record<string, number[]>,
243
+ );
244
+
245
+ return { psbtBase64, signInputs };
246
+ }
247
+
248
+ /**
249
+ * Signs a PSBT using Xverse wallet.
250
+ *
251
+ * @param psbtHex - The hex string of PSBT to sign.
252
+ * @param options - Options for signing the PSBT
253
+ * @returns A promise that resolves to the signed PSBT as a Hex string
254
+ *
255
+ * @remarks
256
+ * Xverse accepts:
257
+ * - psbt: A string representing the PSBT to sign, encoded in base64
258
+ * - signInputs: A Record<string, number[]> where:
259
+ * - keys are the addresses to use for signing
260
+ * - values are the indexes of the inputs to sign with each address
261
+ *
262
+ * Xverse returns:
263
+ * - psbt: The base64 encoded signed PSBT
264
+ *
265
+ * @see https://docs.xverse.app/sats-connect/bitcoin-methods/signpsbt
266
+ */
267
+ async signPsbt(
268
+ psbtHex: ccc.HexLike,
269
+ options?: ccc.SignPsbtOptionsLike,
270
+ ): Promise<ccc.Hex> {
271
+ const { psbtBase64, signInputs } = await this.prepareSignPsbtParams(
272
+ ccc.hexFrom(psbtHex),
273
+ options,
274
+ );
275
+
276
+ const signedPsbtBase64 = (
277
+ await checkResponse(
278
+ this.provider.request("signPsbt", {
279
+ psbt: psbtBase64,
280
+ signInputs,
281
+ broadcast: false,
282
+ }),
283
+ )
284
+ ).psbt;
285
+
286
+ return ccc.hexFrom(ccc.bytesFrom(signedPsbtBase64, "base64"));
287
+ }
288
+
289
+ /**
290
+ * Broadcasts a PSBT to the Bitcoin network.
291
+ *
292
+ * @remarks
293
+ * Xverse does not support broadcasting a signed PSBT directly.
294
+ * It only supports "Sign and Broadcast" as a single atomic operation via `signAndBroadcastPsbt`.
295
+ */
296
+ async broadcastPsbt(
297
+ _psbtHex: ccc.HexLike,
298
+ _options?: ccc.SignPsbtOptionsLike,
299
+ ): Promise<ccc.Hex> {
300
+ throw new Error(
301
+ "Xverse does not support broadcasting signed PSBTs directly. Use signAndBroadcastPsbt instead.",
302
+ );
303
+ }
304
+
305
+ async signAndBroadcastPsbt(
306
+ psbtHex: ccc.HexLike,
307
+ options?: ccc.SignPsbtOptionsLike,
308
+ ): Promise<ccc.Hex> {
309
+ // ccc.hexFrom adds 0x prefix, but BTC expects non-0x
310
+ const { psbtBase64, signInputs } = await this.prepareSignPsbtParams(
311
+ ccc.hexFrom(psbtHex),
312
+ options,
313
+ );
314
+
315
+ const result = await checkResponse(
316
+ this.provider.request("signPsbt", {
317
+ psbt: psbtBase64,
318
+ signInputs,
319
+ broadcast: true,
320
+ }),
321
+ );
322
+
323
+ if (!result.txid) {
324
+ throw new Error("Failed to broadcast PSBT");
325
+ }
326
+
327
+ return ccc.hexFrom(result.txid);
328
+ }
172
329
  }