@xchainjs/xchain-doge 2.0.10 → 2.2.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 Dogecoin from 'bitcoinjs-lib';
5
5
  import { LedgerTxInfo, LedgerTxInfoParams } from './types/ledger';
6
6
  /**
@@ -62,6 +62,7 @@ declare abstract class Client extends UTXOClient {
62
62
  * Asynchronously prepares a transaction for transfer.
63
63
  *
64
64
  * Builds a transaction (PSBT) with the specified transfer options.
65
+ * @deprecated Use `prepareTxEnhanced` instead for better UTXO selection and error handling.
65
66
  * @param {TxParams & { sender: Address; feeRate: FeeRate; spendPendingUTXO?: boolean }} params The transfer options including sender address, fee rate, and optional flag for spending pending UTXOs.
66
67
  * @returns {Promise<PreparedTx>} A promise that resolves to the raw unsigned transaction (PSBT).
67
68
  */
@@ -89,5 +90,63 @@ declare abstract class Client extends UTXOClient {
89
90
  * @returns {number} The calculated transaction fee.
90
91
  */
91
92
  protected getFeeFromUtxos(inputs: UTXO[], feeRate: FeeRate, data?: Buffer | null): number;
93
+ /**
94
+ * Build transaction with enhanced UTXO selection
95
+ * Note: Doge uses legacy P2PKH addresses (nonWitnessUtxo)
96
+ */
97
+ buildTxEnhanced({ amount, recipient, memo, feeRate, sender, spendPendingUTXO, utxoSelectionPreferences, selectedUtxos, }: TxParams & {
98
+ feeRate: FeeRate;
99
+ sender: Address;
100
+ spendPendingUTXO?: boolean;
101
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
102
+ selectedUtxos?: UTXO[];
103
+ }): Promise<{
104
+ psbt: Dogecoin.Psbt;
105
+ utxos: UTXO[];
106
+ inputs: UTXO[];
107
+ }>;
108
+ /**
109
+ * Prepare transaction with enhanced UTXO selection
110
+ */
111
+ prepareTxEnhanced({ sender, memo, amount, recipient, spendPendingUTXO, feeRate, utxoSelectionPreferences, selectedUtxos, }: TxParams & {
112
+ sender: Address;
113
+ feeRate: FeeRate;
114
+ spendPendingUTXO?: boolean;
115
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
116
+ selectedUtxos?: UTXO[];
117
+ }): Promise<PreparedTx>;
118
+ /**
119
+ * Send maximum possible amount (sweep)
120
+ */
121
+ sendMax({ sender, recipient, memo, feeRate, spendPendingUTXO, utxoSelectionPreferences, selectedUtxos, }: {
122
+ sender: Address;
123
+ recipient: Address;
124
+ memo?: string;
125
+ feeRate: FeeRate;
126
+ spendPendingUTXO?: boolean;
127
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
128
+ selectedUtxos?: UTXO[];
129
+ }): Promise<{
130
+ psbt: Dogecoin.Psbt;
131
+ utxos: UTXO[];
132
+ inputs: UTXO[];
133
+ maxAmount: number;
134
+ fee: number;
135
+ }>;
136
+ /**
137
+ * Prepare max send transaction
138
+ */
139
+ prepareMaxTx({ sender, recipient, memo, feeRate, spendPendingUTXO, utxoSelectionPreferences, selectedUtxos, }: {
140
+ sender: Address;
141
+ recipient: Address;
142
+ memo?: string;
143
+ feeRate: FeeRate;
144
+ spendPendingUTXO?: boolean;
145
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
146
+ selectedUtxos?: UTXO[];
147
+ }): Promise<PreparedTx & {
148
+ maxAmount: number;
149
+ fee: number;
150
+ }>;
92
151
  }
93
152
  export { Client };
@@ -1,6 +1,6 @@
1
1
  import { FeeRate, TxHash } from '@xchainjs/xchain-client';
2
2
  import { Address } from '@xchainjs/xchain-util';
3
- import { TxParams } from '@xchainjs/xchain-utxo';
3
+ import { TxParams, UTXO, UtxoSelectionPreferences } from '@xchainjs/xchain-utxo';
4
4
  import { Client } from './client';
5
5
  /**
6
6
  * Custom Doge client extended to support keystore functionality
@@ -44,11 +44,38 @@ declare class ClientKeystore extends Client {
44
44
  * Asynchronously transfers Dogecoin.
45
45
  *
46
46
  * Builds, signs, and broadcasts a Dogecoin transaction with the specified parameters.
47
- * @param {TxParams & { feeRate?: FeeRate }} params The transfer parameters including transaction details and optional fee rate.
48
- * @returns {TxHash} A promise that resolves to the transaction hash once the transfer is completed.
47
+ *
48
+ * @param {Object} params The transfer parameters.
49
+ * @returns {Promise<TxHash>} The transaction hash.
49
50
  */
