@xchainjs/xchain-bitcoin 2.0.10 → 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 +68 -4
- package/lib/clientKeystore.d.ts +26 -2
- package/lib/clientLedger.d.ts +2 -1
- package/lib/index.esm.js +269 -93
- package/lib/index.js +268 -93
- package/package.json +2 -2
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
|
-
*
|
|
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
|
-
*
|
|
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
|
*/
|
package/lib/clientKeystore.d.ts
CHANGED
|
@@ -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
|
|
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 };
|
package/lib/clientLedger.d.ts
CHANGED
|
@@ -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 (
|
|
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
|
-
*
|
|
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
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
throw
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
465
|
+
return {
|
|
466
|
+
rawUnsignedTx: psbt.toBase64(),
|
|
467
|
+
utxos,
|
|
468
|
+
inputs,
|
|
469
|
+
maxAmount,
|
|
470
|
+
fee,
|
|
471
|
+
};
|
|
348
472
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
379
|
-
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
//
|
|
562
|
-
const
|
|
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 (
|
|
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
|
-
*
|
|
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
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
throw
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
491
|
+
return {
|
|
492
|
+
rawUnsignedTx: psbt.toBase64(),
|
|
493
|
+
utxos,
|
|
494
|
+
inputs,
|
|
495
|
+
maxAmount,
|
|
496
|
+
fee,
|
|
497
|
+
};
|
|
375
498
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
406
|
-
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
//
|
|
589
|
-
const
|
|
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
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Custom Bitcoin client and utilities used by XChainJS clients",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"XChain",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
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
|
|
42
|
+
"@xchainjs/xchain-utxo": "2.1.0",
|
|
43
43
|
"@xchainjs/xchain-utxo-providers": "2.0.10",
|
|
44
44
|
"bitcoinjs-lib": "^6.1.7",
|
|
45
45
|
"coinselect": "3.1.12",
|