@dynamic-labs-wallet/btc 0.0.229 → 0.0.230

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 (91) hide show
  1. package/index.cjs.js +1135 -2
  2. package/index.esm.js +1117 -2
  3. package/package.json +8 -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/convertSignatureToDER/convertSignatureToDER.d.ts +9 -0
  15. package/src/utils/convertSignatureToDER/convertSignatureToDER.d.ts.map +1 -0
  16. package/src/utils/convertSignatureToDER/index.d.ts +2 -0
  17. package/src/utils/convertSignatureToDER/index.d.ts.map +1 -0
  18. package/src/utils/createLegacyAddress/createLegacyAddress.d.ts +10 -0
  19. package/src/utils/createLegacyAddress/createLegacyAddress.d.ts.map +1 -0
  20. package/src/utils/createLegacyAddress/index.d.ts +2 -0
  21. package/src/utils/createLegacyAddress/index.d.ts.map +1 -0
  22. package/src/utils/createNativeSegWitAddress/createNativeSegWitAddress.d.ts +10 -0
  23. package/src/utils/createNativeSegWitAddress/createNativeSegWitAddress.d.ts.map +1 -0
  24. package/src/utils/createNativeSegWitAddress/index.d.ts +2 -0
  25. package/src/utils/createNativeSegWitAddress/index.d.ts.map +1 -0
  26. package/src/utils/createSegWitAddress/createSegWitAddress.d.ts +10 -0
  27. package/src/utils/createSegWitAddress/createSegWitAddress.d.ts.map +1 -0
  28. package/src/utils/createSegWitAddress/index.d.ts +2 -0
  29. package/src/utils/createSegWitAddress/index.d.ts.map +1 -0
  30. package/src/utils/createTaprootAddress/createTaprootAddress.d.ts +10 -0
  31. package/src/utils/createTaprootAddress/createTaprootAddress.d.ts.map +1 -0
  32. package/src/utils/createTaprootAddress/index.d.ts +2 -0
  33. package/src/utils/createTaprootAddress/index.d.ts.map +1 -0
  34. package/src/utils/encodeBip322Signature/encodeBip322Signature.d.ts +13 -0
  35. package/src/utils/encodeBip322Signature/encodeBip322Signature.d.ts.map +1 -0
  36. package/src/utils/encodeBip322Signature/index.d.ts +2 -0
  37. package/src/utils/encodeBip322Signature/index.d.ts.map +1 -0
  38. package/src/utils/getAddressTypeFromDerivationPath/getAddressTypeFromDerivationPath.d.ts +9 -0
  39. package/src/utils/getAddressTypeFromDerivationPath/getAddressTypeFromDerivationPath.d.ts.map +1 -0
  40. package/src/utils/getAddressTypeFromDerivationPath/index.d.ts +2 -0
  41. package/src/utils/getAddressTypeFromDerivationPath/index.d.ts.map +1 -0
  42. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts +10 -0
  43. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts.map +1 -0
  44. package/src/utils/getBitcoinNetwork/index.d.ts +2 -0
  45. package/src/utils/getBitcoinNetwork/index.d.ts.map +1 -0
  46. package/src/utils/getDefaultRpcUrl/getDefaultRpcUrl.d.ts +9 -0
  47. package/src/utils/getDefaultRpcUrl/getDefaultRpcUrl.d.ts.map +1 -0
  48. package/src/utils/getDefaultRpcUrl/index.d.ts +2 -0
  49. package/src/utils/getDefaultRpcUrl/index.d.ts.map +1 -0
  50. package/src/utils/getFeeRates/getFeeRates.d.ts +18 -0
  51. package/src/utils/getFeeRates/getFeeRates.d.ts.map +1 -0
  52. package/src/utils/getFeeRates/index.d.ts +2 -0
  53. package/src/utils/getFeeRates/index.d.ts.map +1 -0
  54. package/src/utils/getUTXOs/getUTXOs.d.ts +15 -0
  55. package/src/utils/getUTXOs/getUTXOs.d.ts.map +1 -0
  56. package/src/utils/getUTXOs/index.d.ts +2 -0
  57. package/src/utils/getUTXOs/index.d.ts.map +1 -0
  58. package/src/utils/index.d.ts +12 -0
  59. package/src/utils/index.d.ts.map +1 -0
  60. package/src/utils/initEccLib/index.d.ts +2 -0
  61. package/src/utils/initEccLib/index.d.ts.map +1 -0
  62. package/src/utils/initEccLib/initEccLib.d.ts +5 -0
  63. package/src/utils/initEccLib/initEccLib.d.ts.map +1 -0
  64. package/src/utils/normalizeForCompressed/index.d.ts +2 -0
  65. package/src/utils/normalizeForCompressed/index.d.ts.map +1 -0
  66. package/src/utils/normalizeForCompressed/normalizeForCompressed.d.ts +9 -0
  67. package/src/utils/normalizeForCompressed/normalizeForCompressed.d.ts.map +1 -0
  68. package/src/utils/normalizeForTaproot/index.d.ts +2 -0
  69. package/src/utils/normalizeForTaproot/index.d.ts.map +1 -0
  70. package/src/utils/normalizeForTaproot/normalizeForTaproot.d.ts +9 -0
  71. package/src/utils/normalizeForTaproot/normalizeForTaproot.d.ts.map +1 -0
  72. package/src/utils/normalizePublicKey/index.d.ts +2 -0
  73. package/src/utils/normalizePublicKey/index.d.ts.map +1 -0
  74. package/src/utils/normalizePublicKey/normalizePublicKey.d.ts +14 -0
  75. package/src/utils/normalizePublicKey/normalizePublicKey.d.ts.map +1 -0
  76. package/src/utils/privateKeyToWIF/index.d.ts +2 -0
  77. package/src/utils/privateKeyToWIF/index.d.ts.map +1 -0
  78. package/src/utils/privateKeyToWIF/privateKeyToWIF.d.ts +24 -0
  79. package/src/utils/privateKeyToWIF/privateKeyToWIF.d.ts.map +1 -0
  80. package/src/utils/publicKeyToBitcoinAddress/index.d.ts +2 -0
  81. package/src/utils/publicKeyToBitcoinAddress/index.d.ts.map +1 -0
  82. package/src/utils/publicKeyToBitcoinAddress/publicKeyToBitcoinAddress.d.ts +12 -0
  83. package/src/utils/publicKeyToBitcoinAddress/publicKeyToBitcoinAddress.d.ts.map +1 -0
  84. package/src/utils/selectUTXOs/index.d.ts +2 -0
  85. package/src/utils/selectUTXOs/index.d.ts.map +1 -0
  86. package/src/utils/selectUTXOs/selectUTXOs.d.ts +19 -0
  87. package/src/utils/selectUTXOs/selectUTXOs.d.ts.map +1 -0
  88. package/src/utils/toBuffer/index.d.ts +2 -0
  89. package/src/utils/toBuffer/index.d.ts.map +1 -0
  90. package/src/utils/toBuffer/toBuffer.d.ts +8 -0
  91. package/src/utils/toBuffer/toBuffer.d.ts.map +1 -0