50
51
  transfer(params: TxParams & {
51
52
  feeRate?: FeeRate;
53
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
54
+ selectedUtxos?: UTXO[];
52
55
  }): Promise<TxHash>;
56
+ /**
57
+ * Transfer the maximum amount of Dogecoin (sweep).
58
+ *
59
+ * Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
60
+ * @param {Object} params The transfer parameters.
61
+ * @param {string} params.recipient The recipient address.
62
+ * @param {string} [params.memo] Optional memo for the transaction.
63
+ * @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
64
+ * @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
65
+ * @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
66
+ * @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
67
+ */
68
+ transferMax(params: {
69
+ recipient: Address;
70
+ memo?: string;
71
+ feeRate?: FeeRate;
72
+ walletIndex?: number;
73
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
74
+ selectedUtxos?: UTXO[];
75
+ }): Promise<{
76
+ hash: TxHash;
77
+ maxAmount: number;
78
+ fee: number;
79
+ }>;
53
80
  }
54
81
  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, UTXO, UtxoClientParams, UtxoSelectionPreferences } from '@xchainjs/xchain-utxo';
5
5
  import { Client } from './client';
6
6
  /**
7
7
  * Custom Ledger Bitcoin client
@@ -18,5 +18,17 @@ declare class ClientLedger extends Client {
18
18
  transfer(params: TxParams & {
19
19
  feeRate?: FeeRate;
20
20
  }): Promise<TxHash>;
21
+ transferMax(params: {
22
+ recipient: Address;
23
+ memo?: string;
24
+ feeRate?: FeeRate;
25
+ walletIndex?: number;
26
+ utxoSelectionPreferences?: UtxoSelectionPreferences;
27
+ selectedUtxos?: UTXO[];
28
+ }): Promise<{
29
+ hash: TxHash;
30
+ maxAmount: number;
31
+ fee: number;
32
+ }>;
21
33
  }
22
34
  export { ClientLedger };
package/lib/index.esm.js CHANGED
@@ -4,7 +4,7 @@ import { getSeed } from '@xchainjs/xchain-crypto';
4
4
  import * as Dogecoin from 'bitcoinjs-lib';
5
5
  import { ECPairFactory } from 'ecpair';
6
6
  import { HDKey } from '@scure/bip32';
7
- import { toBitcoinJS, Client as Client$1 } from '@xchainjs/xchain-utxo';
7
+ import { toBitcoinJS, Client as Client$1, UtxoTransactionValidator, UtxoError } from '@xchainjs/xchain-utxo';
8
8
  import accumulative from 'coinselect/accumulative.js';
9
9
  import { AssetType } from '@xchainjs/xchain-util';
10
10
  import { SochainProvider, SochainNetwork, BlockcypherProvider, BlockcypherNetwork, BitgoProvider } from '@xchainjs/xchain-utxo-providers';
@@ -340,6 +340,7 @@ class Client extends Client$1 {
340
340
  * Asynchronously prepares a transaction for transfer.
341
341
  *
342
342
  * Builds a transaction (PSBT) with the specified transfer options.
343
+ * @deprecated Use `prepareTxEnhanced` instead for better UTXO selection and error handling.
343
344
  * @param {TxParams & { sender: Address; feeRate: FeeRate; spendPendingUTXO?: boolean }} params The transfer options including sender address, fee rate, and optional flag for spending pending UTXOs.
344
345
  * @returns {Promise<PreparedTx>} A promise that resolves to the raw unsigned transaction (PSBT).
345
346
  */
@@ -398,6 +399,183 @@ class Client extends Client$1 {
398
399
  // Ensure the fee is not less than the minimum transaction fee
399
400
  return fee > MIN_TX_FEE ? fee : MIN_TX_FEE;
400
401
  }
