@dynamic-labs-wallet/btc 0.0.0-pr506.0 → 0.0.0-pr534.2

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.
Files changed (103) hide show
  1. package/index.cjs.js +1223 -2
  2. package/index.esm.js +1205 -2
  3. package/package.json +11 -2
  4. package/src/client/client.d.ts +158 -0
  5. package/src/client/client.d.ts.map +1 -0
  6. package/src/index.d.ts +1 -1
  7. package/src/index.d.ts.map +1 -1
  8. package/src/types/index.d.ts +0 -21
  9. package/src/types/index.d.ts.map +1 -1
  10. package/src/utils/calculateBip322Hash/calculateBip322Hash.d.ts +16 -0
  11. package/src/utils/calculateBip322Hash/calculateBip322Hash.d.ts.map +1 -0
  12. package/src/utils/calculateBip322Hash/index.d.ts +2 -0
  13. package/src/utils/calculateBip322Hash/index.d.ts.map +1 -0
  14. package/src/utils/calculateTaprootTweak/calculateTaprootTweak.d.ts +7 -0
  15. package/src/utils/calculateTaprootTweak/calculateTaprootTweak.d.ts.map +1 -0
  16. package/src/utils/calculateTaprootTweak/index.d.ts +2 -0
  17. package/src/utils/calculateTaprootTweak/index.d.ts.map +1 -0
  18. package/src/utils/collectPSBTInputData/collectPSBTInputData.d.ts +13 -0
  19. package/src/utils/collectPSBTInputData/collectPSBTInputData.d.ts.map +1 -0
  20. package/src/utils/collectPSBTInputData/index.d.ts +2 -0
  21. package/src/utils/collectPSBTInputData/index.d.ts.map +1 -0
  22. package/src/utils/convertSignatureToDER/convertSignatureToDER.d.ts +9 -0
  23. package/src/utils/convertSignatureToDER/convertSignatureToDER.d.ts.map +1 -0
  24. package/src/utils/convertSignatureToDER/index.d.ts +2 -0
  25. package/src/utils/convertSignatureToDER/index.d.ts.map +1 -0
  26. package/src/utils/convertSignatureToTaprootBuffer/convertSignatureToTaprootBuffer.d.ts +9 -0
  27. package/src/utils/convertSignatureToTaprootBuffer/convertSignatureToTaprootBuffer.d.ts.map +1 -0
  28. package/src/utils/convertSignatureToTaprootBuffer/index.d.ts +2 -0
  29. package/src/utils/convertSignatureToTaprootBuffer/index.d.ts.map +1 -0
  30. package/src/utils/createLegacyAddress/createLegacyAddress.d.ts +10 -0
  31. package/src/utils/createLegacyAddress/createLegacyAddress.d.ts.map +1 -0
  32. package/src/utils/createLegacyAddress/index.d.ts +2 -0
  33. package/src/utils/createLegacyAddress/index.d.ts.map +1 -0
  34. package/src/utils/createNativeSegWitAddress/createNativeSegWitAddress.d.ts +10 -0
  35. package/src/utils/createNativeSegWitAddress/createNativeSegWitAddress.d.ts.map +1 -0
  36. package/src/utils/createNativeSegWitAddress/index.d.ts +2 -0
  37. package/src/utils/createNativeSegWitAddress/index.d.ts.map +1 -0
  38. package/src/utils/createSegWitAddress/createSegWitAddress.d.ts +10 -0
  39. package/src/utils/createSegWitAddress/createSegWitAddress.d.ts.map +1 -0
  40. package/src/utils/createSegWitAddress/index.d.ts +2 -0
  41. package/src/utils/createSegWitAddress/index.d.ts.map +1 -0
  42. package/src/utils/createTaprootAddress/createTaprootAddress.d.ts +10 -0
  43. package/src/utils/createTaprootAddress/createTaprootAddress.d.ts.map +1 -0
  44. package/src/utils/createTaprootAddress/index.d.ts +2 -0
  45. package/src/utils/createTaprootAddress/index.d.ts.map +1 -0
  46. package/src/utils/encodeBip322Signature/encodeBip322Signature.d.ts +13 -0
  47. package/src/utils/encodeBip322Signature/encodeBip322Signature.d.ts.map +1 -0
  48. package/src/utils/encodeBip322Signature/index.d.ts +2 -0
  49. package/src/utils/encodeBip322Signature/index.d.ts.map +1 -0
  50. package/src/utils/getAddressTypeFromDerivationPath/getAddressTypeFromDerivationPath.d.ts +9 -0
  51. package/src/utils/getAddressTypeFromDerivationPath/getAddressTypeFromDerivationPath.d.ts.map +1 -0
  52. package/src/utils/getAddressTypeFromDerivationPath/index.d.ts +2 -0
  53. package/src/utils/getAddressTypeFromDerivationPath/index.d.ts.map +1 -0
  54. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts +10 -0
  55. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts.map +1 -0
  56. package/src/utils/getBitcoinNetwork/index.d.ts +2 -0
  57. package/src/utils/getBitcoinNetwork/index.d.ts.map +1 -0
  58. package/src/utils/getDefaultRpcUrl/getDefaultRpcUrl.d.ts +9 -0
  59. package/src/utils/getDefaultRpcUrl/getDefaultRpcUrl.d.ts.map +1 -0
  60. package/src/utils/getDefaultRpcUrl/index.d.ts +2 -0
  61. package/src/utils/getDefaultRpcUrl/index.d.ts.map +1 -0
  62. package/src/utils/getFeeRates/getFeeRates.d.ts +18 -0
  63. package/src/utils/getFeeRates/getFeeRates.d.ts.map +1 -0
  64. package/src/utils/getFeeRates/index.d.ts +2 -0
  65. package/src/utils/getFeeRates/index.d.ts.map +1 -0
  66. package/src/utils/getUTXOs/getUTXOs.d.ts +15 -0
  67. package/src/utils/getUTXOs/getUTXOs.d.ts.map +1 -0
  68. package/src/utils/getUTXOs/index.d.ts +2 -0
  69. package/src/utils/getUTXOs/index.d.ts.map +1 -0
  70. package/src/utils/index.d.ts +15 -0
  71. package/src/utils/index.d.ts.map +1 -0
  72. package/src/utils/initEccLib/index.d.ts +2 -0
  73. package/src/utils/initEccLib/index.d.ts.map +1 -0
  74. package/src/utils/initEccLib/initEccLib.d.ts +5 -0
  75. package/src/utils/initEccLib/initEccLib.d.ts.map +1 -0
  76. package/src/utils/normalizeForCompressed/index.d.ts +2 -0
  77. package/src/utils/normalizeForCompressed/index.d.ts.map +1 -0
  78. package/src/utils/normalizeForCompressed/normalizeForCompressed.d.ts +9 -0
  79. package/src/utils/normalizeForCompressed/normalizeForCompressed.d.ts.map +1 -0
  80. package/src/utils/normalizeForTaproot/index.d.ts +2 -0
  81. package/src/utils/normalizeForTaproot/index.d.ts.map +1 -0
  82. package/src/utils/normalizeForTaproot/normalizeForTaproot.d.ts +9 -0
  83. package/src/utils/normalizeForTaproot/normalizeForTaproot.d.ts.map +1 -0
  84. package/src/utils/normalizePublicKey/index.d.ts +2 -0
  85. package/src/utils/normalizePublicKey/index.d.ts.map +1 -0
  86. package/src/utils/normalizePublicKey/normalizePublicKey.d.ts +14 -0
  87. package/src/utils/normalizePublicKey/normalizePublicKey.d.ts.map +1 -0
  88. package/src/utils/privateKeyToWIF/index.d.ts +2 -0
  89. package/src/utils/privateKeyToWIF/index.d.ts.map +1 -0
  90. package/src/utils/privateKeyToWIF/privateKeyToWIF.d.ts +24 -0
  91. package/src/utils/privateKeyToWIF/privateKeyToWIF.d.ts.map +1 -0
  92. package/src/utils/publicKeyToBitcoinAddress/index.d.ts +2 -0
  93. package/src/utils/publicKeyToBitcoinAddress/index.d.ts.map +1 -0
  94. package/src/utils/publicKeyToBitcoinAddress/publicKeyToBitcoinAddress.d.ts +12 -0
  95. package/src/utils/publicKeyToBitcoinAddress/publicKeyToBitcoinAddress.d.ts.map +1 -0
  96. package/src/utils/selectUTXOs/index.d.ts +2 -0
  97. package/src/utils/selectUTXOs/index.d.ts.map +1 -0
  98. package/src/utils/selectUTXOs/selectUTXOs.d.ts +19 -0
  99. package/src/utils/selectUTXOs/selectUTXOs.d.ts.map +1 -0
  100. package/src/utils/toBuffer/index.d.ts +2 -0
  101. package/src/utils/toBuffer/index.d.ts.map +1 -0
  102. package/src/utils/toBuffer/toBuffer.d.ts +8 -0
  103. package/src/utils/toBuffer/toBuffer.d.ts.map +1 -0
