@xchainjs/xchain-bitcoin 2.0.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AssetInfo, FeeRate } from '@xchainjs/xchain-client';
2
2
  import { Address } from '@xchainjs/xchain-util';
3
- import { Client as UTXOClient, PreparedTx, TxParams, UTXO, UtxoClientParams } from '@xchainjs/xchain-utxo';
3
+ import { Client as UTXOClient, PreparedTx, TxParams, UTXO, UtxoClientParams, UtxoSelectionPreferences } from '@xchainjs/xchain-utxo';
4
4
  import * as Bitcoin from 'bitcoinjs-lib';
5
5
  import { AddressFormat } from './types';
6
6
  export declare const defaultBTCParams: UtxoClientParams;
@@ -43,9 +43,24 @@ declare abstract class Client extends UTXOClient {
43
43
  */
44
44
  protected getFeeFromUtxos(inputs: UTXO[], feeRate: FeeRate, data?: Buffer | null): number;
45
45
  /**
46
- * Build a Bitcoin transaction.*
46
+ * Enhanced Bitcoin transaction builder with comprehensive validation and optimal UTXO selection
47
+ * @param params Transaction parameters
48
+ * @returns Enhanced transaction build result with PSBT, UTXOs, and inputs
49
+ */
50
+ buildTxEnhanced({ amount, recipient, memo, feeRate, sender, spendPendingUTXO, utxoSelectionPreferences, }: TxParams & {
51
+ feeRate: FeeRate;
52
+ sender: Address;
53
+ spendPendingUTXO?: boolean;
54
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
55
+ }): Promise<{
56
+ psbt: Bitcoin.Psbt;
57
+ utxos: UTXO[];
58
+ inputs: UTXO[];
59
+ }>;
60
+ /**
61
+ * Build a Bitcoin transaction with enhanced validation and performance.
62
+ * Now uses the enhanced logic internally while maintaining the same API.
47
63
  * @param param0
48
- * @deprecated
49
64
  */
50
65
  buildTx({ amount, recipient, memo, feeRate, sender, spendPendingUTXO, }: TxParams & {
51
66
  feeRate: FeeRate;
@@ -58,8 +73,57 @@ declare abstract class Client extends UTXOClient {
58
73
  inputs: UTXO[];
59
74
  }>;
60
75
  /**
61
- * Prepare transfer.
76
+ * Send maximum possible amount (sweep) with optimal fee calculation
77
+ * @param params Send max parameters
78
+ * @returns Transaction details with maximum sendable amount
79
+ */
80
+ sendMax({ sender, recipient, memo, feeRate, spendPendingUTXO, utxoSelectionPreferences, }: {
81
+ sender: Address;
82
+ recipient: Address;
83
+ memo?: string;
84
+ feeRate: FeeRate;
85
+ spendPendingUTXO?: boolean;
86
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
87
+ }): Promise<{
88
+ psbt: Bitcoin.Psbt;
89
+ utxos: UTXO[];
90
+ inputs: UTXO[];
91
+ maxAmount: number;
92
+ fee: number;
93
+ }>;
94
+ /**
95
+ * Prepare maximum amount transfer (sweep transaction)
96
+ * @param params Send max parameters
97
+ * @returns Prepared transaction with maximum sendable amount
98
+ */
99
+ prepareMaxTx({ sender, recipient, memo, feeRate, spendPendingUTXO, utxoSelectionPreferences, }: {
100
+ sender: Address;
101
+ recipient: Address;
102
+ memo?: string;
103
+ feeRate: FeeRate;
104
+ spendPendingUTXO?: boolean;
105
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
106
+ }): Promise<PreparedTx & {
107
+ maxAmount: number;
108
+ fee: number;
109
+ }>;
110
+ /**
111
+ * Enhanced prepare transfer with comprehensive validation and optimal UTXO selection.
112
+ *
113
+ * @param params The transfer options with enhanced UTXO selection preferences.
114
+ * @returns The raw unsigned transaction with enhanced error handling.
115
+ */
116
+ prepareTxEnhanced({ sender, memo, amount, recipient, spendPendingUTXO, feeRate, utxoSelectionPreferences, }: TxParams & {
117
+ sender: Address;
118
+ feeRate: FeeRate;
119
+ spendPendingUTXO?: boolean;
120
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
121
+ }): Promise<PreparedTx>;
122
+ /**
123
+ * Prepare transfer with enhanced validation and performance.
124
+ * Now uses the enhanced logic internally while maintaining the same API.
62
125
  *
126
+ * @deprecated Use `prepareTxEnhanced` directly for explicit enhanced UTXO selection.
63
127
  * @param {TxParams&Address&FeeRate&boolean} params The transfer options.
64
128
  * @returns {PreparedTx} The raw unsigned transaction.
65
129
  */
@@ -1,6 +1,6 @@
1
1
  import { FeeRate, TxHash } from '@xchainjs/xchain-client';
2
2
  import { Address } from '@xchainjs/xchain-util';
3
- import { TxParams, UtxoClientParams } from '@xchainjs/xchain-utxo';
3
+ import { TxParams, UtxoClientParams, UtxoSelectionPreferences } from '@xchainjs/xchain-utxo';
4
4
  import { Client } from './client';
5
5
  import { AddressFormat } from './types';
6
6
  /**
@@ -41,11 +41,35 @@ declare class ClientKeystore extends Client {
41
41
  * Transfer BTC.
42
42
  *
43
43
  * @param {TxParams&FeeRate} params The transfer options including the fee rate.
44
- * @returns {Promise<TxHash|string>} A promise that resolves to the transaction hash or an error message.
44
+ * @returns {Promise<TxHash>} A promise that resolves to the transaction hash.
45
45
  * @throws {"memo too long"} Thrown if the memo is longer than 80 characters.
46
46
  */
47
47
  transfer(params: TxParams & {
48
48
  feeRate?: FeeRate;
49
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
49
50
  }): Promise<TxHash>;
51
+ /**
52
+ * Transfer the maximum amount of Bitcoin (sweep).
53
+ *
54
+ * Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
55
+ * @param {Object} params The transfer parameters.
56
+ * @param {string} params.recipient The recipient address.
57
+ * @param {string} [params.memo] Optional memo for the transaction.
58
+ * @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
59
+ * @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
60
+ * @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
61
+ * @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
62
+ */
63
+ transferMax(params: {
64
+ recipient: Address;
65
+ memo?: string;
66
+ feeRate?: FeeRate;
67
+ walletIndex?: number;
68
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
69
+ }): Promise<{
70
+ hash: TxHash;
71
+ maxAmount: number;
72
+ fee: number;
73
+ }>;
50
74
  }
51
75
  export { ClientKeystore };
@@ -1,7 +1,7 @@
1
1
  import AppBtc from '@ledgerhq/hw-app-btc';
2
2
  import { FeeRate, TxHash } from '@xchainjs/xchain-client';
3
3
  import { Address } from '@xchainjs/xchain-util';
4
- import { TxParams, UtxoClientParams } from '@xchainjs/xchain-utxo';
4
+ import { TxParams, UtxoClientParams, UtxoSelectionPreferences } from '@xchainjs/xchain-utxo';
5
5
  import { Client } from './client';
6
6
  import { AddressFormat } from './types';
7
7
  /**
@@ -19,6 +19,7 @@ declare class ClientLedger extends Client {
19
19
  getAddressAsync(index?: number, verify?: boolean): Promise<Address>;
20
20
  transfer(params: TxParams & {
21
21
  feeRate?: FeeRate;
22
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
22
23
  }): Promise<TxHash>;
23
24
  }
24
25
  export { ClientLedger };
package/lib/index.esm.js CHANGED
@@ -4,8 +4,7 @@ import { getSeed } from '@xchainjs/xchain-crypto';
4
4
  import { HDKey } from '@scure/bip32';
5
5
  import * as Bitcoin from 'bitcoinjs-lib';
6
6
  import { ECPairFactory } from 'ecpair';
7
- import { Client as Client$1 } from '@xchainjs/xchain-utxo';
8
- import accumulative from 'coinselect/accumulative.js';
7
+ import { Client as Client$1, UtxoError } from '@xchainjs/xchain-utxo';
9
8
  import { AssetType } from '@xchainjs/xchain-util';
10
9
  import { SochainProvider, SochainNetwork, HaskoinProvider, HaskoinNetwork, BlockcypherProvider, BlockcypherNetwork, BitgoProvider } from '@xchainjs/xchain-utxo-providers';
11
10
  import AppBtc from '@ledgerhq/hw-app-btc';
@@ -160,7 +159,7 @@ const validateAddress = (address, network) => {
160
159
  Bitcoin.address.toOutputScript(address, btcNetwork(network)); // Try to convert the address to an output script using the specified network
161
160
  return true; // If successful, the address is valid
162
161
  }
163
- catch (_error) {
162
+ catch (_a) {
164
163
  return false; // If an error occurs, the address is invalid
165
164
  }
166
165
  };
@@ -285,98 +284,242 @@ class Client extends Client$1 {
285
284
  return fee > MIN_TX_FEE ? fee : MIN_TX_FEE;
286
285
  }
287
286
  /**
288
- * Build a Bitcoin transaction.*
287
+ * Enhanced Bitcoin transaction builder with comprehensive validation and optimal UTXO selection
288
+ * @param params Transaction parameters
289
+ * @returns Enhanced transaction build result with PSBT, UTXOs, and inputs
290
+ */
291
+ buildTxEnhanced(_a) {
292
+ return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, utxoSelectionPreferences, }) {
293
+ try {
294
+ // Comprehensive input validation
295
+ this.validateTransactionInputs({
296
+ amount,
297
+ recipient,
298
+ memo,
299
+ sender,
300
+ feeRate,
301
+ });
302
+ // Get validated UTXOs
303
+ const confirmedOnly = !spendPendingUTXO;
304
+ const utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
305
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
306
+ const targetValue = amount.amount().toNumber();
307
+ const extraOutputs = 1 + (compiledMemo ? 1 : 0); // recipient + optional memo (change calculated separately)
308
+ // Enhanced UTXO selection
309
+ const selectionResult = this.selectUtxosForTransaction(utxos, targetValue, Math.ceil(feeRate), extraOutputs, utxoSelectionPreferences);
310
+ const psbt = new Bitcoin.Psbt({ network: btcNetwork(this.network) });
311
+ // Add inputs based on selection
312
+ if (this.addressFormat === AddressFormat.P2WPKH) {
313
+ selectionResult.inputs.forEach((utxo) => {
314
+ if (!utxo.witnessUtxo) {
315
+ throw UtxoError.fromUnknown(new Error(`Missing witnessUtxo for UTXO ${utxo.hash}:${utxo.index}`), 'buildTxPsbt');
316
+ }
317
+ psbt.addInput({
318
+ hash: utxo.hash,
319
+ index: utxo.index,
320
+ witnessUtxo: utxo.witnessUtxo,
321
+ });
322
+ });
323
+ }
324
+ else {
325
+ const { pubkey, output } = Bitcoin.payments.p2tr({
326
+ address: sender,
327
+ });
328
+ selectionResult.inputs.forEach((utxo) => psbt.addInput({
329
+ hash: utxo.hash,
330
+ index: utxo.index,
331
+ witnessUtxo: { value: utxo.value, script: output },
332
+ tapInternalKey: pubkey,
333
+ }));
334
+ }
335
+ // Add recipient output
336
+ psbt.addOutput({
337
+ address: recipient,
338
+ value: targetValue,
339
+ });
340
+ // Add change output if needed
341
+ if (selectionResult.changeAmount > 0) {
342
+ psbt.addOutput({
343
+ address: sender,
344
+ value: selectionResult.changeAmount,
345
+ });
346
+ }
347
+ // Add memo output if present
348
+ if (compiledMemo) {
349
+ psbt.addOutput({ script: compiledMemo, value: 0 });
350
+ }
351
+ return { psbt, utxos, inputs: selectionResult.inputs };
352
+ }
353
+ catch (error) {
354
+ if (UtxoError.isUtxoError(error)) {
355
+ throw error;
356
+ }
357
+ throw UtxoError.fromUnknown(error, 'buildTxEnhanced');
358
+ }
359
+ });
360
+ }
361
+ /**
362
+ * Build a Bitcoin transaction with enhanced validation and performance.
363
+ * Now uses the enhanced logic internally while maintaining the same API.
289
364
  * @param param0
290
- * @deprecated
291
365
  */