402
+ // ==================== Enhanced Transaction Methods ====================
403
+ /**
404
+ * Build transaction with enhanced UTXO selection
405
+ * Note: Doge uses legacy P2PKH addresses (nonWitnessUtxo)
406
+ */
407
+ buildTxEnhanced(_a) {
408
+ return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
409
+ try {
410
+ this.validateTransactionInputs({
411
+ amount,
412
+ recipient,
413
+ memo,
414
+ sender,
415
+ feeRate,
416
+ });
417
+ let utxos;
418
+ if (selectedUtxos && selectedUtxos.length > 0) {
419
+ UtxoTransactionValidator.validateUtxoSet(selectedUtxos);
420
+ utxos = selectedUtxos;
421
+ }
422
+ else {
423
+ const confirmedOnly = !spendPendingUTXO;
424
+ utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
425
+ }
426
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
427
+ const targetValue = amount.amount().toNumber();
428
+ const extraOutputs = 1 + (compiledMemo ? 1 : 0);
429
+ const selectionResult = this.selectUtxosForTransaction(utxos, targetValue, Math.ceil(feeRate), extraOutputs, utxoSelectionPreferences);
430
+ const psbt = new Dogecoin.Psbt({ network: dogeNetwork(this.network) });
431
+ psbt.setMaximumFeeRate(7500000);
432
+ // Add inputs - Doge uses nonWitnessUtxo (legacy P2PKH)
433
+ for (const utxo of selectionResult.inputs) {
434
+ psbt.addInput({
435
+ hash: utxo.hash,
436
+ index: utxo.index,
437
+ nonWitnessUtxo: Buffer.from(utxo.txHex || '', 'hex'),
438
+ });
439
+ }
440
+ // Add recipient output
441
+ psbt.addOutput({
442
+ address: recipient,
443
+ value: targetValue,
444
+ });
445
+ // Add change output if needed
446
+ if (selectionResult.changeAmount > 0) {
447
+ psbt.addOutput({
448
+ address: sender,
449
+ value: selectionResult.changeAmount,
450
+ });
451
+ }
452
+ // Add memo output if present
453
+ if (compiledMemo) {
454
+ psbt.addOutput({ script: compiledMemo, value: 0 });
455
+ }
456
+ return { psbt, utxos, inputs: selectionResult.inputs };
457
+ }
458
+ catch (error) {
459
+ if (UtxoError.isUtxoError(error)) {
460
+ throw error;
461
+ }
462
+ throw UtxoError.fromUnknown(error, 'buildTxEnhanced');
463
+ }
464
+ });
465
+ }
466
+ /**
467
+ * Prepare transaction with enhanced UTXO selection
468
+ */
469
+ prepareTxEnhanced(_a) {
470
+ return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, utxoSelectionPreferences, selectedUtxos, }) {
471
+ try {
472
+ const { psbt, utxos, inputs } = yield this.buildTxEnhanced({
473
+ sender,
474
+ recipient,
475
+ amount,
476
+ feeRate,
477
+ memo,
478
+ spendPendingUTXO,
479
+ utxoSelectionPreferences,
480
+ selectedUtxos,
481
+ });
482
+ return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
483
+ }
484
+ catch (error) {
485
+ if (UtxoError.isUtxoError(error)) {
486
+ throw error;
487
+ }
488
+ throw UtxoError.fromUnknown(error, 'prepareTxEnhanced');
489
+ }
490
+ });
491
+ }
492
+ /**
493
+ * Send maximum possible amount (sweep)
494
+ */
495
+ sendMax(_a) {
496
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
497
+ try {
498
+ if (!this.validateAddress(recipient)) {
499
+ throw UtxoError.invalidAddress(recipient, this.network);
500
+ }
501
+ if (!this.validateAddress(sender)) {
502
+ throw UtxoError.invalidAddress(sender, this.network);
503
+ }
504
+ let utxos;
505
+ if (selectedUtxos && selectedUtxos.length > 0) {
506
+ UtxoTransactionValidator.validateUtxoSet(selectedUtxos);
507
+ utxos = selectedUtxos;
508
+ }
509
+ else {
510
+ const confirmedOnly = !spendPendingUTXO;
511
+ utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
512
+ }
513
+ const maxCalc = this.calculateMaxSendableAmount(utxos, Math.ceil(feeRate), !!memo, utxoSelectionPreferences);
514
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
515
+ const psbt = new Dogecoin.Psbt({ network: dogeNetwork(this.network) });
516
+ psbt.setMaximumFeeRate(7500000);
517
+ // Add inputs - Doge uses nonWitnessUtxo
518
+ for (const utxo of maxCalc.inputs) {
519
+ psbt.addInput({
520
+ hash: utxo.hash,
521
+ index: utxo.index,
522
+ nonWitnessUtxo: Buffer.from(utxo.txHex || '', 'hex'),
523
+ });
524
+ }
525
+ psbt.addOutput({
526
+ address: recipient,
527
+ value: maxCalc.amount,
528
+ });
529
+ if (compiledMemo) {
530
+ psbt.addOutput({ script: compiledMemo, value: 0 });
531
+ }
532
+ return {
533
+ psbt,
534
+ utxos,
535
+ inputs: maxCalc.inputs,
536
+ maxAmount: maxCalc.amount,
537
+ fee: maxCalc.fee,
538
+ };
539
+ }
540
+ catch (error) {
541
+ if (UtxoError.isUtxoError(error)) {
542
+ throw error;
543
+ }
544
+ throw UtxoError.fromUnknown(error, 'sendMax');
545
+ }
546
+ });
547
+ }
548
+ /**
549
+ * Prepare max send transaction
550
+ */
551
+ prepareMaxTx(_a) {
552
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
553
+ try {
554
+ const { psbt, utxos, inputs, maxAmount, fee } = yield this.sendMax({
555
+ sender,
556
+ recipient,
557
+ memo,
558
+ feeRate,
559
+ spendPendingUTXO,
560
+ utxoSelectionPreferences,
561
+ selectedUtxos,
562
+ });
563
+ return {
564
+ rawUnsignedTx: psbt.toBase64(),
565
+ utxos,
566
+ inputs,
567
+ maxAmount,
568
+ fee,
569
+ };
570
+ }
571
+ catch (error) {
572
+ if (UtxoError.isUtxoError(error)) {
573
+ throw error;
574
+ }
575
+ throw UtxoError.fromUnknown(error, 'prepareMaxTx');
576
+ }
577
+ });
578
+ }
401
579
  }
