@@ -20,7 +20,9 @@ import (
2020 "github.com/lightninglabs/loop/staticaddr/deposit"
2121 "github.com/lightninglabs/loop/swapserverrpc"
2222 looprpc "github.com/lightninglabs/loop/swapserverrpc"
23+ "github.com/lightningnetwork/lnd/input"
2324 "github.com/lightningnetwork/lnd/lntypes"
25+ "github.com/lightningnetwork/lnd/lnwallet"
2426 "github.com/lightningnetwork/lnd/routing/route"
2527)
2628
@@ -206,8 +208,8 @@ func (m *Manager) Run(ctx context.Context) error {
206208 case request .respChan <- resp :
207209
208210 case <- ctx .Done ():
209- // Noify subroutines that the main loop has been
210- // canceled.
211+ // Notify subroutines that the main loop has
212+ // been canceled.
211213 close (m .exitChan )
212214
213215 return ctx .Err ()
@@ -297,6 +299,33 @@ func (m *Manager) handleLoopInSweepReq(ctx context.Context,
297299 len (req .PrevoutInfo ), len (sweepTx .TxIn ))
298300 }
299301
302+ // If the user selected an amount we'll check that the server sends us
303+ // the correct change amount back to our static address.
304+ if loopIn .SelectedAmount > 0 {
305+ var foundChange bool
306+ totalDepositAmount := loopIn .TotalDepositAmount ()
307+ changeAmt := totalDepositAmount - loopIn .SelectedAmount
308+ changePkScript := loopIn .AddressParams .PkScript
309+
310+ for _ , out := range sweepTx .TxOut {
311+ if out .Value == int64 (changeAmt ) &&
312+ bytes .Equal (out .PkScript , changePkScript ) {
313+
314+ foundChange = true
315+ break
316+ }
317+ }
318+
319+ if ! foundChange {
320+ return fmt .Errorf ("expected change output to our " +
321+ "static address, total_deposit_amount=%v, " +
322+ "selected_amount=%v, " +
323+ "expected_change_amount=%v " ,
324+ totalDepositAmount , loopIn .SelectedAmount ,
325+ changeAmt )
326+ }
327+ }
328+
300329 // Check if all the deposits requested are part of the loop-in and
301330 // find them in the requested sweep.
302331 depositToIdxMap , err := mapDepositsToIndices (req , loopIn , sweepTx )
@@ -531,14 +560,20 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
531560 req * loop.StaticAddressLoopInRequest ) (* StaticAddressLoopIn , error ) {
532561
533562 // Validate the loop-in request.
534- if len (req .DepositOutpoints ) == 0 {
535- return nil , fmt .Errorf ("no deposit outpoints provided" )
563+ if len (req .DepositOutpoints ) == 0 && req .SelectedAmount == 0 {
564+ return nil , fmt .Errorf ("no deposit outpoints provided and no " +
565+ "amount selected" )
536566 }
537567
568+ var (
569+ err error
570+ selectedOutpoints = req .DepositOutpoints
571+ )
572+
538573 // Retrieve all deposits referenced by the outpoints and ensure that
539574 // they are in state Deposited.
540575 deposits , active := m .cfg .DepositManager .AllStringOutpointsActiveDeposits ( //nolint:lll
541- req . DepositOutpoints , deposit .Deposited ,
576+ selectedOutpoints , deposit .Deposited ,
542577 )
543578 if ! active {
544579 return nil , fmt .Errorf ("one or more deposits are not in " +
@@ -551,8 +586,22 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
551586 }
552587 totalDepositAmount := tmp .TotalDepositAmount ()
553588
589+ // If the selected amount would leave a dust change output or exceeds
590+ // the total deposits value, we return an error.
591+ dustLimit := lnwallet .DustLimitForSize (input .P2TRSize )
592+ if totalDepositAmount - req .SelectedAmount < dustLimit {
593+ return nil , fmt .Errorf ("selected amount %v leaves " +
594+ "dust or exceeds total deposit value %v" ,
595+ req .SelectedAmount , totalDepositAmount )
596+ }
597+
598+ swapAmount := totalDepositAmount
599+ if req .SelectedAmount > 0 {
600+ swapAmount = req .SelectedAmount
601+ }
602+
554603 // Check that the label is valid.
555- err : = labels .Validate (req .Label )
604+ err = labels .Validate (req .Label )
556605 if err != nil {
557606 return nil , fmt .Errorf ("invalid label: %w" , err )
558607 }
@@ -576,7 +625,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
576625 // Because the Private flag is set, we'll generate our own set
577626 // of hop hints.
578627 req .RouteHints , err = loop .SelectHopHints (
579- ctx , m .cfg .LndClient , totalDepositAmount ,
628+ ctx , m .cfg .LndClient , swapAmount ,
580629 loop .DefaultMaxHopHints , includeNodes ,
581630 )
582631 if err != nil {
@@ -593,11 +642,11 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
593642 // directly anyway and there they have the option to add specific route
594643 // hints.
595644 // The quote call will also request a probe from the server to ensure
596- // feasibility of a loop-in for the totalDepositAmount .
645+ // feasibility of a loop-in for the selected .
597646 numDeposits := uint32 (len (deposits ))
598647 quote , err := m .cfg .QuoteGetter .GetLoopInQuote (
599- ctx , totalDepositAmount , m .cfg .NodePubkey , req .LastHop ,
600- req .RouteHints , req . Initiator , numDeposits ,
648+ ctx , swapAmount , m .cfg .NodePubkey , req .LastHop , req . RouteHints ,
649+ req .Initiator , numDeposits ,
601650 )
602651 if err != nil {
603652 return nil , fmt .Errorf ("unable to get loop in quote: %w" , err )
@@ -618,6 +667,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
618667 }
619668
620669 swap := & StaticAddressLoopIn {
670+ SelectedAmount : req .SelectedAmount ,
621671 DepositOutpoints : req .DepositOutpoints ,
622672 Deposits : deposits ,
623673 Label : req .Label ,
0 commit comments