292
366
  buildTx(_a) {
293
367
  return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, }) {
294
- // Check memo length
295
- if (memo && memo.length > 80) {
296
- throw new Error('memo too long, must not be longer than 80 chars.');
297
- }
298
- // This section of the code is responsible for preparing a transaction by building a Bitcoin PSBT (Partially Signed Bitcoin Transaction).
299
- if (!this.validateAddress(recipient))
300
- throw new Error('Invalid address');
301
- // Determine whether to only use confirmed UTXOs or include pending UTXOs based on the spendPendingUTXO flag.
302
- const confirmedOnly = !spendPendingUTXO;
303
- // Scan UTXOs associated with the sender's address.
304
- const utxos = yield this.scanUTXOs(sender, confirmedOnly);
305
- // Throw an error if there are no available UTXOs to cover the transaction.
306
- if (utxos.length === 0)
307
- throw new Error('Insufficient Balance for transaction');
308
- // Round up the fee rate to the nearest integer.
309
- const feeRateWhole = Math.ceil(feeRate);
310
- // Compile the memo into a Buffer if provided.
311
- const compiledMemo = memo ? this.compileMemo(memo) : null;
312
- // Initialize an array to store the target outputs of the transaction.
313
- const targetOutputs = [];
314
- // 1. Add the recipient address and amount to the target outputs.
315
- targetOutputs.push({
316
- address: recipient,
317
- value: amount.amount().toNumber(),
368
+ // Use the enhanced logic internally while maintaining the same API
369
+ return this.buildTxEnhanced({
370
+ amount,
371
+ recipient,
372
+ memo,
373
+ feeRate,
374
+ sender,
375
+ spendPendingUTXO,
318
376
  });
319
- // 2. Add the compiled memo to the target outputs if it exists.
320
- if (compiledMemo) {
321
- targetOutputs.push({ script: compiledMemo, value: 0 });
377
+ });
378
+ }
379
+ /**
380
+ * Send maximum possible amount (sweep) with optimal fee calculation
381
+ * @param params Send max parameters
382
+ * @returns Transaction details with maximum sendable amount
383
+ */
384
+ sendMax(_a) {
385
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, }) {
386
+ try {
387
+ // Basic validation (skip amount validation since we're calculating max)
388
+ if (!(recipient === null || recipient === void 0 ? void 0 : recipient.trim())) {
389
+ throw UtxoError.invalidAddress(recipient, this.network);
390
+ }
391
+ if (!this.validateAddress(recipient)) {
392
+ throw UtxoError.invalidAddress(recipient, this.network);
393
+ }
394
+ if (!this.validateAddress(sender)) {
395
+ throw UtxoError.invalidAddress(sender, this.network);
396
+ }
397
+ // Memo validation is handled by validateTransactionInputs
398
+ // Get validated UTXOs
399
+ const confirmedOnly = !spendPendingUTXO;
400
+ const utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
401
+ // Calculate maximum sendable amount
402
+ const maxCalc = this.calculateMaxSendableAmount(utxos, Math.ceil(feeRate), !!memo, utxoSelectionPreferences);
403
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
404
+ const psbt = new Bitcoin.Psbt({ network: btcNetwork(this.network) });
405
+ // Add inputs
406
+ if (this.addressFormat === AddressFormat.P2WPKH) {
407
+ maxCalc.inputs.forEach((utxo) => psbt.addInput({
408
+ hash: utxo.hash,
409
+ index: utxo.index,
410
+ witnessUtxo: utxo.witnessUtxo,
411
+ }));
412
+ }
413
+ else {
414
+ const { pubkey, output } = Bitcoin.payments.p2tr({
415
+ address: sender,
416
+ });
417
+ maxCalc.inputs.forEach((utxo) => psbt.addInput({
418
+ hash: utxo.hash,
419
+ index: utxo.index,
420
+ witnessUtxo: { value: utxo.value, script: output },
421
+ tapInternalKey: pubkey,
422
+ }));
423
+ }
424
+ // Add recipient output (max amount - no change)
425
+ psbt.addOutput({
426
+ address: recipient,
427
+ value: maxCalc.amount,
428
+ });
429
+ // Add memo output if present
430
+ if (compiledMemo) {
431
+ psbt.addOutput({ script: compiledMemo, value: 0 });
432
+ }
433
+ return {
434
+ psbt,
435
+ utxos,
436
+ inputs: maxCalc.inputs,
437
+ maxAmount: maxCalc.amount,
438
+ fee: maxCalc.fee,
439
+ };
322
440
  }
323
- // Use the coinselect library to determine the inputs and outputs for the transaction.
324
- const { inputs, outputs } = accumulative(utxos, targetOutputs, feeRateWhole);
325
- // If no suitable inputs or outputs are found, throw an error indicating insufficient balance.
326
- if (!inputs || !outputs)
327
- throw new Error('Insufficient Balance for transaction');
328
- // Initialize a new Bitcoin PSBT object.
329
- const psbt = new Bitcoin.Psbt({ network: btcNetwork(this.network) }); // Network-specific
330
- if (this.addressFormat === AddressFormat.P2WPKH) {
331
- // Add inputs to the PSBT from the accumulated inputs.
332
- inputs.forEach((utxo) => psbt.addInput({
333
- hash: utxo.hash,
334
- index: utxo.index,
335
- witnessUtxo: utxo.witnessUtxo,
336
- }));
441
+ catch (error) {
442
+ if (UtxoError.isUtxoError(error)) {
443
+ throw error;
444
+ }
445
+ throw UtxoError.fromUnknown(error, 'sendMax');
337
446
  }
338
- else {
339
- const { pubkey, output } = Bitcoin.payments.p2tr({
340
- address: sender,
447
+ });
448
+ }
449
+ /**
450
+ * Prepare maximum amount transfer (sweep transaction)
451
+ * @param params Send max parameters
452
+ * @returns Prepared transaction with maximum sendable amount
453
+ */
454
+ prepareMaxTx(_a) {
455
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, }) {
456
+ try {
457
+ const { psbt, utxos, inputs, maxAmount, fee } = yield this.sendMax({
458
+ sender,
459
+ recipient,
460
+ memo,
461
+ feeRate,
462
+ spendPendingUTXO,
463
+ utxoSelectionPreferences,
341
464
  });
342
- inputs.forEach((utxo) => psbt.addInput({
343
- hash: utxo.hash,
344
- index: utxo.index,
345
- witnessUtxo: { value: utxo.value, script: output },
346
- tapInternalKey: pubkey,
347
- }));
465
+ return {
466
+ rawUnsignedTx: psbt.toBase64(),
467
+ utxos,
468
+ inputs,
469
+ maxAmount,
470
+ fee,
471
+ };
348
472
  }
349
- // Add outputs to the PSBT from the accumulated outputs.
350
- outputs.forEach((output) => {
351
- // If the output address is not specified, it's considered a change address and set to the sender's address.
352
- if (!output.address) {
353
- //an empty address means this is the change address
354
- output.address = sender;
355
- }
356
- // Add the output to the PSBT.
357
- if (!output.script) {
358
- psbt.addOutput(output);
473
+ catch (error) {
474
+ if (UtxoError.isUtxoError(error)) {
475
+ console.error('Bitcoin max transaction preparation failed:', error.toJSON());
476
+ throw error;
359
477
  }
360
- else {
361
- // If the output is a memo, add it to the PSBT to avoid dust error.
362
- if (compiledMemo) {
363
- psbt.addOutput({ script: compiledMemo, value: 0 });
364
- }
478
+ throw UtxoError.fromUnknown(error, 'prepareMaxTx');
479
+ }
480
+ });
481
+ }
482
+ /**
483
+ * Enhanced prepare transfer with comprehensive validation and optimal UTXO selection.
484
+ *
485
+ * @param params The transfer options with enhanced UTXO selection preferences.
486
+ * @returns The raw unsigned transaction with enhanced error handling.
487
+ */
488
+ prepareTxEnhanced(_a) {
489
+ return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, utxoSelectionPreferences, }) {
490
+ try {
491
+ const { psbt, utxos, inputs } = yield this.buildTxEnhanced({
492
+ sender,
493
+ recipient,
494
+ amount,
495
+ feeRate,
496
+ memo,
497
+ spendPendingUTXO,
498
+ utxoSelectionPreferences,
499
+ });
500
+ return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
501
+ }
502
+ catch (error) {
503
+ if (UtxoError.isUtxoError(error)) {
504
+ console.error('Enhanced Bitcoin transaction preparation failed:', error.toJSON());
505
+ throw error;
365
506
  }
366
- });
367
- return { psbt, utxos, inputs };
507
+ throw UtxoError.fromUnknown(error, 'prepareTxEnhanced');
508
+ }
368
509
  });