402
580
 
403
581
  const ECPair = ECPairFactory(ecc);
@@ -474,33 +652,61 @@ class ClientKeystore extends Client {
474
652
  * Asynchronously transfers Dogecoin.
475
653
  *
476
654
  * Builds, signs, and broadcasts a Dogecoin transaction with the specified parameters.
477
- * @param {TxParams & { feeRate?: FeeRate }} params The transfer parameters including transaction details and optional fee rate.
478
- * @returns {TxHash} A promise that resolves to the transaction hash once the transfer is completed.
655
+ *
656
+ * @param {Object} params The transfer parameters.
657
+ * @returns {Promise<TxHash>} The transaction hash.
479
658
  */
480
659
  transfer(params) {
481
660
  return __awaiter(this, void 0, void 0, function* () {
482
- // Determine the fee rate for the transaction, using provided fee rate or fetching it from the network
483
661
  const feeRate = params.feeRate || (yield this.getFeeRates())[FeeOption.Fast];
484
- // Check if the fee rate is within the specified fee bounds
485
662
  checkFeeBounds(this.feeBounds, feeRate);
486
- // Get the index of the sender's address or use the default index (0)
487
663
  const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
488
- // Prepare the transaction by building it with the specified parameters
489
- const { rawUnsignedTx } = yield this.prepareTx(Object.assign(Object.assign({}, params), { feeRate, sender: yield this.getAddressAsync(fromAddressIndex) }));
490
- // Get the Dogecoin keys for signing the transaction
664
+ const sender = yield this.getAddressAsync(fromAddressIndex);
491
665
  const dogeKeys = this.getDogeKeys(this.phrase, fromAddressIndex);
492
- // Create a Partially Signed Bitcoin Transaction (PSBT) from the raw unsigned transaction
666
+ const mergedPreferences = Object.assign({ minimizeFee: true, avoidDust: true, minimizeInputs: false }, params.utxoSelectionPreferences);
667
+ const { rawUnsignedTx } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { feeRate,
668
+ sender, utxoSelectionPreferences: mergedPreferences, selectedUtxos: params.selectedUtxos }));
493
669
  const psbt = Dogecoin.Psbt.fromBase64(rawUnsignedTx, { maximumFeeRate: 7500000 });
494
- // Sign all inputs of the transaction with the Dogecoin keys
495
670
  psbt.signAllInputs(dogeKeys);
496
- // Finalize all inputs of the transaction
497
671
  psbt.finalizeAllInputs();
498
- // Extract the signed transaction and format it to hexadecimal
499
672
  const txHex = psbt.extractTransaction().toHex();
500
- // Broadcast the signed transaction to the Dogecoin network and return the transaction hash
501
673
  return yield this.roundRobinBroadcastTx(txHex);
502
674
  });
503
675
  }