package/index.cjs.js CHANGED
@@ -1,5 +1,1138 @@
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
+ class DynamicBtcWalletClient extends browser.DynamicWalletClient {
567
+ /**
568
+ * Creates a Bitcoin wallet account
569
+ * @param thresholdSignatureScheme - The threshold signature scheme to use for the wallet
570
+ * @param password - The password to use for the wallet
571
+ * @param onError - The function to call if an error occurs
572
+ * @param signedSessionId - The signed session ID to use for the wallet
573
+ * @param addressType - The type of address to use for the wallet
574
+ * @returns The account address, public key hex, and raw public key
575
+ */ async createWalletAccount({ thresholdSignatureScheme, password = undefined, onError, signedSessionId, bitcoinConfig }) {
576
+ const network = browser.BitcoinNetwork.MAINNET;
577
+ const { addressType } = bitcoinConfig;
578
+ if (!addressType) {
579
+ throw new Error('addressType is required for BTC');
580
+ }
581
+ this.logger.debug('[BTC Client] Creating wallet account', {
582
+ package: 'packages/btc/src/client/client.ts',
583
+ method: 'createWalletAccount',
584
+ addressType,
585
+ network,
586
+ thresholdSignatureScheme,
587
+ signedSessionId
588
+ });
589
+ try {
590
+ // Create a promise that will resolve when the ceremony is complete
591
+ let ceremonyCompleteResolver;
592
+ const ceremonyCompletePromise = new Promise((resolve)=>{
593
+ ceremonyCompleteResolver = resolve;
594
+ });
595
+ // Validate address type is provided
596
+ if (!addressType) {
597
+ throw new Error('Address type is required for BTC');
598
+ }
599
+ const buildBitcoinConfig = {
600
+ addressType,
601
+ network
602
+ };
603
+ // Use 'BTC' as chain name, derivation path and signing algorithm will be resolved from addressType
604
+ const { rawPublicKey, clientKeyShares } = await this.keyGen({
605
+ chainName: this.chainName,
606
+ thresholdSignatureScheme,
607
+ bitcoinConfig: buildBitcoinConfig,
608
+ onError: (error)=>{
609
+ this.logger.error(browser.ERROR_CREATE_WALLET_ACCOUNT, error);
610
+ onError == null ? void 0 : onError(error);
611
+ },
612
+ onCeremonyComplete: (accountAddress, walletId)=>{
613
+ const chainConfig = browser.getMPCChainConfig(this.chainName, bitcoinConfig);
614
+ // Update wallet map with the derived account address
615
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
616
+ accountAddress: accountAddress,
617
+ walletId,
618
+ chainName: this.chainName,
619
+ thresholdSignatureScheme,
620
+ derivationPath: JSON.stringify(Object.fromEntries(chainConfig.derivationPath.map((value, index)=>[
621
+ index,
622
+ value
623
+ ]))),
624
+ addressType: addressType,
625
+ clientKeySharesBackupInfo: browser.getClientKeyShareBackupInfo()
626
+ });
627
+ this.logger.debug('[BTC Client] Updated walletMap in onCeremonyComplete', {
628
+ accountAddress: accountAddress,
629
+ walletId,
630
+ addressType: addressType
631
+ });
632
+ // Resolve the promise when ceremony is complete
633
+ ceremonyCompleteResolver(undefined);
634
+ }
635
+ });
636
+ // Wait for the ceremony to complete before proceeding
637
+ await ceremonyCompletePromise;
638
+ if (!rawPublicKey || !clientKeyShares) {
639
+ throw new Error('Key generation failed');
640
+ }
641
+ // Derive account address from raw public key
642
+ const { accountAddress } = this.deriveAccountAddress({
643
+ rawPublicKey,
644
+ addressType: addressType,
645
+ network
646
+ });
647
+ // Store client key shares to localStorage
648
+ await this.setClientKeySharesToLocalStorage({
649
+ accountAddress,
650
+ clientKeyShares,
651
+ overwriteOrMerge: 'overwrite'
652
+ });
653
+ // Store encrypted backup
654
+ await this.storeEncryptedBackupByWalletWithRetry({
655
+ accountAddress,
656
+ clientKeyShares,
657
+ password,
658
+ signedSessionId
659
+ });
660
+ const publicKeyHex = this.extractPublicKeyHex(rawPublicKey);
661
+ return {
662
+ accountAddress,
663
+ publicKeyHex,
664
+ rawPublicKey
665
+ };
666
+ } catch (error) {
667
+ if (onError) {
668
+ onError(error);
669
+ }
670
+ throw new Error(browser.ERROR_CREATE_WALLET_ACCOUNT);
671
+ }
672
+ }
673
+ /**
674
+ * Automatically determines the chain config based on address type
675
+ * @param rawPublicKey - The raw public key from the server
676
+ * @returns The public key hex
677
+ */ extractPublicKeyHex(rawPublicKey) {
678
+ if (rawPublicKey instanceof Uint8Array) {
679
+ return Buffer.from(rawPublicKey).toString('hex');
680
+ }
681
+ if (rawPublicKey && typeof rawPublicKey === 'object' && typeof rawPublicKey.pubKeyAsHex === 'function') {
682
+ return rawPublicKey.pubKeyAsHex();
683
+ }
684
+ if (typeof rawPublicKey === 'string') {
685
+ return rawPublicKey;
686
+ }
687
+ throw new Error('Invalid public key format');
688
+ }
689
+ /**
690
+ * Derives the Bitcoin account address
691
+ * - BIP340 keys (32 bytes x-only): Only for Taproot addresses
692
+ * - ECDSA keys (33/65 bytes): For all other address types (Legacy, SegWit, Native SegWit)
693
+ * - Algorithm selection is automatic based on addressType
694
+ * @param rawPublicKey - The raw public key to derive the account address from
695
+ * @param addressType - The address type to derive the account address for
696
+ * @param network - The network to derive the account address for
697
+ * @returns The account address
698
+ */ deriveAccountAddress({ rawPublicKey, addressType, network }) {
699
+ // Derive address based on the chosen address type and network
700
+ const normalizedKey = normalizePublicKey(rawPublicKey, addressType);
701
+ const accountAddress = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
702
+ return {
703
+ accountAddress
704
+ };
705
+ }
706
+ /**
707
+ * Signs a message (BIP-322)
708
+ * @param message - The message to sign
709
+ * @param accountAddress - The account address
710
+ * @param network - The network (mainnet/testnet)
711
+ * @param password - The wallet password (optional)
712
+ * @param signedSessionId - The signed session ID
713
+ * @param mfaToken - The MFA token (optional)
714
+ * @param context - Additional context
715
+ * @param onError - Error callback
716
+ * @returns The BIP-322 signature
717
+ */ async signMessage({ message, accountAddress, network, password = undefined, signedSessionId, mfaToken, context, onError }) {
718
+ await this.verifyPassword({
719
+ accountAddress,
720
+ password,
721
+ walletOperation: browser.WalletOperation.SIGN_MESSAGE,
722
+ signedSessionId
723
+ });
724
+ try {
725
+ if (!accountAddress) {
726
+ throw new Error(browser.ERROR_ACCOUNT_ADDRESS_REQUIRED);
727
+ }
728
+ const walletProperties = this.walletMap[accountAddress];
729
+ if (!walletProperties) {
730
+ throw new Error('Wallet not found in walletMap');
731
+ }
732
+ const derivationPath = walletProperties.derivationPath;
733
+ if (!derivationPath) {
734
+ throw new Error('Derivation path missing in walletMap');
735
+ }
736
+ let addressType = walletProperties.addressType;
737
+ if (!addressType) {
738
+ addressType = getAddressTypeFromDerivationPath(derivationPath);
739
+ }
740
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
741
+ accountAddress
742
+ });
743
+ if (!clientKeyShares || clientKeyShares.length === 0) {
744
+ throw new Error('No key shares found');
745
+ }
746
+ const bitcoinConfig = {
747
+ addressType,
748
+ network
749
+ };
750
+ const derivedPublicKey = await this.derivePublicKey({
751
+ chainName: this.chainName,
752
+ keyShare: clientKeyShares[0],
753
+ derivationPath: new Uint32Array(Object.values(JSON.parse(derivationPath))),
754
+ bitcoinConfig
755
+ });
756
+ if (!derivedPublicKey) {
757
+ throw new Error('Failed to derive public key');
758
+ }
759
+ const pubKey = normalizePublicKey(derivedPublicKey, addressType);
760
+ this.verifyWalletAddress(derivedPublicKey, addressType, network, accountAddress);
761
+ // Prepare BIP-322 Transactions and calculate hash
762
+ const { formattedMessage, toSignPsbt } = calculateBip322Hash(message, pubKey, addressType, network);
763
+ // Prepare tweak for Taproot in case of BIP340
764
+ let tweak;
765
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
766
+ const tweakHash = bitcoin__namespace.crypto.taggedHash('TapTweak', pubKey);
767
+ tweak = Buffer.from(tweakHash).toString('hex');
768
+ }
769
+ // Build complete bitcoinConfig with addressType and tweak
770
+ const completeBitcoinConfig = _extends({}, bitcoinConfig, {
771
+ addressType: addressType,
772
+ tweak
773
+ });
774
+ // Sign the message using MPC
775
+ const signature = await this.sign({
776
+ message: formattedMessage,
777
+ accountAddress,
778
+ chainName: this.chainName,
779
+ password,
780
+ signedSessionId,
781
+ mfaToken,
782
+ isFormatted: true,
783
+ context: context != null ? context : {
784
+ btcMessage: message
785
+ },
786
+ bitcoinConfig: completeBitcoinConfig,
787
+ onError
788
+ });
789
+ // Encode Final Result
790
+ const bip322Signature = encodeBip322Signature(toSignPsbt, pubKey, signature, addressType);
791
+ this.logger.debug('[BTC Client] signMessage - bip322Signature', {
792
+ bip322Signature
793
+ });
794
+ return bip322Signature;
795
+ } catch (error) {
796
+ if (onError) {
797
+ onError(error);
798
+ }
799
+ throw new Error(browser.ERROR_SIGN_MESSAGE);
800
+ }
801
+ }
802
+ /**
803
+ * Verifies that the derived address matches the expected address
804
+ * @param rawPublicKey - The raw public key
805
+ * @param addressType - The address type
806
+ * @param network - The network
807
+ * @param expectedAddress - The expected address
808
+ */ verifyWalletAddress(rawPublicKey, addressType, network, expectedAddress) {
809
+ const normalizedKey = normalizePublicKey(rawPublicKey, addressType);
810
+ const derivedAddress = publicKeyToBitcoinAddress(normalizedKey, addressType, network);
811
+ if (derivedAddress !== expectedAddress) {
812
+ throw new Error(`Address verification failed: expected ${expectedAddress}, got ${derivedAddress}`);
813
+ }
814
+ }
815
+ /**
816
+ * Exports the private key for a given account
817
+ * @param accountAddress - The account address to export the private key for
818
+ * @param password - The password to use for the private key
819
+ * @param signedSessionId - The signed session ID to use for the private key
820
+ * @param mfaToken - The MFA token to use for the private key
821
+ * @param network - The network to use for the private key
822
+ * @returns The private key
823
+ */ async exportPrivateKey({ accountAddress, password = undefined, signedSessionId, mfaToken }) {
824
+ await this.verifyPassword({
825
+ accountAddress,
826
+ password,
827
+ walletOperation: browser.WalletOperation.EXPORT_PRIVATE_KEY,
828
+ signedSessionId
829
+ });
830
+ // Get wallet properties to determine addressType
831
+ const walletProperties = this.walletMap[accountAddress];
832
+ if (!walletProperties) {
833
+ throw new Error('Wallet not found in walletMap');
834
+ }
835
+ const derivationPath = walletProperties.derivationPath;
836
+ if (!derivationPath) {
837
+ throw new Error('Derivation path missing in walletMap');
838
+ }
839
+ let addressType = walletProperties.addressType;
840
+ if (!addressType && walletProperties.derivationPath) {
841
+ try {
842
+ addressType = getAddressTypeFromDerivationPath(walletProperties.derivationPath);
843
+ } catch (e) {
844
+ this.logger.warn('Failed to infer address type from derivation path', e);
845
+ }
846
+ }
847
+ if (!addressType) {
848
+ throw new Error('Address type not found in walletMap');
849
+ }
850
+ const { derivedPrivateKey } = await this.exportKey({
851
+ accountAddress: accountAddress,
852
+ chainName: this.chainName,
853
+ bitcoinConfig: {
854
+ addressType
855
+ },
856
+ password,
857
+ signedSessionId,
858
+ mfaToken
859
+ });
860
+ if (!derivedPrivateKey) {
861
+ throw new Error('Failed to derive private key');
862
+ }
863
+ // Convert MPC private key to Bitcoin WIF format
864
+ return this.convertPrivateKeyToBitcoinFormat(derivedPrivateKey, browser.BitcoinNetwork.MAINNET);
865
+ }
866
+ /**
867
+ * Converts MPC private key to Bitcoin WIF (Wallet Import Format)
868
+ * Uses the utility function from utils.ts for the core conversion logic
869
+ * @param privateKey - The private key to convert to a Bitcoin WIF format
870
+ * @param network - The network to convert the private key to a Bitcoin WIF format for
871
+ * @returns The Bitcoin WIF format
872
+ */ convertPrivateKeyToBitcoinFormat(privateKey, network) {
873
+ try {
874
+ const wif = privateKeyToWIF(privateKey, network, {
875
+ compressed: true,
876
+ onWarning: (message, data)=>{
877
+ this.logger.warn(`[BTC Client] convertPrivateKeyToBitcoinFormat - ${message}`, data);
878
+ }
879
+ });
880
+ this.logger.debug('[BTC Client] convertPrivateKeyToBitcoinFormat - converted to WIF', {
881
+ network,
882
+ wifLength: wif.length,
883
+ wifPrefix: wif.substring(0, 1)
884
+ });
885
+ return wif;
886
+ } catch (error) {
887
+ this.logger.error('[BTC Client] convertPrivateKeyToBitcoinFormat - error converting to WIF', {
888
+ error,
889
+ privateKeyLength: privateKey.length,
890
+ network
891
+ });
892
+ const cleanPrivateKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
893
+ return cleanPrivateKey;
894
+ }
895
+ }
896
+ /**
897
+ * Sign a Bitcoin Transaction (PSBT)
898
+ *
899
+ * @param transaction - The PSBT to sign in hex format
900
+ * @param senderAddress - The address of the sender
901
+ * @param password - The password to use for the transaction
902
+ * @param signedSessionId - The signed session ID to use for the transaction
903
+ * @param authToken - The auth token to use for the transaction
904
+ * @param mfaToken - The MFA token to use for the transaction
905
+ * @param context - The context to use for the transaction
906
+ * @param onError - The error handler
907
+ * @returns The signed PSBT
908
+ */ async signTransaction({ transaction, senderAddress, network, password, signedSessionId, mfaToken, context, onError }) {
909
+ try {
910
+ await this.verifyPassword({
911
+ accountAddress: senderAddress,
912
+ password,
913
+ walletOperation: browser.WalletOperation.SIGN_TRANSACTION,
914
+ signedSessionId
915
+ });
916
+ if (!senderAddress) {
917
+ throw new Error(browser.ERROR_ACCOUNT_ADDRESS_REQUIRED);
918
+ }
919
+ const walletProperties = this.walletMap[senderAddress];
920
+ if (!walletProperties) {
921
+ throw new Error('Wallet not found in walletMap');
922
+ }
923
+ const derivationPath = walletProperties.derivationPath;
924
+ if (!derivationPath) {
925
+ throw new Error('Derivation path missing in walletMap');
926
+ }
927
+ let addressType = walletProperties.addressType;
928
+ if (!addressType) {
929
+ addressType = getAddressTypeFromDerivationPath(derivationPath);
930
+ }
931
+ const bitcoinConfig = {
932
+ addressType,
933
+ network
934
+ };
935
+ const psbt = bitcoin__namespace.Psbt.fromBase64(transaction);
936
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
937
+ accountAddress: senderAddress
938
+ });
939
+ if (!clientKeyShares || clientKeyShares.length === 0) {
940
+ throw new Error('No key shares found');
941
+ }
942
+ const derivedPublicKey = await this.derivePublicKey({
943
+ chainName: this.chainName,
944
+ keyShare: clientKeyShares[0],
945
+ derivationPath: new Uint32Array(Object.values(JSON.parse(derivationPath))),
946
+ bitcoinConfig
947
+ });
948
+ if (!derivedPublicKey) {
949
+ throw new Error('Failed to derive public key');
950
+ }
951
+ const pubKey = normalizePublicKey(derivedPublicKey, addressType);
952
+ // Iterate and sign inputs in parallel for better performance
953
+ await Promise.all(psbt.data.inputs.map(async (input, i)=>{
954
+ if (!input.witnessUtxo) {
955
+ throw new Error(`Input ${i} missing witnessUtxo`);
956
+ }
957
+ const { script, value } = input.witnessUtxo;
958
+ const p2pkh = bitcoin__namespace.payments.p2pkh({
959
+ hash: script.slice(2),
960
+ network: getBitcoinNetwork(network)
961
+ });
962
+ const scriptCode = p2pkh.output;
963
+ if (!scriptCode) throw new Error('Failed to generate scriptCode');
964
+ const tx = psbt.__CACHE.__TX;
965
+ const hash = tx.hashForWitnessV0(i, scriptCode, value, bitcoin__namespace.Transaction.SIGHASH_ALL);
966
+ const signature = await this.sign({
967
+ message: new Uint8Array(hash),
968
+ accountAddress: senderAddress,
969
+ chainName: this.chainName,
970
+ password,
971
+ signedSessionId,
972
+ mfaToken,
973
+ isFormatted: true,
974
+ context: context,
975
+ bitcoinConfig,
976
+ onError
977
+ });
978
+ const derSignature = convertSignatureToDER(signature);
979
+ psbt.updateInput(i, {
980
+ partialSig: [
981
+ {
982
+ pubkey: pubKey,
983
+ signature: new Uint8Array(derSignature)
984
+ }
985
+ ]
986
+ });
987
+ }));
988
+ psbt.finalizeAllInputs();
989
+ const transactionHex = psbt.extractTransaction().toHex();
990
+ this.logger.debug('[BTC Client] signTransaction - transactionHex', {
991
+ transactionHex
992
+ });
993
+ return transactionHex;
994
+ } catch (error) {
995
+ if (onError) {
996
+ onError(error);
997
+ }
998
+ throw error;
999
+ }
1000
+ }
1001
+ /**
1002
+ * Creates a PSBT for a transaction
1003
+ * @param receiverAddress - The address to send funds to
1004
+ * @param amount - The amount to send in satoshis
1005
+ * @param senderAddress - The address to send funds from
1006
+ * @param network - The network to use (mainnet/testnet)
1007
+ * @param feeRateLevel - The fee rate level to use (fast, medium, slow)
1008
+ * @returns The PSBT in hex format
1009
+ */ async createTransaction({ receiverAddress, amount, senderAddress, network, feeRateLevel = 'medium' }) {
1010
+ this.logger.debug('[BTC Client] Creating transaction', {
1011
+ receiverAddress,
1012
+ amount,
1013
+ senderAddress,
1014
+ network,
1015
+ feeRateLevel
1016
+ });
1017
+ if (!senderAddress) {
1018
+ throw new Error(browser.ERROR_ACCOUNT_ADDRESS_REQUIRED);
1019
+ }
1020
+ const walletProperties = this.walletMap[senderAddress];
1021
+ if (!walletProperties) {
1022
+ throw new Error('Wallet not found in walletMap');
1023
+ }
1024
+ let addressType = walletProperties.addressType;
1025
+ if (!addressType && walletProperties.derivationPath) {
1026
+ addressType = getAddressTypeFromDerivationPath(walletProperties.derivationPath);
1027
+ }
1028
+ if (!addressType) {
1029
+ throw new Error('Address type not determined');
1030
+ }
1031
+ if (addressType === browser.BitcoinAddressType.TAPROOT) {
1032
+ throw new Error('Taproot transaction creation not yet supported');
1033
+ }
1034
+ const bitcoinNetwork = network === 'testnet' ? bitcoin__namespace.networks.testnet : bitcoin__namespace.networks.bitcoin;
1035
+ // Fetch fee rates
1036
+ const feeRates = await getFeeRates({
1037
+ network
1038
+ });
1039
+ const feeRate = feeRates[feeRateLevel];
1040
+ // Fetch UTXOs
1041
+ const utxos = await getUTXOs({
1042
+ address: senderAddress,
1043
+ network
1044
+ });
1045
+ // Select UTXOs
1046
+ const { inputs, change, fee } = selectUTXOs({
1047
+ utxos,
1048
+ amount: BigInt(amount),
1049
+ feeRate
1050
+ });
1051
+ const psbt = new bitcoin__namespace.Psbt({
1052
+ network: bitcoinNetwork
1053
+ });
1054
+ // Prepare inputs
1055
+ await Promise.all(inputs.map(async (utxo)=>{
1056
+ const txHex = await this.getTxHex(utxo.txid, network);
1057
+ const inputData = {
1058
+ hash: utxo.txid,
1059
+ index: utxo.vout,
1060
+ nonWitnessUtxo: Buffer.from(txHex, 'hex')
1061
+ };
1062
+ if (addressType === browser.BitcoinAddressType.NATIVE_SEGWIT) {
1063
+ const p2wpkh = bitcoin__namespace.payments.p2wpkh({
1064
+ address: senderAddress,
1065
+ network: bitcoinNetwork
1066
+ });
1067
+ if (!p2wpkh.output) {
1068
+ throw new Error('Failed to generate scriptCode for P2WPKH');
1069
+ }
1070
+ inputData.witnessUtxo = {
1071
+ script: p2wpkh.output,
1072
+ value: BigInt(utxo.value)
1073
+ };
1074
+ } else if (addressType === browser.BitcoinAddressType.TAPROOT) {
1075
+ throw new Error('Taproot transaction creation not yet supported');
1076
+ }
1077
+ psbt.addInput(inputData);
1078
+ }));
1079
+ // Add Receiver Output
1080
+ psbt.addOutput({
1081
+ address: receiverAddress,
1082
+ value: BigInt(amount)
1083
+ });
1084
+ // Add Change Output
1085
+ if (change > 0n) {
1086
+ psbt.addOutput({
1087
+ address: senderAddress,
1088
+ value: change
1089
+ });
1090
+ }
1091
+ this.logger.debug('[BTC Client] Transaction created', {
1092
+ fee,
1093
+ change,
1094
+ inputCount: inputs.length
1095
+ });
1096
+ return psbt.toHex();
1097
+ }
1098
+ /**
1099
+ * Fetches the raw transaction hex for a given transaction ID
1100
+ * @param txid - The transaction ID
1101
+ * @param network - The network
1102
+ * @returns The transaction hex
1103
+ */ async getTxHex(txid, network) {
1104
+ const baseUrl = getDefaultRpcUrl(network);
1105
+ const response = await fetch(`${baseUrl}/tx/${txid}/hex`);
1106
+ if (!response.ok) {
1107
+ throw new Error(`Failed to fetch TX hex: ${response.statusText}`);
1108
+ }
1109
+ return await response.text();
1110
+ }
1111
+ /**
1112
+ * Gets the Bitcoin wallets
1113
+ * @returns The Bitcoin wallets
1114
+ */ async getBitcoinWallets() {
1115
+ const wallets = await this.getWallets();
1116
+ const btcWallets = wallets.filter((wallet)=>wallet.chainName === 'bitcoin');
1117
+ return btcWallets;
1118
+ }
1119
+ /**
1120
+ * Creates a new instance of DynamicBtcWalletClient
1121
+ * @param props - The client properties
1122
+ */ constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug, featureFlags, authMode = browser.AuthMode.HEADER, sdkVersion, forwardMPCClient }){
1123
+ super({
1124
+ environmentId,
1125
+ authToken,
1126
+ baseApiUrl,
1127
+ baseMPCRelayApiUrl,
1128
+ storageKey,
1129
+ debug,
1130
+ featureFlags,
1131
+ authMode,
1132
+ sdkVersion,
1133
+ forwardMPCClient
1134
+ }), this.chainName = 'BTC';
1135
+ }
1136
+ }
1137
+
1138
+ exports.DynamicBtcWalletClient = DynamicBtcWalletClient;