@@ -75,6 +75,7 @@ type newWithdrawalRequest struct {
7575 respChan chan * newWithdrawalResponse
7676 destAddr string
7777 satPerVbyte int64
78+ amount int64
7879}
7980
8081// newWithdrawalResponse is used to return withdrawal info and error to the
@@ -156,10 +157,10 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
156157 err )
157158 }
158159
159- case request := <- m .newWithdrawalRequestChan :
160+ case req := <- m .newWithdrawalRequestChan :
160161 txHash , pkScript , err = m .WithdrawDeposits (
161- ctx , request .outpoints , request .destAddr ,
162- request .satPerVbyte ,
162+ ctx , req .outpoints , req .destAddr ,
163+ req .satPerVbyte , req . amount ,
163164 )
164165 if err != nil {
165166 log .Errorf ("Error withdrawing deposits: %v" ,
@@ -174,7 +175,7 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
174175 err : err ,
175176 }
176177 select {
177- case request .respChan <- resp :
178+ case req .respChan <- resp :
178179
179180 case <- ctx .Done ():
180181 // Notify subroutines that the main loop has
@@ -261,8 +262,8 @@ func (m *Manager) WaitInitComplete() {
261262
262263// WithdrawDeposits starts a deposits withdrawal flow.
263264func (m * Manager ) WithdrawDeposits (ctx context.Context ,
264- outpoints []wire.OutPoint , destAddr string , satPerVbyte int64 ) ( string ,
265- string , error ) {
265+ outpoints []wire.OutPoint , destAddr string , satPerVbyte int64 ,
266+ amount int64 ) ( string , string , error ) {
266267
267268 if len (outpoints ) == 0 {
268269 return "" , "" , fmt .Errorf ("no outpoints selected to " +
@@ -272,7 +273,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
272273 // Ensure that the deposits are in a state in which they can be
273274 // withdrawn.
274275 deposits , allActive := m .cfg .DepositManager .AllOutpointsActiveDeposits (
275- outpoints , deposit .Deposited )
276+ outpoints , deposit .Deposited ,
277+ )
276278
277279 if ! allActive {
278280 return "" , "" , ErrWithdrawingInactiveDeposits
@@ -303,7 +305,7 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
303305 }
304306
305307 finalizedTx , err := m .createFinalizedWithdrawalTx (
306- ctx , deposits , withdrawalAddress , satPerVbyte ,
308+ ctx , deposits , withdrawalAddress , satPerVbyte , amount ,
307309 )
308310 if err != nil {
309311 return "" , "" , err
@@ -355,7 +357,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
355357
356358func (m * Manager ) createFinalizedWithdrawalTx (ctx context.Context ,
357359 deposits []* deposit.Deposit , withdrawalAddress btcutil.Address ,
358- satPerVbyte int64 ) (* wire.MsgTx , error ) {
360+ satPerVbyte int64 , selectedWithdrawalAmount int64 ) (* wire.MsgTx ,
361+ error ) {
359362
360363 // Create a musig2 session for each deposit.
361364 withdrawalSessions , clientNonces , err := m .createMusig2Sessions (
@@ -380,32 +383,43 @@ func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
380383 ).FeePerKWeight ()
381384 }
382385
383- outpoints := toOutpoints (deposits )
384- resp , err := m .cfg .StaticAddressServerClient .ServerWithdrawDeposits (
385- ctx , & staticaddressrpc.ServerWithdrawRequest {
386- Outpoints : toPrevoutInfo (outpoints ),
387- ClientNonces : clientNonces ,
388- ClientSweepAddr : withdrawalAddress .String (),
389- TxFeeRate : uint64 (withdrawalSweepFeeRate ),
390- },
386+ params , err := m .cfg .AddressManager .GetStaticAddressParameters (
387+ ctx ,
391388 )
392389 if err != nil {
393- return nil , err
390+ return nil , fmt .Errorf ("couldn't get confirmation height for " +
391+ "deposit, %w" , err )
394392 }
395393
396- addressParams , err := m .cfg .AddressManager .GetStaticAddressParameters (
397- ctx ,
394+ // Send change back to the static address.
395+ changeAddress , err := m .cfg .AddressManager .GetTaprootAddress (
396+ params .ClientPubkey , params .ServerPubkey , int64 (params .Expiry ),
398397 )
399398 if err != nil {
400- return nil , fmt .Errorf ("couldn't get confirmation height for " +
401- "deposit, %w" , err )
399+ log .Warnf ("error retrieving taproot address %w" , err )
400+
401+ return nil , fmt .Errorf ("withdrawal failed" )
402402 }
403403
404- prevOuts := m .toPrevOuts (deposits , addressParams .PkScript )
404+ outpoints := toOutpoints (deposits )
405+ prevOuts := m .toPrevOuts (deposits , params .PkScript )
405406 totalValue := withdrawalValue (prevOuts )
406- withdrawalTx , err := m .createWithdrawalTx (
407- outpoints , totalValue , withdrawalAddress ,
408- withdrawalSweepFeeRate ,
407+ withdrawalTx , withdrawAmount , changeAmount , err := m .createWithdrawalTx (
408+ outpoints , totalValue , btcutil .Amount (selectedWithdrawalAmount ),
409+ withdrawalAddress , changeAddress , withdrawalSweepFeeRate ,
410+ )
411+ if err != nil {
412+ return nil , err
413+ }
414+
415+ resp , err := m .cfg .StaticAddressServerClient .ServerWithdrawDeposits (
416+ ctx , & staticaddressrpc.ServerWithdrawRequest {
417+ Outpoints : toPrevoutInfo (outpoints ),
418+ ClientNonces : clientNonces ,
419+ ClientWithdrawalAddr : withdrawalAddress .String (),
420+ WithdrawAmount : uint64 (withdrawAmount ),
421+ ChangeAmount : int64 (changeAmount ),
422+ },
409423 )
410424 if err != nil {
411425 return nil , err
@@ -613,9 +627,10 @@ func byteSliceTo66ByteSlice(b []byte) ([musig2.PubNonceSize]byte, error) {
613627}
614628
615629func (m * Manager ) createWithdrawalTx (outpoints []wire.OutPoint ,
616- withdrawlAmount btcutil.Amount , clientSweepAddress btcutil.Address ,
617- feeRate chainfee.SatPerKWeight ) (* wire.MsgTx ,
618- error ) {
630+ totalWithdrawalAmount btcutil.Amount ,
631+ selectedWithdrawalAmount btcutil.Amount , withdrawAddr btcutil.Address ,
632+ changeAddress * btcutil.AddressTaproot , feeRate chainfee.SatPerKWeight ) (
633+ * wire.MsgTx , btcutil.Amount , btcutil.Amount , error ) {
619634
620635 // First Create the tx.
621636 msgTx := wire .NewMsgTx (2 )
@@ -628,33 +643,89 @@ func (m *Manager) createWithdrawalTx(outpoints []wire.OutPoint,
628643 })
629644 }
630645
631- // Estimate the fee.
632- weight , err := withdrawalFee (len (outpoints ), clientSweepAddress )
646+ var (
647+ hasChange bool
648+ dustLimit = lnwallet .DustLimitForSize (input .P2TRSize )
649+ withdrawalAmount = totalWithdrawalAmount
650+ changeAmount btcutil.Amount
651+ )
652+
653+ // Estimate the transaction weight without change.
654+ weight , err := withdrawalTxWeight (len (outpoints ), withdrawAddr , false )
633655 if err != nil {
634- return nil , err
656+ return nil , 0 , 0 , err
657+ }
658+ feeWithoutChange := feeRate .FeeForWeight (weight )
659+
660+ // If the user selected a fraction of the sum of the selected deposits
661+ // to withdraw, check if a change output is needed.
662+ if selectedWithdrawalAmount > 0 {
663+ // Estimate the transaction weight with change.
664+ weight , err = withdrawalTxWeight (
665+ len (outpoints ), withdrawAddr , true ,
666+ )
667+ if err != nil {
668+ return nil , 0 , 0 , err
669+ }
670+ feeWithChange := feeRate .FeeForWeight (weight )
671+
672+ change := totalWithdrawalAmount - selectedWithdrawalAmount
673+
674+ switch {
675+ case change - feeWithChange >= dustLimit :
676+ // If the change can cover the fees without turning into
677+ // dust, add a non-dust change output.
678+ hasChange = true
679+ changeAmount = change - feeWithChange
680+ withdrawalAmount = selectedWithdrawalAmount
681+
682+ case change - feeWithChange >= 0 :
683+ // If the change is dust, we give it to the miners.
684+ hasChange = false
685+ withdrawalAmount = selectedWithdrawalAmount
686+
687+ default :
688+ // If the fees eat into our withdrawal amount, we fail
689+ // the withdrawal.
690+ return nil , 0 , 0 , fmt .Errorf ("the change doesn't " +
691+ "cover for fees. Consider lowering the fee " +
692+ "rate or increase the withdrawal amount" )
693+ }
694+ } else {
695+ // If the user wants to withdraw the full amount, we don't need
696+ // a change output.
697+ withdrawalAmount = totalWithdrawalAmount - feeWithoutChange
635698 }
636699
637- pkscript , err := txscript .PayToAddrScript (clientSweepAddress )
700+ withdrawScript , err := txscript .PayToAddrScript (withdrawAddr )
638701 if err != nil {
639- return nil , err
702+ return nil , 0 , 0 , err
640703 }
641704
642- fee := feeRate .FeeForWeight (weight )
705+ // Create the withdrawal output.
706+ msgTx .AddTxOut (& wire.TxOut {
707+ Value : int64 (withdrawalAmount ),
708+ PkScript : withdrawScript ,
709+ })
643710
644- // Create the sweep output
645- sweepOutput := & wire. TxOut {
646- Value : int64 ( withdrawlAmount ) - int64 ( fee ),
647- PkScript : pkscript ,
648- }
711+ if hasChange {
712+ changeScript , err := txscript . PayToAddrScript ( changeAddress )
713+ if err != nil {
714+ return nil , 0 , 0 , err
715+ }
649716
650- msgTx .AddTxOut (sweepOutput )
717+ msgTx .AddTxOut (& wire.TxOut {
718+ Value : int64 (changeAmount ),
719+ PkScript : changeScript ,
720+ })
721+ }
651722
652- return msgTx , nil
723+ return msgTx , withdrawalAmount , changeAmount , nil
653724}
654725
655726// withdrawalFee returns the weight for the withdrawal transaction.
656- func withdrawalFee (numInputs int ,
657- sweepAddress btcutil. Address ) (lntypes.WeightUnit , error ) {
727+ func withdrawalTxWeight (numInputs int , sweepAddress btcutil. Address ,
728+ hasChange bool ) (lntypes.WeightUnit , error ) {
658729
659730 var weightEstimator input.TxWeightEstimator
660731 for i := 0 ; i < numInputs ; i ++ {
@@ -676,6 +747,11 @@ func withdrawalFee(numInputs int,
676747 sweepAddress )
677748 }
678749
750+ // If there's a change output add the weight of the static address.
751+ if hasChange {
752+ weightEstimator .AddP2TROutput ()
753+ }
754+
679755 return weightEstimator .Weight (), nil
680756}
681757
@@ -814,13 +890,14 @@ func (m *Manager) republishWithdrawals(ctx context.Context) error {
814890// DeliverWithdrawalRequest forwards a withdrawal request to the manager main
815891// loop.
816892func (m * Manager ) DeliverWithdrawalRequest (ctx context.Context ,
817- outpoints []wire.OutPoint , destAddr string , satPerVbyte int64 ) ( string ,
818- string , error ) {
893+ outpoints []wire.OutPoint , destAddr string , satPerVbyte int64 ,
894+ amount int64 ) ( string , string , error ) {
819895
820896 request := newWithdrawalRequest {
821897 outpoints : outpoints ,
822898 destAddr : destAddr ,
823899 satPerVbyte : satPerVbyte ,
900+ amount : amount ,
824901 respChan : make (chan * newWithdrawalResponse ),
825902 }
826903
0 commit comments