@ckb-ccc/xverse 1.0.32 → 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 +16 -0
- package/dist/signer.d.ts +34 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +100 -0
- package/dist.commonjs/signer.d.ts +34 -0
- package/dist.commonjs/signer.d.ts.map +1 -1
- package/dist.commonjs/signer.js +100 -0
- package/package.json +3 -2
- package/src/signer.ts +157 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
3
19
|
## 1.0.32
|
|
4
20
|
### Patch Changes
|
|
5
21
|
|
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
|
package/dist/signer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;
|
|
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;
|
|
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.commonjs/signer.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
}
|