369
510
  }
370
511
  /**
371
- * Prepare transfer.
512
+ * Prepare transfer with enhanced validation and performance.
513
+ * Now uses the enhanced logic internally while maintaining the same API.
372
514
  *
515
+ * @deprecated Use `prepareTxEnhanced` directly for explicit enhanced UTXO selection.
373
516
  * @param {TxParams&Address&FeeRate&boolean} params The transfer options.
374
517
  * @returns {PreparedTx} The raw unsigned transaction.
375
518
  */
376
519
  prepareTx(_a) {
377
520
  return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, }) {
378
- // Build the transaction using the provided parameters.
379
- const { psbt, utxos, inputs } = yield this.buildTx({
521
+ // Use the enhanced logic internally while maintaining the same API
522
+ return this.prepareTxEnhanced({
380
523
  sender,
381
524
  recipient,
382
525
  amount,
@@ -384,8 +527,6 @@ class Client extends Client$1 {
384
527
  memo,
385
528
  spendPendingUTXO,
386
529
  });
387
- // Return the raw unsigned transaction (PSBT) and associated UTXOs.
388
- return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
389
530
  });
390
531
  }
391
532
  }
@@ -471,7 +612,7 @@ class ClientKeystore extends Client {
471
612
  * Transfer BTC.
472
613
  *
473
614
  * @param {TxParams&FeeRate} params The transfer options including the fee rate.
474
- * @returns {Promise<TxHash|string>} A promise that resolves to the transaction hash or an error message.
615
+ * @returns {Promise<TxHash>} A promise that resolves to the transaction hash.
475
616
  * @throws {"memo too long"} Thrown if the memo is longer than 80 characters.
476
617
  */
477
618
  transfer(params) {
@@ -484,8 +625,10 @@ class ClientKeystore extends Client {
484
625
  const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
485
626
  // Get the Bitcoin keys
486
627
  const btcKeys = this.getBtcKeys(this.phrase, fromAddressIndex);
628
+ // Merge default preferences with caller-provided preferences
629
+ const mergedUtxoSelectionPreferences = Object.assign({ minimizeFee: true, avoidDust: true, minimizeInputs: false }, params.utxoSelectionPreferences);
487
630
  // Prepare the transaction
488
- const { rawUnsignedTx } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender: this.getAddress(fromAddressIndex), feeRate }));
631
+ const { rawUnsignedTx } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { sender: this.getAddress(fromAddressIndex), feeRate, utxoSelectionPreferences: mergedUtxoSelectionPreferences }));
489
632
  // Build the PSBT
