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