676
+ /**
677
+ * Transfer the maximum amount of Dogecoin (sweep).
678
+ *
679
+ * Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
680
+ * @param {Object} params The transfer parameters.
681
+ * @param {string} params.recipient The recipient address.
682
+ * @param {string} [params.memo] Optional memo for the transaction.
683
+ * @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
684
+ * @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
685
+ * @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
686
+ * @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
687
+ */
688
+ transferMax(params) {
689
+ return __awaiter(this, void 0, void 0, function* () {
690
+ const feeRate = params.feeRate || (yield this.getFeeRates())[FeeOption.Fast];
691
+ checkFeeBounds(this.feeBounds, feeRate);
692
+ const fromAddressIndex = params.walletIndex || 0;
693
+ const sender = yield this.getAddressAsync(fromAddressIndex);
694
+ const { psbt, maxAmount, fee } = yield this.sendMax({
695
+ sender,
696
+ recipient: params.recipient,
697
+ memo: params.memo,
698
+ feeRate,
699
+ utxoSelectionPreferences: params.utxoSelectionPreferences,
700
+ selectedUtxos: params.selectedUtxos,
701
+ });
702
+ const dogeKeys = this.getDogeKeys(this.phrase, fromAddressIndex);
703
+ psbt.signAllInputs(dogeKeys);
704
+ psbt.finalizeAllInputs();
705
+ const txHex = psbt.extractTransaction().toHex();
706
+ const hash = yield this.roundRobinBroadcastTx(txHex);
707
+ return { hash, maxAmount, fee };
708
+ });
709
+ }
504
710
  }
505
711
 
506
712
  /**
@@ -581,6 +787,47 @@ class ClientLedger extends Client {
581
787
  return txHash;
582
788
  });
583
789
  }
790
+ // Transfer max DOGE from Ledger (sweep transaction)
791
+ transferMax(params) {
792
+ return __awaiter(this, void 0, void 0, function* () {
793
+ const app = yield this.getApp();
794
+ const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
795
+ const feeRate = params.feeRate || (yield this.getFeeRates())[FeeOption.Fast];
796
+ checkFeeBounds(this.feeBounds, feeRate);
797
+ const sender = yield this.getAddressAsync(fromAddressIndex);
798
+ const { rawUnsignedTx, inputs, maxAmount, fee } = yield this.prepareMaxTx({
799
+ sender,
800
+ recipient: params.recipient,
801
+ memo: params.memo,
802
+ feeRate,
803
+ utxoSelectionPreferences: params.utxoSelectionPreferences,
804
+ selectedUtxos: params.selectedUtxos,
805
+ });
806
+ const psbt = Dogecoin.Psbt.fromBase64(rawUnsignedTx);
807
+ const ledgerInputs = inputs.map(({ txHex, hash, index }) => {
808
+ if (!txHex) {
809
+ throw Error(`Missing 'txHex' for UTXO (txHash ${hash})`);
810
+ }
811
+ const splittedTx = app.splitTransaction(txHex, false /* no segwit support */);
812
+ return [splittedTx, index, null, null];
813
+ });
814
+ const associatedKeysets = ledgerInputs.map(() => this.getFullDerivationPath(fromAddressIndex));
815
+ const unsignedHex = psbt.data.globalMap.unsignedTx.toBuffer().toString('hex');
816
+ const newTx = app.splitTransaction(unsignedHex, true);
817
+ const outputScriptHex = app.serializeTransactionOutputs(newTx).toString('hex');
818
+ const txHex = yield app.createPaymentTransaction({
819
+ inputs: ledgerInputs,
820
+ associatedKeysets,
821
+ outputScriptHex,
822
+ additionals: [],
823
+ });
824
+ const hash = yield this.broadcastTx(txHex);
825
+ if (!hash) {
826
+ throw Error('No Tx hash');
827
+ }
828
+ return { hash, maxAmount, fee };
829
+ });
830
+ }
584
831
  }
585
832
 
586
833
  /**
package/lib/index.js CHANGED
@@ -367,6 +367,7 @@ class Client extends xchainUtxo.Client {
367
367
  * Asynchronously prepares a transaction for transfer.
368
368
  *
369
369
  * Builds a transaction (PSBT) with the specified transfer options.
370
+ * @deprecated Use `prepareTxEnhanced` instead for better UTXO selection and error handling.
370
371
  * @param {TxParams & { sender: Address; feeRate: FeeRate; spendPendingUTXO?: boolean }} params The transfer options including sender address, fee rate, and optional flag for spending pending UTXOs.
371
372
  * @returns {Promise<PreparedTx>} A promise that resolves to the raw unsigned transaction (PSBT).
372
373
  */
