@@ -4,10 +4,12 @@ import (
4
4
"bytes"
5
5
"context"
6
6
"fmt"
7
+ "sort"
7
8
"sync/atomic"
8
9
"time"
9
10
10
11
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
12
+ "github.com/btcsuite/btcd/btcutil"
11
13
"github.com/btcsuite/btcd/btcutil/psbt"
12
14
"github.com/btcsuite/btcd/chaincfg"
13
15
"github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -20,7 +22,9 @@ import (
20
22
"github.com/lightninglabs/loop/staticaddr/deposit"
21
23
"github.com/lightninglabs/loop/swapserverrpc"
22
24
looprpc "github.com/lightninglabs/loop/swapserverrpc"
25
+ "github.com/lightningnetwork/lnd/input"
23
26
"github.com/lightningnetwork/lnd/lntypes"
27
+ "github.com/lightningnetwork/lnd/lnwallet"
24
28
"github.com/lightningnetwork/lnd/routing/route"
25
29
)
26
30
@@ -205,8 +209,8 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
205
209
case request .respChan <- resp :
206
210
207
211
case <- ctx .Done ():
208
- // Noify subroutines that the main loop has been
209
- // canceled.
212
+ // Notify subroutines that the main loop has
213
+ // been canceled.
210
214
close (m .exitChan )
211
215
212
216
return ctx .Err ()
@@ -529,14 +533,30 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
529
533
req * loop.StaticAddressLoopInRequest ) (* StaticAddressLoopIn , error ) {
530
534
531
535
// Validate the loop-in request.
536
+ if len (req .DepositOutpoints ) == 0 && req .SelectedAmount == 0 {
537
+ return nil , fmt .Errorf ("no deposit outpoints provided and no " +
538
+ "amount selected" )
539
+ }
540
+
541
+ var (
542
+ err error
543
+ selectedOutpoints = req .DepositOutpoints
544
+ )
545
+ // If there's only an amount selected by the user, we need to find
546
+ // deposits that cover this amount.
532
547
if len (req .DepositOutpoints ) == 0 {
533
- return nil , fmt .Errorf ("no deposit outpoints provided" )
548
+ selectedOutpoints , err = m .selectDeposits (
549
+ ctx , req .SelectedAmount ,
550
+ )
551
+ if err != nil {
552
+ return nil , err
553
+ }
534
554
}
535
555
536
556
// Retrieve all deposits referenced by the outpoints and ensure that
537
557
// they are in state Deposited.
538
558
deposits , active := m .cfg .DepositManager .AllStringOutpointsActiveDeposits ( //nolint:lll
539
- req . DepositOutpoints , deposit .Deposited ,
559
+ selectedOutpoints , deposit .Deposited ,
540
560
)
541
561
if ! active {
542
562
return nil , fmt .Errorf ("one or more deposits are not in " +
@@ -549,8 +569,17 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
549
569
}
550
570
totalDepositAmount := tmp .TotalDepositAmount ()
551
571
572
+ // If the selected amount would leave a dust change output or exceeds
573
+ // the total deposits value, we return an error.
574
+ dustLimit := lnwallet .DustLimitForSize (input .P2TRSize )
575
+ if totalDepositAmount - req .SelectedAmount < dustLimit {
576
+ return nil , fmt .Errorf ("selected amount %v leaves " +
577
+ "dust or exceeds total deposit value %v" ,
578
+ req .SelectedAmount , totalDepositAmount )
579
+ }
580
+
552
581
// Check that the label is valid.
553
- err : = labels .Validate (req .Label )
582
+ err = labels .Validate (req .Label )
554
583
if err != nil {
555
584
return nil , fmt .Errorf ("invalid label: %w" , err )
556
585
}
@@ -616,6 +645,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
616
645
}
617
646
618
647
swap := & StaticAddressLoopIn {
648
+ SelectedAmount : req .SelectedAmount ,
619
649
DepositOutpoints : req .DepositOutpoints ,
620
650
Deposits : deposits ,
621
651
Label : req .Label ,
@@ -635,6 +665,52 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
635
665
return m .startLoopInFsm (ctx , swap )
636
666
}
637
667
668
+ // selectDeposits finds a set of deposits that are ready to be used for a
669
+ // loop-in and cover a given amount. It returns the outpoints of the selected
670
+ // deposits.
671
+ func (m * Manager ) selectDeposits (ctx context.Context ,
672
+ amount btcutil.Amount ) ([]string , error ) {
673
+
674
+ // TODO(hieblmi): provide sql query to get all deposits in given state.
675
+ allDeposits , err := m .cfg .DepositManager .GetAllDeposits (ctx )
676
+ if err != nil {
677
+ return nil , err
678
+ }
679
+
680
+ deposits := make ([]* deposit.Deposit , 0 )
681
+ for _ , d := range allDeposits {
682
+ if d .IsInState (deposit .Deposited ) {
683
+ deposits = append (deposits , d )
684
+ }
685
+ }
686
+
687
+ // Sort deposits by confirmation block in descending order first to pick
688
+ // the oldest deposits, then sort by deposit amount in descending order.
689
+ sort .Slice (deposits , func (i , j int ) bool {
690
+ if deposits [i ].ConfirmationHeight !=
691
+ deposits [j ].ConfirmationHeight {
692
+
693
+ return deposits [i ].ConfirmationHeight >
694
+ deposits [j ].ConfirmationHeight
695
+ }
696
+
697
+ return deposits [i ].Value > deposits [j ].Value
698
+ })
699
+
700
+ // Now select deposits from the front of the sorted slice until the sum
701
+ // satisfies the required amount.
702
+ selectedDeposits := make ([]string , 0 )
703
+ for _ , d := range deposits {
704
+ amount -= d .Value
705
+ selectedDeposits = append (selectedDeposits , d .OutPoint .String ())
706
+ if amount <= 0 {
707
+ return selectedDeposits , nil
708
+ }
709
+ }
710
+
711
+ return nil , fmt .Errorf ("not enough deposits to cover amount" )
712
+ }
713
+
638
714
// startLoopInFsm initiates a loop-in state machine based on the user-provided
639
715
// swap information, sends that info to the server and waits for the server to
640
716
// return htlc signature information. It then creates the loop-in object in the
0 commit comments