490
633
  const psbt = Bitcoin.Psbt.fromBase64(rawUnsignedTx);
491
634
  // Sign all inputs
@@ -496,18 +639,43 @@ class ClientKeystore extends Client {
496
639
  psbt.finalizeAllInputs();
497
640
  // Extract the transaction hex
498
641
  const txHex = psbt.extractTransaction().toHex();
499
- // Extract the transaction hash
500
- const txHash = psbt.extractTransaction().getId();
501
- try {
502
- // Broadcast the transaction and return the transaction hash
503
- const txId = yield this.roundRobinBroadcastTx(txHex);
504
- return txId;
505
- }
506
- catch (_err) {
507
- // If broadcasting fails, return an error message with a link to the explorer
508
- const error = `Server error, please check explorer for tx confirmation ${this.explorerProviders[this.network].getExplorerTxUrl(txHash)}`;
509
- return error;
510
- }
642
+ // Broadcast the transaction and return the transaction hash
643
+ return yield this.roundRobinBroadcastTx(txHex);
644
+ });
645
+ }
646
+ /**
647
+ * Transfer the maximum amount of Bitcoin (sweep).
648
+ *
649
+ * Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
650
+ * @param {Object} params The transfer parameters.
651
+ * @param {string} params.recipient The recipient address.
652
+ * @param {string} [params.memo] Optional memo for the transaction.
653
+ * @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
654
+ * @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
655
+ * @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
656
+ * @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
657
+ */
658
+ transferMax(params) {
659
+ return __awaiter(this, void 0, void 0, function* () {
660
+ const feeRate = params.feeRate || (yield this.getFeeRates())[FeeOption.Fast];
661
+ checkFeeBounds(this.feeBounds, feeRate);
662
+ const fromAddressIndex = params.walletIndex || 0;
663
+ const sender = yield this.getAddressAsync(fromAddressIndex);
664
+ const { psbt, maxAmount, fee } = yield this.sendMax({
665
+ sender,
666
+ recipient: params.recipient,
667
+ memo: params.memo,
668
+ feeRate,
669
+ utxoSelectionPreferences: params.utxoSelectionPreferences,
670
+ });
671
+ const btcKeys = this.getBtcKeys(this.phrase, fromAddressIndex);
672
+ psbt.signAllInputs(this.addressFormat === AddressFormat.P2WPKH
673
+ ? btcKeys
674
+ : btcKeys.tweak(Bitcoin.crypto.taggedHash('TapTweak', toXOnly(btcKeys.publicKey))));
675
+ psbt.finalizeAllInputs();
676
+ const txHex = psbt.extractTransaction().toHex();
677
+ const hash = yield this.roundRobinBroadcastTx(txHex);
678
+ return { hash, maxAmount, fee };
511
679
  });
512
680
  }