package/index.cjs.js CHANGED
@@ -1,5 +1,1226 @@
1
1
  'use strict';
2
2
 
3
- const version = '0.0.1';
3
+ var browser = require('@dynamic-labs-wallet/browser');
4
+ var bitcoin = require('bitcoinjs-lib');
5
+ var ecc = require('@bitcoinerlab/secp256k1');
6
+ var bip322Js = require('bip322-js');
7
+ var bs58 = require('bs58');
8
+ var sha256 = require('@noble/hashes/sha256');
4
9
 
5
- exports.version = version;
10
+ function _interopNamespaceDefault(e) {
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var bitcoin__namespace = /*#__PURE__*/_interopNamespaceDefault(bitcoin);
28
+
29
+ function _extends() {
30
+ _extends = Object.assign || function assign(target) {
31
+ for(var i = 1; i < arguments.length; i++){
32
+ var source = arguments[i];
33
+ for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
34
+ }
35
+ return target;
36
+ };
37
+ return _extends.apply(this, arguments);
38
+ }
39
+
40
+ /**
41
+ * Helper to handle Buffer/Uint8Array compatibility issues
42
+ *
43
+ * @param data - The data to convert to a Buffer
44
+ * @returns The Buffer representation of the data
45
+ */ const toBuffer = (data)=>{
46
+ if (Buffer.isBuffer(data)) return data;
47
+ return Buffer.from(data);
48
+ };
49
+
50
+ /**
51
+ * Maps a BitcoinNetwork string to a bitcoinjs-lib Network object.
52
+ *
53
+ * @param network - The network identifier ('mainnet' or 'testnet').
54
+ * @returns The corresponding bitcoinjs-lib Network object. Defaults to bitcoin mainnet if the network is not recognized.
55
+ */ function getBitcoinNetwork(network) {
56
+ switch(network){
57
+ case 'mainnet':
58
+ return bitcoin__namespace.networks.bitcoin;
59
+ case 'testnet':
60
+ return bitcoin__namespace.networks.testnet;
61
+ default:
62
+ return bitcoin__namespace.networks.bitcoin;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Creates a native SegWit P2WPKH address
68
+ *
69
+ * @param pubKey - The public key
70
+ * @param networkName - The network name
71
+ * @returns The native SegWit address
72
+ */ const createNativeSegWitAddress = (pubKey, networkName)=>{
73
+ const network = getBitcoinNetwork(networkName);
74
+ // Convert to Uint8Array
75
+ let pubKeyArray = Uint8Array.from(pubKey);
76
+ // If it's an uncompressed key (65 bytes), compress it (33 bytes)
77
+ if (pubKeyArray.length === 65) {
78
+ // Uncompressed format: 0x04 || x (32 bytes) || y (32 bytes)
79
+ // Compressed format: 0x02 or 0x03 || x (32 bytes)
80
+ const y = pubKeyArray[64];
81
+ const compressedX = pubKeyArray.slice(1, 33); // Skip the 0x04 prefix, take x (32 bytes)
82
+ const compressed = new Uint8Array(33);
83
+ compressed[0] = y % 2 === 0 ? 0x02 : 0x03; // Even y -> 0x02, Odd y -> 0x03
84
+ compressed.set(compressedX, 1);
85
+ pubKeyArray = compressed;
86
+ }
87
+ const { address } = bitcoin.payments.p2wpkh({
88
+ pubkey: toBuffer(pubKeyArray),
89
+ network
90
+ });
91
+ return address || '';
92
+ };
93
+
94
+ let isInitialized = false;
95
+ /**
96
+ * Initializes the ECC library if not already initialized
97
+ */ const initEccLib = ()=>{
98
+ if (isInitialized) return;
99
+ bitcoin.initEccLib(ecc);
100
+ isInitialized = true;
101
+ };
102
+
103
+ /**
104
+ * Creates a Taproot P2TR address
105
+ *
106
+ * @param pubKey - The public key
107
+ * @param networkName - The network name
108
+ * @returns The Taproot address
109
+ */ const createTaprootAddress = (pubKey, networkName)=>{
110
+ initEccLib();
111
+ const network = getBitcoinNetwork(networkName);
112
+ // For Taproot, we need the 32-byte x-only public key
113
+ const xOnlyPubKey = pubKey.subarray(-32);
114
+ const xOnlyPubKeyArray = Uint8Array.from(xOnlyPubKey);
115
+ const { address } = bitcoin.payments.p2tr({
116
+ internalPubkey: toBuffer(xOnlyPubKeyArray),
117
+ network
118
+ });
119
+ if (!address) {
120
+ throw new Error('Failed to create Taproot address');
121
+ }
122
+ return address;
123
+ };
124
+
125
+ /**
126
+ * Converts a public key to a Bitcoin address
127
+ *
128
+ * @param publicKey - The public key
129
+ * @param addressType - The address type (default: TAPROOT)
130
+ * @param network - The Bitcoin network (default: 'mainnet')
131
+ * @returns The Bitcoin address
132
+ */ const publicKeyToBitcoinAddress = (publicKey, addressType = browser.BitcoinAddressType.TAPROOT, network = 'mainnet')=>{
133
+ initEccLib();
134
+ // Convert to Uint8Array to Buffer if needed
135
+ const pubKeyBuffer = toBuffer(publicKey);
136
+ switch(addressType){
137
+ case browser.BitcoinAddressType.NATIVE_SEGWIT:
138
+ return createNativeSegWitAddress(pubKeyBuffer, network);
139
+ case browser.BitcoinAddressType.TAPROOT:
140
+ return createTaprootAddress(pubKeyBuffer, network);
141
+ default:
142
+ throw new Error(`Unsupported address type: ${addressType}`);
143
+ }
144
+ };
145
+
146
+ /**
147
+ * Converts a public key input (various formats) to a Buffer.
148
+ * Handles objects with `pubkey` property, numeric key maps, `serializeUncompressed` methods, etc.
149
+ *
150
+ * @param pubkey - The raw public key input
151
+ * @returns The public key as a Buffer
152
+ */ const convertPublicKeyToBuffer = (pubkey)=>{
153
+ const actualPubkey = pubkey;
154
+ if (Buffer.isBuffer(actualPubkey)) {
155
+ return actualPubkey;
156
+ }
157
+ if (actualPubkey instanceof Uint8Array) {
158
+ return Buffer.from(actualPubkey);
159
+ }
160
+ if (typeof actualPubkey === 'string') {
161
+ const result = Buffer.from(actualPubkey, 'hex');
162
+ if (result.length === 0) {
163
+ throw new Error('Invalid public key format');
164
+ }
165
+ return result;
166
+ }
167
+ if (typeof actualPubkey === 'object' && actualPubkey !== null) {
168
+ return convertObjectToBuffer(actualPubkey);
169
+ }
170
+ throw new Error('Invalid public key format');
171
+ };
172
+ const convertObjectToBuffer = (actualPubkey)=>{
173
+ if ('pubkey' in actualPubkey) {
174
+ return convertPublicKeyToBuffer(actualPubkey.pubkey);
175
+ }
176
+ if ('serializeUncompressed' in actualPubkey && typeof actualPubkey.serializeUncompressed === 'function') {
177
+ return Buffer.from(actualPubkey.serializeUncompressed());
178
+ }
179
+ const keys = Object.keys(actualPubkey).map(Number).filter((k)=>!Number.isNaN(k)).sort((a, b)=>a - b);
180
+ if (keys.length > 0) {
181
+ return Buffer.from(keys.map((k)=>actualPubkey[k]));
182
+ }
183
+ throw new Error('Invalid public key object');
184
+ };
185
+
186
+ /**
187
+ * Normalizes a public key buffer for Taproot (P2TR).
188
+ * Ensures the result is a 32-byte x-only key.
189
+ *
190
+ * @param buffer - The public key buffer
191
+ * @returns The 32-byte x-only public key buffer
192
+ */ const normalizeForTaproot = (buffer)=>{
193
+ if (buffer.length === 32) return buffer;
194
+ if (buffer.length === 33) return buffer.subarray(1, 33);
195
+ if (buffer.length === 65) return buffer.subarray(1, 33);
196
+ if (buffer.length === 64) return buffer.subarray(0, 32);
197
+ throw new Error(`Unexpected public key length for Taproot: ${buffer.length}`);
198
+ };
199
+
200
+ /**
201
+ * Normalizes a public key buffer for Compressed formats (Legacy, SegWit, Native SegWit).
202
+ * Ensures the result is a 33-byte compressed key.
203
+ *
204
+ * @param buffer - The public key buffer
205
+ * @returns The 33-byte compressed public key buffer
206
+ */ const normalizeForCompressed = (buffer)=>{
207
+ if (buffer.length === 33) {
208
+ return buffer;
209
+ }
210
+ if (buffer.length === 65 && buffer[0] === 0x04) {
211
+ // Uncompressed standard (04 + X + Y)
212
+ const x = buffer.subarray(1, 33);
213
+ const yLastByte = buffer[64];
214
+ return compressPublicKey(x, yLastByte);
215
+ }
216
+ if (buffer.length === 64) {
217
+ // Raw X + Y (missing 04 prefix)
218
+ const x = buffer.subarray(0, 32);
219
+ const yLastByte = buffer[63];
220
+ return compressPublicKey(x, yLastByte);
221
+ }
222
+ throw new Error(`Unexpected public key length: ${buffer.length}`);
223
+ };
224
+ const compressPublicKey = (x, yLastByte)=>{
225
+ const prefix = yLastByte % 2 === 0 ? 0x02 : 0x03;
226
+ return Buffer.concat([
227
+ Buffer.from([
228
+ prefix
229
+ ]),
230
+ x
231
+ ]);
232
+ };
233
+
234
+ /**
235
+ * Normalizes a public key to a standard Buffer format
236
+ * Handles various input types (Uint8Array, Buffer, object with pubKeyAsHex, etc.)
237
+ * and formats (32-byte x-only, 33-byte compressed, 65-byte uncompressed)
238
+ * based on the target address type.
239
+ *
240
+ * @param pubkey - The raw public key input
241
+ * @param addressType - The target Bitcoin address type
242
+ * @returns The normalized public key as a Buffer
243
+ */ const normalizePublicKey = (pubkey, addressType)=>{
244
+ const buffer = convertPublicKeyToBuffer(pubkey);
245
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
246
+ return normalizeForTaproot(buffer);
247
+ }
248
+ return normalizeForCompressed(buffer);
249
+ };
250
+
251
+ /**
252
+ * Infers the Bitcoin address type from a BIP-44 derivation path
253
+ *
254
+ * @param derivationPathStr - The derivation path string (e.g. "m/44'/0'/0'/0/0" as JSON or string)
255
+ * @returns The inferred BitcoinAddressType
256
+ */ const getAddressTypeFromDerivationPath = (derivationPathStr)=>{
257
+ const derivationPathObj = JSON.parse(derivationPathStr);
258
+ const path = Object.values(derivationPathObj).map(Number);
259
+ if (path.length < 2) throw new Error('Invalid derivation path length');
260
+ const purpose = path[0];
261
+ // Handle both raw and hardened just in case, or just raw if strictly non-hardened
262
+ // 44 = 0x2C (Legacy), 49 = 0x31 (SegWit P2SH), 84 = 0x54 (Native SegWit), 86 = 0x56 (Taproot)
263
+ const purposeIndex = purpose >= 0x80000000 ? purpose - 0x80000000 : purpose;
264
+ let addressType;
265
+ switch(purposeIndex){
266
+ case 84:
267
+ addressType = browser.BitcoinAddressType.NATIVE_SEGWIT;
268
+ break;
269
+ case 86:
270
+ addressType = browser.BitcoinAddressType.TAPROOT;
271
+ break;
272
+ default:
273
+ throw new Error(`Unknown derivation path purpose: ${purposeIndex}`);
274
+ }
275
+ return addressType;
276
+ };
277
+
278
+ /**
279
+ * Calculates the hash to be signed for a BIP-322 message verification
280
+ *
281
+ * @param message - The message to sign
282
+ * @param pubKey - The public key of the signer
283
+ * @param addressType - The address type (e.g. TAPROOT, SEGWIT)
284
+ * @param network - The Bitcoin network
285
+ * @returns The formatted message hash and the PSBT to be signed
286
+ */ const calculateBip322Hash = (message, pubKey, addressType, network)=>{
287
+ // Normalize key and derive address
288
+ const normalizedKey = normalizePublicKey(pubKey, addressType);
289
+ const address = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
290
+ if (!address) {
291
+ throw new Error('Failed to generate address for BIP-322');
292
+ }
293
+ const scriptPubKey = bip322Js.Address.convertAdressToScriptPubkey(address);
294
+ const toSpendTx = bip322Js.BIP322.buildToSpendTx(message, scriptPubKey);
295
+ const toSignPsbt = bip322Js.BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey);
296
+ // Calculate Hash to Sign
297
+ const txToSign = toSignPsbt.__CACHE.__TX; // Access underlying TX like prototype
298
+ let hashToSign;
299
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
300
+ // Taproot (BIP-341) Signing
301
+ const prevOutScripts = [
302
+ scriptPubKey
303
+ ];
304
+ const values = [
305
+ 0
306
+ ]; // BIP-322 value
307
+ // Cast to any to avoid type issues if definition is missing
308
+ hashToSign = Buffer.from(txToSign.hashForWitnessV1(0, prevOutScripts, values, bitcoin__namespace.Transaction.SIGHASH_DEFAULT // Default sighash for Taproot
309
+ ));
310
+ } else {
311
+ // SegWit (BIP-143) Signing (P2WPKH)
312
+ const txToSignSegwit = new bitcoin__namespace.Transaction();
313
+ txToSignSegwit.version = 0;
314
+ txToSignSegwit.locktime = 0;
315
+ const prevTxId = toSpendTx.getId();
316
+ const prevHash = Buffer.from(prevTxId, 'hex').reverse();
317
+ txToSignSegwit.addInput(prevHash, 0, 0);
318
+ txToSignSegwit.addOutput(Buffer.from([
319
+ 0x6a
320
+ ]), BigInt(0));
321
+ const p2pkh = bitcoin__namespace.payments.p2pkh({
322
+ pubkey: normalizedKey,
323
+ network: network === 'testnet' ? bitcoin__namespace.networks.testnet : bitcoin__namespace.networks.bitcoin
324
+ });
325
+ const scriptCode = p2pkh.output;
326
+ if (!scriptCode) throw new Error('Failed to generate scriptCode');
327
+ hashToSign = txToSignSegwit.hashForWitnessV0(0, scriptCode, BigInt(0), bitcoin__namespace.Transaction.SIGHASH_ALL);
328
+ }
329
+ return {
330
+ formattedMessage: new Uint8Array(hashToSign),
331
+ toSignPsbt
332
+ };
333
+ };
334
+
335
+ /**
336
+ * Encodes the signature into the BIP-322 witness format
337
+ *
338
+ * @param toSignPsbt - The PSBT that was signed
339
+ * @param pubKey - The public key of the signer
340
+ * @param signature - The signature produced by the signer
341
+ * @param addressType - The address type
342
+ * @returns The base64 encoded BIP-322 signature
343
+ */ const encodeBip322Signature = (toSignPsbt, pubKey, signature, addressType)=>{
344
+ const normalizedKey = normalizePublicKey(pubKey, addressType);
345
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
346
+ // Prototype logic: direct update
347
+ // Signature from MPC should be 64 bytes (r|s)
348
+ let sigBuffer;
349
+ if (signature instanceof Uint8Array || Buffer.isBuffer(signature)) {
350
+ sigBuffer = Buffer.from(signature);
351
+ } else {
352
+ // ECDSA signature object? Should be raw bytes for Schnorr/BIP340
353
+ // If it's EcdsaSignature (r,s), concat them.
354
+ const r = signature.r;
355
+ const s = signature.s;
356
+ const rBuf = Buffer.isBuffer(r) ? r : Buffer.from(r);
357
+ const sBuf = Buffer.isBuffer(s) ? s : Buffer.from(s);
358
+ sigBuffer = Buffer.concat([
359
+ rBuf,
360
+ sBuf
361
+ ]);
362
+ }
363
+ toSignPsbt.updateInput(0, {
364
+ tapKeySig: sigBuffer
365
+ });
366
+ } else {
367
+ // Serialize the signature (COMPACT SIGNATURE)
368
+ const r = signature.r;
369
+ const s = signature.s;
370
+ const compactSignature = Buffer.concat([
371
+ Buffer.from(r),
372
+ Buffer.from(s)
373
+ ]);
374
+ // Apply Signature
375
+ const mpcSignerForPsbt = {
376
+ publicKey: normalizedKey,
377
+ sign: (_hash)=>{
378
+ return compactSignature;
379
+ }
380
+ };
381
+ // Sign the input
382
+ toSignPsbt.signInput(0, mpcSignerForPsbt);
383
+ }
384
+ // Finalize the inputs
385
+ toSignPsbt.finalizeAllInputs();
386
+ // Encode Final Result
387
+ return bip322Js.BIP322.encodeWitness(toSignPsbt);
388
+ };
389
+
390
+ initEccLib();
391
+ /**
392
+ * Converts a private key (hex string) to Bitcoin WIF (Wallet Import Format)
393
+ * WIF is the standard format that Bitcoin wallets expect for importing private keys
394
+ *
395
+ * WIF encoding steps:
396
+ * 1. Add network prefix (0x80 for mainnet, 0xef for testnet/regtest)
397
+ * 2. Add compression flag (0x01) for compressed public keys (used for SegWit)
398
+ * 3. Calculate checksum (double SHA-256, take first 4 bytes)
399
+ * 4. Base58 encode the result
400
+ *
401
+ * @param privateKey - Private key as hex string (with or without 0x prefix)
402
+ * @param network - Bitcoin network (mainnet, testnet)
403
+ * @param options - Optional configuration
404
+ * @param options.compressed - Whether to use compressed format (default: true for SegWit compatibility)
405
+ * @param options.onWarning - Optional callback for warnings (e.g., unexpected key length)
406
+ * @returns WIF-encoded private key string
407
+ * @throws Error if conversion fails
408
+ */ const privateKeyToWIF = (privateKey, network = 'mainnet', options)=>{
409
+ const { compressed = true, onWarning } = options || {};
410
+ try {
411
+ // Remove 0x prefix if present
412
+ const cleanPrivateKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
413
+ // Validate hex string length (should be 64 characters = 32 bytes)
414
+ if (cleanPrivateKey.length !== 64) {
415
+ onWarning == null ? void 0 : onWarning('Unexpected private key length', {
416
+ length: cleanPrivateKey.length,
417
+ expected: 64
418
+ });
419
+ }
420
+ // Convert hex to Uint8Array
421
+ const privateKeyBytes = new Uint8Array(cleanPrivateKey.match(/.{1,2}/g).map((byte)=>Number.parseInt(byte, 16)));
422
+ // Step 1: Get network prefix from bitcoinjs-lib network
423
+ const networkConfig = getBitcoinNetwork(network);
424
+ const networkPrefix = networkConfig.wif;
425
+ // Step 2: Build extended key with network prefix and optional compression flag
426
+ // For Native SegWit and SegWit, we use compressed format (0x01 flag)
427
+ const extendedKeyLength = compressed ? 34 : 33; // 1 byte prefix + 32 bytes key + (optional) 1 byte compression
428
+ const extendedKey = new Uint8Array(extendedKeyLength);
429
+ extendedKey[0] = networkPrefix;
430
+ extendedKey.set(privateKeyBytes, 1);
431
+ if (compressed) {
432
+ extendedKey[33] = 0x01; // Compression flag for SegWit addresses
433
+ }
434
+ // Step 3: Calculate checksum (double SHA-256, take first 4 bytes)
435
+ const firstHash = sha256.sha256(extendedKey);
436
+ const secondHash = sha256.sha256(firstHash);
437
+ const checksum = secondHash.slice(0, 4);
438
+ // Step 4: Append checksum and Base58 encode
439
+ const wifBytes = new Uint8Array(extendedKey.length + checksum.length);
440
+ wifBytes.set(extendedKey, 0);
441
+ wifBytes.set(checksum, extendedKey.length);
442
+ const wif = bs58.encode(wifBytes);
443
+ return wif;
444
+ } catch (error) {
445
+ // Re-throw with more context
446
+ throw new Error(`Failed to convert private key to WIF: ${error instanceof Error ? error.message : String(error)}`);
447
+ }
448
+ };
449
+
450
+ /**
451
+ * Converts an ECDSA signature to DER format
452
+ *
453
+ * @param signature - The ECDSA signature
454
+ * @returns The DER encoded signature
455
+ */ const convertSignatureToDER = (signature)=>{
456
+ // Construct raw 64-byte signature (r + s) using Uint8Array to ensure compatibility
457
+ const r = signature.r instanceof Uint8Array ? signature.r : new Uint8Array(signature.r);
458
+ const s = signature.s instanceof Uint8Array ? signature.s : new Uint8Array(signature.s);
459
+ // Enforce 32-byte length (padding or slicing if needed)
460
+ const r32 = new Uint8Array(32);
461
+ const rSource = r.length > 32 ? r.slice(-32) : r;
462
+ r32.set(rSource, 32 - rSource.length);
463
+ const s32 = new Uint8Array(32);
464
+ const sSource = s.length > 32 ? s.slice(-32) : s;
465
+ s32.set(sSource, 32 - sSource.length);
466
+ const rawSignature = new Uint8Array(64);
467
+ rawSignature.set(r32, 0);
468
+ rawSignature.set(s32, 32);
469
+ const derSignature = bitcoin__namespace.script.signature.encode(rawSignature, bitcoin__namespace.Transaction.SIGHASH_ALL);
470
+ return derSignature;
471
+ };
472
+
473
+ /**
474
+ * Helper to get the default RPC URL
475
+ *
476
+ * @param network - The Bitcoin network
477
+ * @returns The default RPC URL
478
+ */ const getDefaultRpcUrl = (network)=>{
479
+ return network === 'mainnet' ? 'https://mempool.space/api' : 'https://mempool.space/testnet/api';
480
+ };
481
+
482
+ /**
483
+ * Fetches UTXOs for a given address using mempool.space API
484
+ *
485
+ * @param address - The address to fetch UTXOs for
486
+ * @param network - The Bitcoin network (default: 'mainnet')
487
+ * @param rpcUrl - Optional custom RPC URL
488
+ * @returns The list of UTXOs
489
+ */ const getUTXOs = async ({ address, network = 'mainnet', rpcUrl })=>{
490
+ const baseUrl = rpcUrl || getDefaultRpcUrl(network);
491
+ try {
492
+ const response = await fetch(`${baseUrl}/address/${address}/utxo`);
493
+ if (!response.ok) {
494
+ throw new Error(`Failed to fetch UTXOs: ${response.statusText}`);
495
+ }
496
+ const utxos = await response.json();
497
+ return utxos.map((utxo)=>({
498
+ txid: utxo.txid,
499
+ vout: utxo.vout,
500
+ value: BigInt(utxo.value)
501
+ }));
502
+ } catch (error) {
503
+ throw new Error(`Error fetching UTXOs: ${error}`);
504
+ }
505
+ };
506
+
507
+ /**
508
+ * Simple greedy coin selection
509
+ *
510
+ * @param utxos - The list of UTXOs to select from
511
+ * @param amount - The amount to spend
512
+ * @param feeRate - The fee rate in sat/vByte
513
+ * @returns The selected inputs, change amount, and fee
514
+ */ const selectUTXOs = ({ utxos, amount, feeRate })=>{
515
+ // Sort by value descending
516
+ const sorted = [
517
+ ...utxos
518
+ ].sort((a, b)=>Number(b.value - a.value));
519
+ const inputs = [];
520
+ let totalInput = 0n;
521
+ // Basic fee estimation constants
522
+ // Optimized for Native SegWit (P2WPKH) only
523
+ // P2WPKH: ~68 vbytes per input, ~31 vbytes per output
524
+ // Overhead: ~10 vbytes
525
+ const INPUT_SIZE = 68;
526
+ const OUTPUT_SIZE = 31;
527
+ const OVERHEAD = 10;
528
+ for (const utxo of sorted){
529
+ inputs.push(utxo);
530
+ totalInput += utxo.value;
531
+ // Calculate fee for current set + 2 outputs (recipient + change)
532
+ const vbytes = OVERHEAD + inputs.length * INPUT_SIZE + 2 * OUTPUT_SIZE;
533
+ const fee = BigInt(Math.ceil(vbytes * feeRate));
534
+ if (totalInput >= amount + fee) {
535
+ return {
536
+ inputs,
537
+ change: totalInput - amount - fee,
538
+ fee
539
+ };
540
+ }
541
+ }
542
+ throw new Error('Insufficient funds');
543
+ };
544
+
545
+ /**
546
+ * Fetches current fee rates from mempool.space
547
+ * Returns fees in sat/vByte
548
+ *
549
+ * @param network - The Bitcoin network (default: 'mainnet')
550
+ * @param rpcUrl - Optional custom RPC URL
551
+ * @returns The fee rates for fast, medium, and slow transactions
552
+ */ const getFeeRates = async ({ network = 'mainnet', rpcUrl })=>{
553
+ const baseUrl = rpcUrl || getDefaultRpcUrl(network);
554
+ const response = await fetch(`${baseUrl}/v1/fees/recommended`);
555
+ if (!response.ok) {
556
+ throw new Error(`Failed to fetch fee rates: ${response.statusText}`);
557
+ }
558
+ const fees = await response.json();
559
+ return {
560
+ fast: fees.fastestFee,
561
+ medium: fees.halfHourFee,
562
+ slow: fees.hourFee
563
+ };
564
+ };
565
+
566
+ /**
567
+ * Calculates the Taproot tweak hash for BIP340 signing
568
+ * @param pubKey - The normalized public key (32-byte x-only for Taproot)
569
+ * @returns The tweak hash as a hex string (64 hex chars = 32 bytes)
570
+ */ const calculateTaprootTweak = (pubKey)=>{
571
+ const tweakHash = bitcoin__namespace.crypto.taggedHash('TapTweak', pubKey);
572
+ return Buffer.from(tweakHash).toString('hex');
573
+ };
574
+
575
+ /**
576
+ * Converts a signature to a Buffer format suitable for Taproot (BIP340/Schnorr)
577
+ * Taproot signatures are 64 bytes (r|s concatenated)
578
+ * @param signature - The signature from MPC (can be Uint8Array, Buffer, or EcdsaSignature object)
579
+ * @returns A Buffer containing the 64-byte Schnorr signature
580
+ */ const convertSignatureToTaprootBuffer = (signature)=>{
581
+ if (signature instanceof Uint8Array || Buffer.isBuffer(signature)) {
582
+ return Buffer.from(signature);
583
+ }
584
+ // ECDSA signature object - concat r and s for Schnorr
585
+ const r = signature.r;
586
+ const s = signature.s;
587
+ const rBuf = Buffer.isBuffer(r) ? r : Buffer.from(r);
588
+ const sBuf = Buffer.isBuffer(s) ? s : Buffer.from(s);
589
+ return Buffer.concat([
590
+ rBuf,
591
+ sBuf
592
+ ]);
593
+ };
594
+
595
+ /**
596
+ * Collects prevOutScripts and values from all PSBT inputs
597
+ * Required for Taproot (BIP-341) hashForWitnessV1 calculation
598
+ * @param psbt - The PSBT to collect input data from
599
+ * @returns An object containing arrays of prevOutScripts and values
600
+ * @throws Error if any input is missing witnessUtxo
601
+ */ const collectPSBTInputData = (psbt)=>{
602
+ const prevOutScripts = [];
603
+ const values = [];
604
+ psbt.data.inputs.forEach((input, index)=>{
605
+ if (!input.witnessUtxo) {
606
+ throw new Error(`Input ${index} missing witnessUtxo`);
607
+ }
608
+ prevOutScripts.push(Buffer.isBuffer(input.witnessUtxo.script) ? input.witnessUtxo.script : Buffer.from(input.witnessUtxo.script));
609
+ values.push(input.witnessUtxo.value);
610
+ });
611
+ return {
612
+ prevOutScripts,
613
+ values
614
+ };
615
+ };
616
+
617
+ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
618
+ /**
619
+ * Creates a Bitcoin wallet account
620
+ * @param thresholdSignatureScheme - The threshold signature scheme to use for the wallet
621
+ * @param password - The password to use for the wallet
622
+ * @param onError - The function to call if an error occurs
623
+ * @param signedSessionId - The signed session ID to use for the wallet
624
+ * @param addressType - The type of address to use for the wallet
625
+ * @returns The account address, public key hex, and raw public key
626
+ */ async createWalletAccount({ thresholdSignatureScheme, password = undefined, onError, signedSessionId, bitcoinConfig }) {
627
+ const network = browser.BitcoinNetwork.MAINNET;
628
+ const { addressType } = bitcoinConfig;
629
+ if (!addressType) {
630
+ throw new Error('addressType is required for BTC');
631
+ }
632
+ this.logger.debug('[BTC Client] Creating wallet account', {
633
+ package: 'packages/btc/src/client/client.ts',
634
+ method: 'createWalletAccount',
635
+ addressType,
636
+ network,
637
+ thresholdSignatureScheme,
638
+ signedSessionId
639
+ });
640
+ try {
641
+ // Create a promise that will resolve when the ceremony is complete
642
+ let ceremonyCompleteResolver;
643
+ const ceremonyCompletePromise = new Promise((resolve)=>{
644
+ ceremonyCompleteResolver = resolve;
645
+ });
646
+ // Validate address type is provided
647
+ if (!addressType) {
648
+ throw new Error('Address type is required for BTC');
649
+ }
650
+ const buildBitcoinConfig = {
651
+ addressType,
652
+ network
653
+ };
654
+ // Use 'BTC' as chain name, derivation path and signing algorithm will be resolved from addressType
655
+ const { rawPublicKey, clientKeyShares } = await this.keyGen({
656
+ chainName: this.chainName,
657
+ thresholdSignatureScheme,
658
+ bitcoinConfig: buildBitcoinConfig,
659
+ onError: (error)=>{
660
+ this.logger.error(browser.ERROR_CREATE_WALLET_ACCOUNT, error);
661
+ onError == null ? void 0 : onError(error);
662
+ },
663
+ onCeremonyComplete: (accountAddress, walletId)=>{
664
+ const chainConfig = browser.getMPCChainConfig(this.chainName, bitcoinConfig);
665
+ // Update wallet map with the derived account address
666
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
667
+ accountAddress: accountAddress,
668
+ walletId,
669
+ chainName: this.chainName,
670
+ thresholdSignatureScheme,
671
+ derivationPath: JSON.stringify(Object.fromEntries(chainConfig.derivationPath.map((value, index)=>[
672
+ index,
673
+ value
674
+ ]))),
675
+ addressType: addressType,
676
+ clientKeySharesBackupInfo: browser.getClientKeyShareBackupInfo()
677
+ });
678
+ this.logger.debug('[BTC Client] Updated walletMap in onCeremonyComplete', {
679
+ accountAddress: accountAddress,
680
+ walletId,
681
+ addressType: addressType
682
+ });
683
+ // Resolve the promise when ceremony is complete
684
+ ceremonyCompleteResolver(undefined);
685
+ }
686
+ });
687
+ // Wait for the ceremony to complete before proceeding
688
+ await ceremonyCompletePromise;
689
+ if (!rawPublicKey || !clientKeyShares) {
690
+ throw new Error('Key generation failed');
691
+ }
692
+ // Derive account address from raw public key
693
+ const { accountAddress } = this.deriveAccountAddress({
694
+ rawPublicKey,
695
+ addressType: addressType,
696
+ network
697
+ });
698
+ // Store client key shares to localStorage
699
+ await this.setClientKeySharesToLocalStorage({
700
+ accountAddress,
701
+ clientKeyShares,
702
+ overwriteOrMerge: 'overwrite'
703
+ });
704
+ // Store encrypted backup
705
+ await this.storeEncryptedBackupByWalletWithRetry({
706
+ accountAddress,
707
+ clientKeyShares,
708
+ password,
709
+ signedSessionId
710
+ });
711
+ const publicKeyHex = this.extractPublicKeyHex(rawPublicKey);
712
+ return {
713
+ accountAddress,
714
+ publicKeyHex,
715
+ rawPublicKey
716
+ };
717
+ } catch (error) {
718
+ if (onError) {
719
+ onError(error);
720
+ }
721
+ throw new Error(browser.ERROR_CREATE_WALLET_ACCOUNT);
722
+ }
723
+ }
724
+ /**
725
+ * Automatically determines the chain config based on address type
726
+ * @param rawPublicKey - The raw public key from the server
727
+ * @returns The public key hex
728
+ */ extractPublicKeyHex(rawPublicKey) {
729
+ if (rawPublicKey instanceof Uint8Array) {
730
+ return Buffer.from(rawPublicKey).toString('hex');
731
+ }
732
+ if (rawPublicKey && typeof rawPublicKey === 'object' && typeof rawPublicKey.pubKeyAsHex === 'function') {
733
+ return rawPublicKey.pubKeyAsHex();
734
+ }
735
+ if (typeof rawPublicKey === 'string') {
736
+ return rawPublicKey;
737
+ }
738
+ throw new Error('Invalid public key format');
739
+ }
740
+ /**
741
+ * Derives the Bitcoin account address
742
+ * - BIP340 keys (32 bytes x-only): Only for Taproot addresses
743
+ * - ECDSA keys (33/65 bytes): For all other address types (Legacy, SegWit, Native SegWit)
744
+ * - Algorithm selection is automatic based on addressType
745
+ * @param rawPublicKey - The raw public key to derive the account address from
746
+ * @param addressType - The address type to derive the account address for
747
+ * @param network - The network to derive the account address for
748
+ * @returns The account address
749
+ */ deriveAccountAddress({ rawPublicKey, addressType, network }) {
750
+ // Derive address based on the chosen address type and network
751
+ const normalizedKey = normalizePublicKey(rawPublicKey, addressType);
752
+ const accountAddress = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
753
+ return {
754
+ accountAddress
755
+ };
756
+ }
757
+ /**
758
+ * Signs a message (BIP-322)
759
+ * @param message - The message to sign
760
+ * @param accountAddress - The account address
761
+ * @param network - The network (mainnet/testnet)
762
+ * @param password - The wallet password (optional)
763
+ * @param signedSessionId - The signed session ID
764
+ * @param mfaToken - The MFA token (optional)
765
+ * @param context - Additional context
766
+ * @param onError - Error callback
767
+ * @returns The BIP-322 signature
768
+ */ async signMessage({ message, accountAddress, network, password = undefined, signedSessionId, mfaToken, context, onError }) {
769
+ await this.verifyPassword({
770
+ accountAddress,
771
+ password,
772
+ walletOperation: browser.WalletOperation.SIGN_MESSAGE,
773
+ signedSessionId
774
+ });
775
+ try {
776
+ if (!accountAddress) {
777
+ throw new Error(browser.ERROR_ACCOUNT_ADDRESS_REQUIRED);
778
+ }
779
+ const walletProperties = this.walletMap[accountAddress];
780
+ if (!walletProperties) {
781
+ throw new Error('Wallet not found in walletMap');
782
+ }
783
+ const derivationPath = walletProperties.derivationPath;
784
+ if (!derivationPath) {
785
+ throw new Error('Derivation path missing in walletMap');
786
+ }
787
+ let addressType = walletProperties.addressType;
788
+ if (!addressType) {
789
+ addressType = getAddressTypeFromDerivationPath(derivationPath);
790
+ }
791
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
792
+ accountAddress
793
+ });
794
+ if (!clientKeyShares || clientKeyShares.length === 0) {
795
+ throw new Error('No key shares found');
796
+ }
797
+ const bitcoinConfig = {
798
+ addressType,
799
+ network
800
+ };
801
+ const derivedPublicKey = await this.derivePublicKey({
802
+ chainName: this.chainName,
803
+ keyShare: clientKeyShares[0],
804
+ derivationPath: new Uint32Array(Object.values(JSON.parse(derivationPath))),
805
+ bitcoinConfig
806
+ });
807
+ if (!derivedPublicKey) {
808
+ throw new Error('Failed to derive public key');
809
+ }
810
+ const pubKey = normalizePublicKey(derivedPublicKey, addressType);
811
+ this.verifyWalletAddress(derivedPublicKey, addressType, network, accountAddress);
812
+ // Prepare BIP-322 Transactions and calculate hash
813
+ const { formattedMessage, toSignPsbt } = calculateBip322Hash(message, pubKey, addressType, network);
814
+ // Prepare tweak for Taproot in case of BIP340
815
+ let tweak;
816
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
817
+ tweak = calculateTaprootTweak(pubKey);
818
+ }
819
+ // Build complete bitcoinConfig with addressType and tweak
820
+ const completeBitcoinConfig = _extends({}, bitcoinConfig, {
821
+ addressType: addressType,
822
+ tweak
823
+ });
824
+ // Sign the message using MPC
825
+ const signature = await this.sign({
826
+ message: formattedMessage,
827
+ accountAddress,
828
+ chainName: this.chainName,
829
+ password,
830
+ signedSessionId,
831
+ mfaToken,
832
+ isFormatted: true,
833
+ context: context != null ? context : {
834
+ btcMessage: message
835
+ },
836
+ bitcoinConfig: completeBitcoinConfig,
837
+ onError
838
+ });
839
+ // Encode Final Result
840
+ const bip322Signature = encodeBip322Signature(toSignPsbt, pubKey, signature, addressType);
841
+ this.logger.debug('[BTC Client] signMessage - bip322Signature', {
842
+ bip322Signature
843
+ });
844
+ return bip322Signature;
845
+ } catch (error) {
846
+ if (onError) {
847
+ onError(error);
848
+ }
849
+ throw new Error(browser.ERROR_SIGN_MESSAGE);
850
+ }
851
+ }
852
+ /**
853
+ * Verifies that the derived address matches the expected address
854
+ * @param rawPublicKey - The raw public key
855
+ * @param addressType - The address type
856
+ * @param network - The network
857
+ * @param expectedAddress - The expected address
858
+ */ verifyWalletAddress(rawPublicKey, addressType, network, expectedAddress) {
859
+ const normalizedKey = normalizePublicKey(rawPublicKey, addressType);
860
+ const derivedAddress = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
861
+ if (derivedAddress !== expectedAddress) {
862
+ throw new Error(`Address verification failed: expected ${expectedAddress}, got ${derivedAddress}`);
863
+ }
864
+ }
865
+ /**
866
+ * Exports the private key for a given account
867
+ * @param accountAddress - The account address to export the private key for
868
+ * @param password - The password to use for the private key
869
+ * @param signedSessionId - The signed session ID to use for the private key
870
+ * @param mfaToken - The MFA token to use for the private key
871
+ * @param network - The network to use for the private key
872
+ * @returns The private key
873
+ */ async exportPrivateKey({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
874
+ await this.verifyPassword({
875
+ accountAddress,
876
+ password,
877
+ walletOperation: browser.WalletOperation.EXPORT_PRIVATE_KEY,
878
+ signedSessionId
879
+ });
880
+ // Get wallet properties to determine addressType
881
+ const walletProperties = this.walletMap[accountAddress];
882
+ if (!walletProperties) {
883
+ throw new Error('Wallet not found in walletMap');
884
+ }
885
+ const derivationPath = walletProperties.derivationPath;
886
+ if (!derivationPath) {
887
+ throw new Error('Derivation path missing in walletMap');
888
+ }
889
+ let addressType = walletProperties.addressType;
890
+ if (!addressType && walletProperties.derivationPath) {
891
+ try {
892
+ addressType = getAddressTypeFromDerivationPath(walletProperties.derivationPath);
893
+ } catch (e) {
894
+ this.logger.warn('Failed to infer address type from derivation path', e);
895
+ }
896
+ }
897
+ if (!addressType) {
898
+ throw new Error('Address type not found in walletMap');
899
+ }
900
+ const { derivedPrivateKey } = await this.exportKey({
901
+ accountAddress: accountAddress,
902
+ chainName: this.chainName,
903
+ bitcoinConfig: {
904
+ addressType
905
+ },
906
+ password,
907
+ signedSessionId,
908
+ mfaToken
909
+ });
910
+ if (!derivedPrivateKey) {
911
+ throw new Error('Failed to derive private key');
912
+ }
913
+ // Convert MPC private key to Bitcoin WIF format
914
+ return this.convertPrivateKeyToBitcoinFormat(derivedPrivateKey, browser.BitcoinNetwork.MAINNET);
915
+ }
916
+ /**
917
+ * Converts MPC private key to Bitcoin WIF (Wallet Import Format)
918
+ * Uses the utility function from utils.ts for the core conversion logic
919
+ * @param privateKey - The private key to convert to a Bitcoin WIF format
920
+ * @param network - The network to convert the private key to a Bitcoin WIF format for
921
+ * @returns The Bitcoin WIF format
922
+ */ convertPrivateKeyToBitcoinFormat(privateKey, network) {
923
+ try {
924
+ const wif = privateKeyToWIF(privateKey, network, {
925
+ compressed: true,
926
+ onWarning: (message, data)=>{
927
+ this.logger.warn(`[BTC Client] convertPrivateKeyToBitcoinFormat - ${message}`, data);
928
+ }
929
+ });
930
+ this.logger.debug('[BTC Client] convertPrivateKeyToBitcoinFormat - converted to WIF', {
931
+ network,
932
+ wifLength: wif.length,
933
+ wifPrefix: wif.substring(0, 1)
934
+ });
935
+ return wif;
936
+ } catch (error) {
937
+ this.logger.error('[BTC Client] convertPrivateKeyToBitcoinFormat - error converting to WIF', {
938
+ error,
939
+ privateKeyLength: privateKey.length,
940
+ network
941
+ });
942
+ const cleanPrivateKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
943
+ return cleanPrivateKey;
944
+ }
945
+ }
946
+ /**
947
+ * Sign a Bitcoin Transaction (PSBT)
948
+ *
949
+ * @param transaction - The PSBT to sign in hex format
950
+ * @param senderAddress - The address of the sender
951
+ * @param password - The password to use for the transaction
952
+ * @param signedSessionId - The signed session ID to use for the transaction
953
+ * @param authToken - The auth token to use for the transaction
954
+ * @param mfaToken - The MFA token to use for the transaction
955
+ * @param context - The context to use for the transaction
956
+ * @param onError - The error handler
957
+ * @returns The signed PSBT
958
+ */ async signTransaction({ transaction, senderAddress, network, password, signedSessionId, mfaToken, context, onError }) {
959
+ try {
960
+ await this.verifyPassword({
961
+ accountAddress: senderAddress,
962
+ password,
963
+ walletOperation: browser.WalletOperation.SIGN_TRANSACTION,
964
+ signedSessionId
965
+ });
966
+ if (!senderAddress) {
967
+ throw new Error(browser.ERROR_ACCOUNT_ADDRESS_REQUIRED);
968
+ }
969
+ const walletProperties = this.walletMap[senderAddress];
970
+ if (!walletProperties) {
971
+ throw new Error('Wallet not found in walletMap');
972
+ }
973
+ const derivationPath = walletProperties.derivationPath;
974
+ if (!derivationPath) {
975
+ throw new Error('Derivation path missing in walletMap');
976
+ }
977
+ let addressType = walletProperties.addressType;
978
+ if (!addressType) {
979
+ addressType = getAddressTypeFromDerivationPath(derivationPath);
980
+ }
981
+ const bitcoinConfig = {
982
+ addressType,
983
+ network
984
+ };
985
+ const psbt = bitcoin__namespace.Psbt.fromBase64(transaction);
986
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
987
+ accountAddress: senderAddress
988
+ });
989
+ if (!clientKeyShares || clientKeyShares.length === 0) {
990
+ throw new Error('No key shares found');
991
+ }
992
+ const derivedPublicKey = await this.derivePublicKey({
993
+ chainName: this.chainName,
994
+ keyShare: clientKeyShares[0],
995
+ derivationPath: new Uint32Array(Object.values(JSON.parse(derivationPath))),
996
+ bitcoinConfig
997
+ });
998
+ if (!derivedPublicKey) {
999
+ throw new Error('Failed to derive public key');
1000
+ }
1001
+ const pubKey = normalizePublicKey(derivedPublicKey, addressType);
1002
+ const tx = psbt.__CACHE.__TX;
1003
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
1004
+ const tweak = calculateTaprootTweak(pubKey);
1005
+ const completeBitcoinConfig = _extends({}, bitcoinConfig, {
1006
+ addressType: addressType,
1007
+ tweak
1008
+ });
1009
+ const { prevOutScripts, values } = collectPSBTInputData(psbt);
1010
+ await Promise.all(psbt.data.inputs.map(async (input, i)=>{
1011
+ if (!input.witnessUtxo) {
1012
+ throw new Error(`Input ${i} missing witnessUtxo`);
1013
+ }
1014
+ const hash = Buffer.from(tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin__namespace.Transaction.SIGHASH_DEFAULT));
1015
+ const signature = await this.sign({
1016
+ message: new Uint8Array(hash),
1017
+ accountAddress: senderAddress,
1018
+ chainName: this.chainName,
1019
+ password,
1020
+ signedSessionId,
1021
+ mfaToken,
1022
+ isFormatted: true,
1023
+ context: context,
1024
+ bitcoinConfig: completeBitcoinConfig,
1025
+ onError
1026
+ });
1027
+ const sigBuffer = convertSignatureToTaprootBuffer(signature);
1028
+ psbt.updateInput(i, {
1029
+ tapKeySig: sigBuffer
1030
+ });
1031
+ }));
1032
+ } else {
1033
+ // Native SegWit (P2WPKH) or other ECDSA-based signing
1034
+ // Build bitcoinConfig without tweak (not needed for ECDSA)
1035
+ const completeBitcoinConfig = _extends({}, bitcoinConfig, {
1036
+ addressType: addressType
1037
+ });
1038
+ // Iterate and sign inputs in parallel for better performance
1039
+ await Promise.all(psbt.data.inputs.map(async (input, i)=>{
1040
+ if (!input.witnessUtxo) {
1041
+ throw new Error(`Input ${i} missing witnessUtxo`);
1042
+ }
1043
+ const { script, value } = input.witnessUtxo;
1044
+ // For Native SegWit (P2WPKH), use p2wpkh
1045
+ const p2wpkh = bitcoin__namespace.payments.p2wpkh({
1046
+ output: script,
1047
+ network: getBitcoinNetwork(network)
1048
+ });
1049
+ const scriptCode = p2wpkh.output || script;
1050
+ if (!scriptCode) throw new Error('Failed to generate scriptCode');
1051
+ const hash = tx.hashForWitnessV0(i, scriptCode, value, bitcoin__namespace.Transaction.SIGHASH_ALL);
1052
+ const signature = await this.sign({
1053
+ message: new Uint8Array(hash),
1054
+ accountAddress: senderAddress,
1055
+ chainName: this.chainName,
1056
+ password,
1057
+ signedSessionId,
1058
+ mfaToken,
1059
+ isFormatted: true,
1060
+ context: context,
1061
+ bitcoinConfig: completeBitcoinConfig,
1062
+ onError
1063
+ });
1064
+ const derSignature = convertSignatureToDER(signature);
1065
+ psbt.updateInput(i, {
1066
+ partialSig: [
1067
+ {
1068
+ pubkey: pubKey,
1069
+ signature: new Uint8Array(derSignature)
1070
+ }
1071
+ ]
1072
+ });
1073
+ }));
1074
+ }
1075
+ // Return signed PSBT in base64 format (not finalized)
1076
+ // This allows users to review, add additional signatures, or finalize themselves
1077
+ const signedPsbtBase64 = psbt.toBase64();
1078
+ this.logger.debug('[BTC Client] signTransaction - signedPsbtBase64', {
1079
+ signedPsbtBase64
1080
+ });
1081
+ return signedPsbtBase64;
1082
+ } catch (error) {
1083
+ if (onError) {
1084
+ onError(error);
1085
+ }
1086
+ throw error;
1087
+ }
1088
+ }
1089
+ /**
1090
+ * Creates a PSBT for a transaction
1091
+ * @param receiverAddress - The address to send funds to
1092
+ * @param amount - The amount to send in satoshis
1093
+ * @param senderAddress - The address to send funds from
1094
+ * @param network - The network to use (mainnet/testnet)
1095
+ * @param feeRateLevel - The fee rate level to use (fast, medium, slow)
1096
+ * @returns The PSBT in hex format
1097
+ */ async createTransaction({ receiverAddress, amount, senderAddress, network, feeRateLevel = 'medium' }) {
1098
+ this.logger.debug('[BTC Client] Creating transaction', {
1099
+ receiverAddress,
1100
+ amount,
1101
+ senderAddress,
1102
+ network,
1103
+ feeRateLevel
1104
+ });
1105
+ if (!senderAddress) {
1106
+ throw new Error(browser.ERROR_ACCOUNT_ADDRESS_REQUIRED);
1107
+ }
1108
+ const walletProperties = this.walletMap[senderAddress];
1109
+ if (!walletProperties) {
1110
+ throw new Error('Wallet not found in walletMap');
1111
+ }
1112
+ let addressType = walletProperties.addressType;
1113
+ if (!addressType && walletProperties.derivationPath) {
1114
+ addressType = getAddressTypeFromDerivationPath(walletProperties.derivationPath);
1115
+ }
1116
+ if (!addressType) {
1117
+ throw new Error('Address type not determined');
1118
+ }
1119
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
1120
+ throw new Error('Taproot transaction creation not yet supported');
1121
+ }
1122
+ const bitcoinNetwork = network === 'testnet' ? bitcoin__namespace.networks.testnet : bitcoin__namespace.networks.bitcoin;
1123
+ // Fetch fee rates
1124
+ const feeRates = await getFeeRates({
1125
+ network
1126
+ });
1127
+ const feeRate = feeRates[feeRateLevel];
1128
+ // Fetch UTXOs
1129
+ const utxos = await getUTXOs({
1130
+ address: senderAddress,
1131
+ network
1132
+ });
1133
+ // Select UTXOs
1134
+ const { inputs, change, fee } = selectUTXOs({
1135
+ utxos,
1136
+ amount: BigInt(amount),
1137
+ feeRate
1138
+ });
1139
+ const psbt = new bitcoin__namespace.Psbt({
1140
+ network: bitcoinNetwork
1141
+ });
1142
+ // Prepare inputs
1143
+ await Promise.all(inputs.map(async (utxo)=>{
1144
+ const txHex = await this.getTxHex(utxo.txid, network);
1145
+ const inputData = {
1146
+ hash: utxo.txid,
1147
+ index: utxo.vout,
1148
+ nonWitnessUtxo: Buffer.from(txHex, 'hex')
1149
+ };
1150
+ if (addressType === browser.BitcoinAddressType.NATIVE_SEGWIT) {
1151
+ const p2wpkh = bitcoin__namespace.payments.p2wpkh({
1152
+ address: senderAddress,
1153
+ network: bitcoinNetwork
1154
+ });
1155
+ if (!p2wpkh.output) {
1156
+ throw new Error('Failed to generate scriptCode for P2WPKH');
1157
+ }
1158
+ inputData.witnessUtxo = {
1159
+ script: p2wpkh.output,
1160
+ value: BigInt(utxo.value)
1161
+ };
1162
+ } else if (addressType === browser.BitcoinAddressType.TAPROOT) {
1163
+ throw new Error('Taproot transaction creation not yet supported');
1164
+ }
1165
+ psbt.addInput(inputData);
1166
+ }));
1167
+ // Add Receiver Output
1168
+ psbt.addOutput({
1169
+ address: receiverAddress,
1170
+ value: BigInt(amount)
1171
+ });
1172
+ // Add Change Output
1173
+ if (change > 0n) {
1174
+ psbt.addOutput({
1175
+ address: senderAddress,
1176
+ value: change
1177
+ });
1178
+ }
1179
+ this.logger.debug('[BTC Client] Transaction created', {
1180
+ fee,
1181
+ change,
1182
+ inputCount: inputs.length
1183
+ });
1184
+ return psbt.toHex();
1185
+ }
1186
+ /**
1187
+ * Fetches the raw transaction hex for a given transaction ID
1188
+ * @param txid - The transaction ID
1189
+ * @param network - The network
1190
+ * @returns The transaction hex
1191
+ */ async getTxHex(txid, network) {
1192
+ const baseUrl = getDefaultRpcUrl(network);
1193
+ const response = await fetch(`${baseUrl}/tx/${txid}/hex`);
1194
+ if (!response.ok) {
1195
+ throw new Error(`Failed to fetch TX hex: ${response.statusText}`);
1196
+ }
1197
+ return await response.text();
1198
+ }
1199
+ /**
1200
+ * Gets the Bitcoin wallets
1201
+ * @returns The Bitcoin wallets
1202
+ */ async getBitcoinWallets() {
1203
+ const wallets = await this.getWallets();
1204
+ const btcWallets = wallets.filter((wallet)=>wallet.chainName === 'bitcoin');
1205
+ return btcWallets;
1206
+ }
1207
+ /**
1208
+ * Creates a new instance of DynamicBtcWalletClient
1209
+ * @param props - The client properties
1210
+ */ constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, featureFlags, authMode = browser.AuthMode.HEADER, sdkVersion, forwardMPCClient }){
1211
+ super({
1212
+ environmentId,
1213
+ authToken,
1214
+ baseApiUrl,
1215
+ baseMPCRelayApiUrl,
1216
+ storageKey,
1217
+ debug,
1218
+ featureFlags,
1219
+ authMode,
1220
+ sdkVersion,
1221
+ forwardMPCClient
1222
+ }), this.chainName = 'BTC';
1223
+ }
1224
+ }
1225
+
1226
+ exports.DynamicBtcWalletClient = DynamicBtcWalletClient;