@@ -425,6 +426,183 @@ class Client extends xchainUtxo.Client {
425
426
  // Ensure the fee is not less than the minimum transaction fee
426
427
  return fee > MIN_TX_FEE ? fee : MIN_TX_FEE;
427
428
  }
429
+ // ==================== Enhanced Transaction Methods ====================
430
+ /**
431
+ * Build transaction with enhanced UTXO selection
432
+ * Note: Doge uses legacy P2PKH addresses (nonWitnessUtxo)
433
+ */
434
+ buildTxEnhanced(_a) {
435
+ return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
436
+ try {
437
+ this.validateTransactionInputs({
438
+ amount,
439
+ recipient,
440
+ memo,
441
+ sender,
442
+ feeRate,
443
+ });
444
+ let utxos;
445
+ if (selectedUtxos && selectedUtxos.length > 0) {
446
+ xchainUtxo.UtxoTransactionValidator.validateUtxoSet(selectedUtxos);
447
+ utxos = selectedUtxos;
448
+ }
449
+ else {
450
+ const confirmedOnly = !spendPendingUTXO;
451
+ utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
452
+ }
453
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
454
+ const targetValue = amount.amount().toNumber();
455
+ const extraOutputs = 1 + (compiledMemo ? 1 : 0);
456
+ const selectionResult = this.selectUtxosForTransaction(utxos, targetValue, Math.ceil(feeRate), extraOutputs, utxoSelectionPreferences);
457
+ const psbt = new Dogecoin__namespace.Psbt({ network: dogeNetwork(this.network) });
458
+ psbt.setMaximumFeeRate(7500000);
459
+ // Add inputs - Doge uses nonWitnessUtxo (legacy P2PKH)
460
+ for (const utxo of selectionResult.inputs) {
461
+ psbt.addInput({
462
+ hash: utxo.hash,
463
+ index: utxo.index,
464
+ nonWitnessUtxo: Buffer.from(utxo.txHex || '', 'hex'),
465
+ });
466
+ }
467
+ // Add recipient output
468
+ psbt.addOutput({
469
+ address: recipient,
470
+ value: targetValue,
471
+ });
472
+ // Add change output if needed
473
+ if (selectionResult.changeAmount > 0) {
474
+ psbt.addOutput({
475
+ address: sender,
476
+ value: selectionResult.changeAmount,
477
+ });
478
+ }
479
+ // Add memo output if present
480
+ if (compiledMemo) {
481
+ psbt.addOutput({ script: compiledMemo, value: 0 });
482
+ }
483
+ return { psbt, utxos, inputs: selectionResult.inputs };
484
+ }
485
+ catch (error) {
486
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
487
+ throw error;
488
+ }
489
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'buildTxEnhanced');
490
+ }
491
+ });
492
+ }
493
+ /**
494
+ * Prepare transaction with enhanced UTXO selection
495
+ */
496
+ prepareTxEnhanced(_a) {
497
+ return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, utxoSelectionPreferences, selectedUtxos, }) {
498
+ try {
499
+ const { psbt, utxos, inputs } = yield this.buildTxEnhanced({
500
+ sender,
501
+ recipient,
502
+ amount,
503
+ feeRate,
504
+ memo,
505
+ spendPendingUTXO,
506
+ utxoSelectionPreferences,
507
+ selectedUtxos,
508
+ });
509
+ return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
510
+ }
511
+ catch (error) {
512
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
513
+ throw error;
514
+ }
515
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'prepareTxEnhanced');
516
+ }
517
+ });
518
+ }
519
+ /**
520
+ * Send maximum possible amount (sweep)
521
+ */
522
+ sendMax(_a) {
523
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
524
+ try {
525
+ if (!this.validateAddress(recipient)) {
526
+ throw xchainUtxo.UtxoError.invalidAddress(recipient, this.network);
527
+ }
528
+ if (!this.validateAddress(sender)) {
529
+ throw xchainUtxo.UtxoError.invalidAddress(sender, this.network);
530
+ }
531
+ let utxos;
532
+ if (selectedUtxos && selectedUtxos.length > 0) {
533
+ xchainUtxo.UtxoTransactionValidator.validateUtxoSet(selectedUtxos);
534
+ utxos = selectedUtxos;
535
+ }
536
+ else {
537
+ const confirmedOnly = !spendPendingUTXO;
538
+ utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
539
+ }
540
+ const maxCalc = this.calculateMaxSendableAmount(utxos, Math.ceil(feeRate), !!memo, utxoSelectionPreferences);
541
+ const compiledMemo = memo ? this.compileMemo(memo) : null;
542
+ const psbt = new Dogecoin__namespace.Psbt({ network: dogeNetwork(this.network) });
543
+ psbt.setMaximumFeeRate(7500000);
544
+ // Add inputs - Doge uses nonWitnessUtxo
545
+ for (const utxo of maxCalc.inputs) {
546
+ psbt.addInput({
547
+ hash: utxo.hash,
548
+ index: utxo.index,
549
+ nonWitnessUtxo: Buffer.from(utxo.txHex || '', 'hex'),
550
+ });
551
+ }
552
+ psbt.addOutput({
553
+ address: recipient,
554
+ value: maxCalc.amount,
555
+ });
556
+ if (compiledMemo) {
557
+ psbt.addOutput({ script: compiledMemo, value: 0 });
558
+ }
559
+ return {
560
+ psbt,
561
+ utxos,
562
+ inputs: maxCalc.inputs,
563
+ maxAmount: maxCalc.amount,
564
+ fee: maxCalc.fee,
565
+ };
566
+ }
567
+ catch (error) {
568
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
569
+ throw error;
570
+ }
571
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'sendMax');
572
+ }
573
+ });
574
+ }
575
+ /**
576
+ * Prepare max send transaction
577
+ */
578
+ prepareMaxTx(_a) {
579
+ return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
580
+ try {
581
+ const { psbt, utxos, inputs, maxAmount, fee } = yield this.sendMax({
582
+ sender,
583
+ recipient,
584
+ memo,
585
+ feeRate,
586
+ spendPendingUTXO,
587
+ utxoSelectionPreferences,
588
+ selectedUtxos,
589
+ });
590
+ return {
591
+ rawUnsignedTx: psbt.toBase64(),
592
+ utxos,
593
+ inputs,
594
+ maxAmount,
595
+ fee,
596
+ };
597
+ }
598
+ catch (error) {
599
+ if (xchainUtxo.UtxoError.isUtxoError(error)) {
600
+ throw error;
601
+ }
602
+ throw xchainUtxo.UtxoError.fromUnknown(error, 'prepareMaxTx');
603
+ }
604
+ });
605
+ }
428
606
  }