513
681
  }
@@ -558,8 +726,16 @@ class ClientLedger extends Client {
558
726
  checkFeeBounds(this.feeBounds, feeRate);
559
727
  // Get sender address
560
728
  const sender = yield this.getAddressAsync(fromAddressIndex);
561
- // Prepare transaction
562
- const { rawUnsignedTx, inputs } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender, feeRate }));
729
+ // Create defaults and merge with caller-provided preferences
730
+ const defaults = {
731
+ minimizeFee: true,
732
+ avoidDust: true,
733
+ minimizeInputs: false,
734
+ };
735
+ const mergedUtxoSelectionPreferences = Object.assign(Object.assign({}, defaults), params.utxoSelectionPreferences);
736
+ // Prepare transaction using enhanced method with optimal UTXO selection
737
+ const { rawUnsignedTx, inputs } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { sender,
738
+ feeRate, utxoSelectionPreferences: mergedUtxoSelectionPreferences }));
563
739
  const psbt = Bitcoin.Psbt.fromBase64(rawUnsignedTx);
564
740
  // Prepare Ledger inputs
565
741
  const ledgerInputs = inputs.map(({ txHex, hash, index }) => {
package/lib/index.js CHANGED
@@ -7,7 +7,6 @@ var bip32 = require('@scure/bip32');
7
7
  var Bitcoin = require('bitcoinjs-lib');
8
8
  var ecpair = require('ecpair');
9
9
  var xchainUtxo = require('@xchainjs/xchain-utxo');
10
- var accumulative = require('coinselect/accumulative.js');
11
10
  var xchainUtil = require('@xchainjs/xchain-util');
12
11
  var xchainUtxoProviders = require('@xchainjs/xchain-utxo-providers');
13
12
  var AppBtc = require('@ledgerhq/hw-app-btc');
@@ -34,7 +33,6 @@ function _interopNamespace(e) {
34
33
 
35
34
  var ecc__namespace = /*#__PURE__*/_interopNamespace(ecc);
36
35
  var Bitcoin__namespace = /*#__PURE__*/_interopNamespace(Bitcoin);
37
- var accumulative__default = /*#__PURE__*/_interopDefault(accumulative);
38
36
  var AppBtc__default = /*#__PURE__*/_interopDefault(AppBtc);
39
37
 
40
38
  exports.AddressFormat = void 0;
@@ -187,7 +185,7 @@ const validateAddress = (address, network) => {
187
185
  Bitcoin__namespace.address.toOutputScript(address, btcNetwork(network)); // Try to convert the address to an output script using the specified network
188
186
  return true; // If successful, the address is valid
189
187
  }
190
- catch (_error) {
188
+ catch (_a) {
191
189
  return false; // If an error occurs, the address is invalid
192
190
  }
193
191
  };
@@ -312,98 +310,242 @@ class Client extends xchainUtxo.Client {
312
310
  return fee > MIN_TX_FEE ? fee : MIN_TX_FEE;
313
311
  }
314
312
  /**
315
- * Build a Bitcoin transaction.*
313
+ * Enhanced Bitcoin transaction builder with comprehensive validation and optimal UTXO selection
314
+ * @param params Transaction parameters
315
+ * @returns Enhanced transaction build result with PSBT, UTXOs, and inputs
316
+ */
317
+ buildTxEnhanced(_a) {
318
+ return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, utxoSelectionPreferences, }) {
319
+ try {
320
+ // Comprehensive input validation
321
+ this.validateTransactionInputs({
322
+ amount,
323
+ recipient,
324
+ memo,
325
+ sender,
326
+ feeRate,
327
+ });
328
+ // Get validated UTXOs
329
+ const confirmedOnly = !spendPendingUTXO;
330
+ const utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
331
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
332
+ const targetValue = amount.amount().toNumber();
333
+ const extraOutputs = 1 + (compiledMemo ? 1 : 0); // recipient + optional memo (change calculated separately)
334
+ // Enhanced UTXO selection
335
+ const selectionResult = this.selectUtxosForTransaction(utxos, targetValue, Math.ceil(feeRate), extraOutputs, utxoSelectionPreferences);
336
+ const psbt = new Bitcoin__namespace.Psbt({ network: btcNetwork(this.network) });
337
+ // Add inputs based on selection
338
+ if (this.addressFormat === exports.AddressFormat.P2WPKH) {
339
+ selectionResult.inputs.forEach((utxo) => {
340
+ if (!utxo.witnessUtxo) {
341
+ throw xchainUtxo.UtxoError.fromUnknown(new Error(`Missing witnessUtxo for UTXO ${utxo.hash}:${utxo.index}`), 'buildTxPsbt');
342
+ }
343
+ psbt.addInput({
344
+ hash: utxo.hash,
345
+ index: utxo.index,
346
+ witnessUtxo: utxo.witnessUtxo,
347
+ });
348
+ });
349
+ }
350
+ else {
351
+ const { pubkey, output } = Bitcoin__namespace.payments.p2tr({
352
+ address: sender,
353
+ });
354
+ selectionResult.inputs.forEach((utxo) => psbt.addInput({
355
+ hash: utxo.hash,
356
+ index: utxo.index,
357
+ witnessUtxo: { value: utxo.value, script: output },
358
+ tapInternalKey: pubkey,
359
+ }));
360
+ }
361
+ // Add recipient output
362
+ psbt.addOutput({
363
+ address: recipient,
364
+ value: targetValue,
365
+ });
366
+ // Add change output if needed
367
+ if (selectionResult.changeAmount > 0) {
368
+ psbt.addOutput({
369
+ address: sender,
370
+ value: selectionResult.changeAmount,
371
+ });
372
+ }
373
+ // Add memo output if present
374
+ if (compiledMemo) {
375
+ psbt.addOutput({ script: compiledMemo, value: 0 });
376
+ }
377
+ return { psbt, utxos, inputs: selectionResult.inputs };
378
+ }
379
+ catch (error) {
380
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
381
+ throw error;
382
+ }
383
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'buildTxEnhanced');
384
+ }
385
+ });
386
+ }
387
+ /**
388
+ * Build a Bitcoin transaction with enhanced validation and performance.
389
+ * Now uses the enhanced logic internally while maintaining the same API.
316
390
  * @param param0
317
- * @deprecated
318
391
  */
