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