429
607
 
430
608
  const ECPair = ecpair.ECPairFactory(ecc__namespace);
@@ -501,33 +679,61 @@ class ClientKeystore extends Client {
501
679
  * Asynchronously transfers Dogecoin.
502
680
  *
503
681
  * Builds, signs, and broadcasts a Dogecoin transaction with the specified parameters.
504
- * @param {TxParams & { feeRate?: FeeRate }} params The transfer parameters including transaction details and optional fee rate.
505
- * @returns {TxHash} A promise that resolves to the transaction hash once the transfer is completed.
682
+ *
683
+ * @param {Object} params The transfer parameters.
684
+ * @returns {Promise<TxHash>} The transaction hash.
506
685
  */
507
686
  transfer(params) {
508
687
  return __awaiter(this, void 0, void 0, function* () {
509
- // Determine the fee rate for the transaction, using provided fee rate or fetching it from the network
510
688
  const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
511
- // Check if the fee rate is within the specified fee bounds
512
689
  xchainClient.checkFeeBounds(this.feeBounds, feeRate);
513
- // Get the index of the sender's address or use the default index (0)
514
690
  const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
515
- // Prepare the transaction by building it with the specified parameters
516
- const { rawUnsignedTx } = yield this.prepareTx(Object.assign(Object.assign({}, params), { feeRate, sender: yield this.getAddressAsync(fromAddressIndex) }));
517
- // Get the Dogecoin keys for signing the transaction
691
+ const sender = yield this.getAddressAsync(fromAddressIndex);
518
692
  const dogeKeys = this.getDogeKeys(this.phrase, fromAddressIndex);
519
- // Create a Partially Signed Bitcoin Transaction (PSBT) from the raw unsigned transaction
693
+ const mergedPreferences = Object.assign({ minimizeFee: true, avoidDust: true, minimizeInputs: false }, params.utxoSelectionPreferences);
694
+ const { rawUnsignedTx } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { feeRate,
695
+ sender, utxoSelectionPreferences: mergedPreferences, selectedUtxos: params.selectedUtxos }));
520
696
  const psbt = Dogecoin__namespace.Psbt.fromBase64(rawUnsignedTx, { maximumFeeRate: 7500000 });
521
- // Sign all inputs of the transaction with the Dogecoin keys
522
697
  psbt.signAllInputs(dogeKeys);
523
- // Finalize all inputs of the transaction
524
698
  psbt.finalizeAllInputs();
525
- // Extract the signed transaction and format it to hexadecimal
526
699
  const txHex = psbt.extractTransaction().toHex();
527
- // Broadcast the signed transaction to the Dogecoin network and return the transaction hash
528
700
  return yield this.roundRobinBroadcastTx(txHex);
529
701
  });