319
392
  buildTx(_a) {
320
393
  return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, }) {
321
- // Check memo length
322
- if (memo && memo.length > 80) {
323
- throw new Error('memo too long, must not be longer than 80 chars.');
324
- }
325
- // This section of the code is responsible for preparing a transaction by building a Bitcoin PSBT (Partially Signed Bitcoin Transaction).
326
- if (!this.validateAddress(recipient))
327
- throw new Error('Invalid address');
328
- // Determine whether to only use confirmed UTXOs or include pending UTXOs based on the spendPendingUTXO flag.
329
- const confirmedOnly = !spendPendingUTXO;
330
- // Scan UTXOs associated with the sender's address.
331
- const utxos = yield this.scanUTXOs(sender, confirmedOnly);
332
- // Throw an error if there are no available UTXOs to cover the transaction.
333
- if (utxos.length === 0)
334
- throw new Error('Insufficient Balance for transaction');
335
- // Round up the fee rate to the nearest integer.
336
- const feeRateWhole = Math.ceil(feeRate);
337
- // Compile the memo into a Buffer if provided.
338
- const compiledMemo = memo ? this.compileMemo(memo) : null;
339
- // Initialize an array to store the target outputs of the transaction.
340
- const targetOutputs = [];
341
- // 1. Add the recipient address and amount to the target outputs.
342
- targetOutputs.push({
343
- address: recipient,
344
- value: amount.amount().toNumber(),
394
+ // Use the enhanced logic internally while maintaining the same API
395
+ return this.buildTxEnhanced({
396
+ amount,
397
+ recipient,
398
+ memo,
399
+ feeRate,
400
+ sender,
401
+ spendPendingUTXO,
345
402
  });
346
- // 2. Add the compiled memo to the target outputs if it exists.
347
- if (compiledMemo) {
348
- targetOutputs.push({ script: compiledMemo, value: 0 });
403
+ });
404
+ }
405
+ /**
406
+ * Send maximum possible amount (sweep) with optimal fee calculation
407
+ * @param params Send max parameters
408
+ * @returns Transaction details with maximum sendable amount
409
+ */
410
+ sendMax(_a) {
411
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, }) {
412
+ try {
413
+ // Basic validation (skip amount validation since we're calculating max)
414
+ if (!(recipient === null || recipient === void 0 ? void 0 : recipient.trim())) {
415
+ throw xchainUtxo.UtxoError.invalidAddress(recipient, this.network);
416
+ }
417
+ if (!this.validateAddress(recipient)) {
418
+ throw xchainUtxo.UtxoError.invalidAddress(recipient, this.network);
419
+ }
420
+ if (!this.validateAddress(sender)) {
421
+ throw xchainUtxo.UtxoError.invalidAddress(sender, this.network);
422
+ }
423
+ // Memo validation is handled by validateTransactionInputs
424
+ // Get validated UTXOs
425
+ const confirmedOnly = !spendPendingUTXO;
426
+ const utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
427
+ // Calculate maximum sendable amount
428
+ const maxCalc = this.calculateMaxSendableAmount(utxos, Math.ceil(feeRate), !!memo, utxoSelectionPreferences);
429
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
430
+ const psbt = new Bitcoin__namespace.Psbt({ network: btcNetwork(this.network) });
431
+ // Add inputs
432
+ if (this.addressFormat === exports.AddressFormat.P2WPKH) {
433
+ maxCalc.inputs.forEach((utxo) => psbt.addInput({
434
+ hash: utxo.hash,
435
+ index: utxo.index,
436
+ witnessUtxo: utxo.witnessUtxo,
437
+ }));
438
+ }
439
+ else {
440
+ const { pubkey, output } = Bitcoin__namespace.payments.p2tr({
441
+ address: sender,
442
+ });
443
+ maxCalc.inputs.forEach((utxo) => psbt.addInput({
444
+ hash: utxo.hash,
445
+ index: utxo.index,
446
+ witnessUtxo: { value: utxo.value, script: output },
447
+ tapInternalKey: pubkey,
448
+ }));
449
+ }
450
+ // Add recipient output (max amount - no change)
451
+ psbt.addOutput({
452
+ address: recipient,
453
+ value: maxCalc.amount,
454
+ });
455
+ // Add memo output if present
456
+ if (compiledMemo) {
457
+ psbt.addOutput({ script: compiledMemo, value: 0 });
458
+ }
459
+ return {
460
+ psbt,
461
+ utxos,
462
+ inputs: maxCalc.inputs,
463
+ maxAmount: maxCalc.amount,
464
+ fee: maxCalc.fee,
465
+ };
349
466
  }
350
- // Use the coinselect library to determine the inputs and outputs for the transaction.
351
- const { inputs, outputs } = accumulative__default.default(utxos, targetOutputs, feeRateWhole);
352
- // If no suitable inputs or outputs are found, throw an error indicating insufficient balance.
353
- if (!inputs || !outputs)
354
- throw new Error('Insufficient Balance for transaction');
355
- // Initialize a new Bitcoin PSBT object.
356
- const psbt = new Bitcoin__namespace.Psbt({ network: btcNetwork(this.network) }); // Network-specific
357
- if (this.addressFormat === exports.AddressFormat.P2WPKH) {
358
- // Add inputs to the PSBT from the accumulated inputs.
359
- inputs.forEach((utxo) => psbt.addInput({
360
- hash: utxo.hash,
361
- index: utxo.index,
362
- witnessUtxo: utxo.witnessUtxo,
363
- }));
467
+ catch (error) {
468
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
469
+ throw error;
470
+ }
471
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'sendMax');
364
472
  }
365
- else {
366
- const { pubkey, output } = Bitcoin__namespace.payments.p2tr({
367
- address: sender,
473
+ });
474
+ }
475
+ /**
476
+ * Prepare maximum amount transfer (sweep transaction)
477
+ * @param params Send max parameters
478
+ * @returns Prepared transaction with maximum sendable amount
479
+ */
480
+ prepareMaxTx(_a) {
481
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, }) {
482
+ try {
483
+ const { psbt, utxos, inputs, maxAmount, fee } = yield this.sendMax({
484
+ sender,
485
+ recipient,
486
+ memo,
487
+ feeRate,
488
+ spendPendingUTXO,
489
+ utxoSelectionPreferences,
368
490
  });
369
- inputs.forEach((utxo) => psbt.addInput({
370
- hash: utxo.hash,
371
- index: utxo.index,
372
- witnessUtxo: { value: utxo.value, script: output },
373
- tapInternalKey: pubkey,
374
- }));
491
+ return {
492
+ rawUnsignedTx: psbt.toBase64(),
493
+ utxos,
494
+ inputs,
495
+ maxAmount,
496
+ fee,
497
+ };
375
498
  }
376
- // Add outputs to the PSBT from the accumulated outputs.
377
- outputs.forEach((output) => {
378
- // If the output address is not specified, it's considered a change address and set to the sender's address.
379
- if (!output.address) {
380
- //an empty address means this is the change address
381
- output.address = sender;
382
- }
383
- // Add the output to the PSBT.
384
- if (!output.script) {
385
- psbt.addOutput(output);
499
+ catch (error) {
500
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
501
+ console.error('Bitcoin max transaction preparation failed:', error.toJSON());
502
+ throw error;
386
503
  }
387
- else {
388
- // If the output is a memo, add it to the PSBT to avoid dust error.
389
- if (compiledMemo) {
390
- psbt.addOutput({ script: compiledMemo, value: 0 });
391
- }
504
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'prepareMaxTx');
505
+ }
506
+ });
507
+ }
508
+ /**
509
+ * Enhanced prepare transfer with comprehensive validation and optimal UTXO selection.
510
+ *
511
+ * @param params The transfer options with enhanced UTXO selection preferences.
512
+ * @returns The raw unsigned transaction with enhanced error handling.
513
+ */
514
+ prepareTxEnhanced(_a) {
515
+ return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, utxoSelectionPreferences, }) {
516
+ try {
517
+ const { psbt, utxos, inputs } = yield this.buildTxEnhanced({
518
+ sender,
519
+ recipient,
520
+ amount,
521
+ feeRate,
522
+ memo,
523
+ spendPendingUTXO,
524
+ utxoSelectionPreferences,
525
+ });
526
+ return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
527
+ }
528
+ catch (error) {
529
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
530
+ console.error('Enhanced Bitcoin transaction preparation failed:', error.toJSON());
531
+ throw error;
392
532
  }
393
- });
394
- return { psbt, utxos, inputs };
533
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'prepareTxEnhanced');
534
+ }
395
535
  });
396
536
  }
397
537
  /**
398
- * Prepare transfer.
538
+ * Prepare transfer with enhanced validation and performance.
539
+ * Now uses the enhanced logic internally while maintaining the same API.
399
540
  *
541
+ * @deprecated Use `prepareTxEnhanced` directly for explicit enhanced UTXO selection.
400
542
  * @param {TxParams&Address&FeeRate&boolean} params The transfer options.
401
543
  * @returns {PreparedTx} The raw unsigned transaction.
402
544
  */
403
545
  prepareTx(_a) {
404
546
  return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, }) {
405
- // Build the transaction using the provided parameters.
406
- const { psbt, utxos, inputs } = yield this.buildTx({
547
+ // Use the enhanced logic internally while maintaining the same API
548
+ return this.prepareTxEnhanced({
407
549
  sender,
408
550
  recipient,
409
551
  amount,
@@ -411,8 +553,6 @@ class Client extends xchainUtxo.Client {
411
553
  memo,
412
554
  spendPendingUTXO,
413
555
  });
414
- // Return the raw unsigned transaction (PSBT) and associated UTXOs.
415
- return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
416
556
  });
417
557
  }
418
558
  }