530
702
  }
703
+ /**
704
+ * Transfer the maximum amount of Dogecoin (sweep).
705
+ *
706
+ * Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
707
+ * @param {Object} params The transfer parameters.
708
+ * @param {string} params.recipient The recipient address.
709
+ * @param {string} [params.memo] Optional memo for the transaction.
710
+ * @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
711
+ * @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
712
+ * @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
713
+ * @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
714
+ */
715
+ transferMax(params) {
716
+ return __awaiter(this, void 0, void 0, function* () {
717
+ const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
718
+ xchainClient.checkFeeBounds(this.feeBounds, feeRate);
719
+ const fromAddressIndex = params.walletIndex || 0;
720
+ const sender = yield this.getAddressAsync(fromAddressIndex);
721
+ const { psbt, maxAmount, fee } = yield this.sendMax({
722
+ sender,
723
+ recipient: params.recipient,
724
+ memo: params.memo,
725
+ feeRate,
726
+ utxoSelectionPreferences: params.utxoSelectionPreferences,
727
+ selectedUtxos: params.selectedUtxos,
728
+ });
729
+ const dogeKeys = this.getDogeKeys(this.phrase, fromAddressIndex);
730
+ psbt.signAllInputs(dogeKeys);
731
+ psbt.finalizeAllInputs();
732
+ const txHex = psbt.extractTransaction().toHex();
733
+ const hash = yield this.roundRobinBroadcastTx(txHex);
734
+ return { hash, maxAmount, fee };
735
+ });
736
+ }
531
737
  }
532
738
 
533
739
  /**
@@ -608,6 +814,47 @@ class ClientLedger extends Client {
608
814
  return txHash;
609
815
  });
610
816
  }
817
+ // Transfer max DOGE from Ledger (sweep transaction)
818
+ transferMax(params) {
819
+ return __awaiter(this, void 0, void 0, function* () {
820
+ const app = yield this.getApp();
821
+ const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
822
+ const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
823
+ xchainClient.checkFeeBounds(this.feeBounds, feeRate);
824
+ const sender = yield this.getAddressAsync(fromAddressIndex);
825
+ const { rawUnsignedTx, inputs, maxAmount, fee } = yield this.prepareMaxTx({
826
+ sender,
827
+ recipient: params.recipient,
828
+ memo: params.memo,
829
+ feeRate,
830
+ utxoSelectionPreferences: params.utxoSelectionPreferences,
831
+ selectedUtxos: params.selectedUtxos,
832
+ });
833
+ const psbt = Dogecoin__namespace.Psbt.fromBase64(rawUnsignedTx);
834
+ const ledgerInputs = inputs.map(({ txHex, hash, index }) => {
835
+ if (!txHex) {
836
+ throw Error(`Missing 'txHex' for UTXO (txHash ${hash})`);
837
+ }
838
+ const splittedTx = app.splitTransaction(txHex, false /* no segwit support */);
839
+ return [splittedTx, index, null, null];
840
+ });
841
+ const associatedKeysets = ledgerInputs.map(() => this.getFullDerivationPath(fromAddressIndex));
842
+ const unsignedHex = psbt.data.globalMap.unsignedTx.toBuffer().toString('hex');
843
+ const newTx = app.splitTransaction(unsignedHex, true);
844
+ const outputScriptHex = app.serializeTransactionOutputs(newTx).toString('hex');
845
+ const txHex = yield app.createPaymentTransaction({
846
+ inputs: ledgerInputs,
847
+ associatedKeysets,
848
+ outputScriptHex,
849
+ additionals: [],
850
+ });
851
+ const hash = yield this.broadcastTx(txHex);
852
+ if (!hash) {
853
+ throw Error('No Tx hash');
854
+ }
855
+ return { hash, maxAmount, fee };
856
+ });
857
+ }
611
858
  }
612
859
 
613
860
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xchainjs/xchain-doge",
3
- "version": "2.0.10",
3
+ "version": "2.2.0",
4
4
  "description": "Custom Doge client and utilities used by XChain 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.10",
42
+ "@xchainjs/xchain-utxo": "2.2.0",
43
43
  "@xchainjs/xchain-utxo-providers": "2.0.10",
44
44
  "bitcoinjs-lib": "^6.1.7",
45
45
  "coinselect": "3.1.12",