@@ -498,7 +638,7 @@ class ClientKeystore extends Client {
498
638
  * Transfer BTC.
499
639
  *
500
640
  * @param {TxParams&FeeRate} params The transfer options including the fee rate.
501
- * @returns {Promise<TxHash|string>} A promise that resolves to the transaction hash or an error message.
641
+ * @returns {Promise<TxHash>} A promise that resolves to the transaction hash.
502
642
  * @throws {"memo too long"} Thrown if the memo is longer than 80 characters.
503
643
  */
504
644
  transfer(params) {
@@ -511,8 +651,10 @@ class ClientKeystore extends Client {
511
651
  const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
512
652
  // Get the Bitcoin keys
513
653
  const btcKeys = this.getBtcKeys(this.phrase, fromAddressIndex);
654
+ // Merge default preferences with caller-provided preferences
655
+ const mergedUtxoSelectionPreferences = Object.assign({ minimizeFee: true, avoidDust: true, minimizeInputs: false }, params.utxoSelectionPreferences);
514
656
  // Prepare the transaction
515
- const { rawUnsignedTx } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender: this.getAddress(fromAddressIndex), feeRate }));
657
+ const { rawUnsignedTx } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { sender: this.getAddress(fromAddressIndex), feeRate, utxoSelectionPreferences: mergedUtxoSelectionPreferences }));
516
658
  // Build the PSBT
517
659
  const psbt = Bitcoin__namespace.Psbt.fromBase64(rawUnsignedTx);
518
660
  // Sign all inputs
@@ -523,18 +665,43 @@ class ClientKeystore extends Client {
523
665
  psbt.finalizeAllInputs();
524
666
  // Extract the transaction hex
525
667
  const txHex = psbt.extractTransaction().toHex();
526
- // Extract the transaction hash
527
- const txHash = psbt.extractTransaction().getId();
528
- try {
529
- // Broadcast the transaction and return the transaction hash
530
- const txId = yield this.roundRobinBroadcastTx(txHex);
531
- return txId;
532
- }
533
- catch (_err) {
534
- // If broadcasting fails, return an error message with a link to the explorer
535
- const error = `Server error, please check explorer for tx confirmation ${this.explorerProviders[this.network].getExplorerTxUrl(txHash)}`;
536
- return error;
537
- }
668
+ // Broadcast the transaction and return the transaction hash
669
+ return yield this.roundRobinBroadcastTx(txHex);
670
+ });
671
+ }
672
+ /**
673
+ * Transfer the maximum amount of Bitcoin (sweep).
674
+ *
675
+ * Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
676
+ * @param {Object} params The transfer parameters.
677
+ * @param {string} params.recipient The recipient address.
678
+ * @param {string} [params.memo] Optional memo for the transaction.
679
+ * @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
680
+ * @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
681
+ * @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
682
+ * @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
683
+ */
684
+ transferMax(params) {
685
+ return __awaiter(this, void 0, void 0, function* () {
686
+ const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
687
+ xchainClient.checkFeeBounds(this.feeBounds, feeRate);
688
+ const fromAddressIndex = params.walletIndex || 0;
689
+ const sender = yield this.getAddressAsync(fromAddressIndex);
690
+ const { psbt, maxAmount, fee } = yield this.sendMax({
691
+ sender,
692
+ recipient: params.recipient,
693
+ memo: params.memo,
694
+ feeRate,
695
+ utxoSelectionPreferences: params.utxoSelectionPreferences,
696
+ });
697
+ const btcKeys = this.getBtcKeys(this.phrase, fromAddressIndex);
698
+ psbt.signAllInputs(this.addressFormat === exports.AddressFormat.P2WPKH
699
+ ? btcKeys
700
+ : btcKeys.tweak(Bitcoin__namespace.crypto.taggedHash('TapTweak', toXOnly(btcKeys.publicKey))));
701
+ psbt.finalizeAllInputs();
702
+ const txHex = psbt.extractTransaction().toHex();
703
+ const hash = yield this.roundRobinBroadcastTx(txHex);
704
+ return { hash, maxAmount, fee };
538
705
  });
539
706
  }
540
707
  }
@@ -585,8 +752,16 @@ class ClientLedger extends Client {
585
752
  xchainClient.checkFeeBounds(this.feeBounds, feeRate);
586
753
  // Get sender address
587
754
  const sender = yield this.getAddressAsync(fromAddressIndex);
588
- // Prepare transaction
589
- const { rawUnsignedTx, inputs } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender, feeRate }));
755
+ // Create defaults and merge with caller-provided preferences
756
+ const defaults = {
757
+ minimizeFee: true,
758
+ avoidDust: true,
759
+ minimizeInputs: false,
760
+ };
761
+ const mergedUtxoSelectionPreferences = Object.assign(Object.assign({}, defaults), params.utxoSelectionPreferences);
762
+ // Prepare transaction using enhanced method with optimal UTXO selection
763
+ const { rawUnsignedTx, inputs } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { sender,
764
+ feeRate, utxoSelectionPreferences: mergedUtxoSelectionPreferences }));
590
765
  const psbt = Bitcoin__namespace.Psbt.fromBase64(rawUnsignedTx);
591
766
  // Prepare Ledger inputs
592
767
  const ledgerInputs = inputs.map(({ txHex, hash, index }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xchainjs/xchain-bitcoin",
3
- "version": "2.0.9",
3
+ "version": "2.1.0",
4
4
  "description": "Custom Bitcoin client and utilities used by XChainJS clients",
5
5
  "keywords": [
6
6
  "XChain",
@@ -36,18 +36,18 @@
36
36
  "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3",
37
37
  "@ledgerhq/hw-app-btc": "^10.9.0",
38
38
  "@scure/bip32": "^1.7.0",
39
- "@xchainjs/xchain-client": "2.0.9",
39
+ "@xchainjs/xchain-client": "2.0.10",
40
40
  "@xchainjs/xchain-crypto": "1.0.6",
41
41
  "@xchainjs/xchain-util": "2.0.5",
42
- "@xchainjs/xchain-utxo": "2.0.9",
43
- "@xchainjs/xchain-utxo-providers": "2.0.9",
42
+ "@xchainjs/xchain-utxo": "2.1.0",
43
+ "@xchainjs/xchain-utxo-providers": "2.0.10",
44
44
  "bitcoinjs-lib": "^6.1.7",
45
45
  "coinselect": "3.1.12",
46
46
  "ecpair": "2.1.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@ledgerhq/hw-transport-node-hid": "^6.28.6",
50
- "axios": "1.12.1",
50
+ "axios": "1.13.5",
51
51
  "axios-mock-adapter": "^2.1.0"
52
52
  },
53
53
  "publishConfig": {