@@ -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 ()
@@ -530,14 +534,30 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
530
534
req * loop.StaticAddressLoopInRequest ) (* StaticAddressLoopIn , error ) {
531
535
532
536
// Validate the loop-in request.
537
+ if len (req .DepositOutpoints ) == 0 && req .SelectedAmount == 0 {
538
+ return nil , fmt .Errorf ("no deposit outpoints provided and no " +
539
+ "amount selected" )
540
+ }
541
+
542
+ var (
543
+ err error
544
+ selectedOutpoints = req .DepositOutpoints
545
+ )
546
+ // If there's only an amount selected by the user, we need to find
547
+ // deposits that cover this amount.
533
548
if len (req .DepositOutpoints ) == 0 {
534
- return nil , fmt .Errorf ("no deposit outpoints provided" )
549
+ selectedOutpoints , err = m .selectDeposits (
550
+ ctx , req .SelectedAmount ,
551
+ )
552
+ if err != nil {
553
+ return nil , err
554
+ }
535
555
}
536
556
537
557
// Retrieve all deposits referenced by the outpoints and ensure that
538
558
// they are in state Deposited.
539
559
deposits , active := m .cfg .DepositManager .AllStringOutpointsActiveDeposits ( //nolint:lll
540
- req . DepositOutpoints , deposit .Deposited ,
560
+ selectedOutpoints , deposit .Deposited ,
541
561
)
542
562
if ! active {
543
563
return nil , fmt .Errorf ("one or more deposits are not in " +
@@ -550,8 +570,17 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
550
570
}
551
571
totalDepositAmount := tmp .TotalDepositAmount ()
552
572
573
+ // If the selected amount would leave a dust change output or exceeds
574
+ // the total deposits value, we return an error.
575
+ dustLimit := lnwallet .DustLimitForSize (input .P2TRSize )
576
+ if totalDepositAmount - req .SelectedAmount < dustLimit {
577
+ return nil , fmt .Errorf ("selected amount %v leaves " +
578
+ "dust or exceeds total deposit value %v" ,
579
+ req .SelectedAmount , totalDepositAmount )
580
+ }
581
+
553
582
// Check that the label is valid.
554
- err : = labels .Validate (req .Label )
583
+ err = labels .Validate (req .Label )
555
584
if err != nil {
556
585
return nil , fmt .Errorf ("invalid label: %w" , err )
557
586
}
@@ -617,6 +646,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
617
646
}
618
647
619
648
swap := & StaticAddressLoopIn {
649
+ SelectedAmount : req .SelectedAmount ,
620
650
DepositOutpoints : req .DepositOutpoints ,
621
651
Deposits : deposits ,
622
652
Label : req .Label ,
@@ -636,6 +666,52 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
636
666
return m .startLoopInFsm (ctx , swap )
637
667
}
638
668
669
+ // selectDeposits finds a set of deposits that are ready to be used for a
670
+ // loop-in and cover a given amount. It returns the outpoints of the selected
671
+ // deposits.
672
+ func (m * Manager ) selectDeposits (ctx context.Context ,
673
+ amount btcutil.Amount ) ([]string , error ) {
674
+
675
+ // TODO(hieblmi): provide sql query to get all deposits in given state.
676
+ allDeposits , err := m .cfg .DepositManager .GetAllDeposits (ctx )
677
+ if err != nil {
678
+ return nil , err
679
+ }
680
+
681
+ deposits := make ([]* deposit.Deposit , 0 )
682
+ for _ , d := range allDeposits {
683
+ if d .IsInState (deposit .Deposited ) {
684
+ deposits = append (deposits , d )
685
+ }
686
+ }
687
+
688
+ // Sort deposits by confirmation block in descending order first to pick
689
+ // the oldest deposits, then sort by deposit amount in descending order.
690
+ sort .Slice (deposits , func (i , j int ) bool {
691
+ if deposits [i ].ConfirmationHeight !=
692
+ deposits [j ].ConfirmationHeight {
693
+
694
+ return deposits [i ].ConfirmationHeight >
695
+ deposits [j ].ConfirmationHeight
696
+ }
697
+
698
+ return deposits [i ].Value > deposits [j ].Value
699
+ })
700
+
701
+ // Now select deposits from the front of the sorted slice until the sum
702
+ // satisfies the required amount.
703
+ selectedDeposits := make ([]string , 0 )
704
+ for _ , d := range deposits {
705
+ amount -= d .Value
706
+ selectedDeposits = append (selectedDeposits , d .OutPoint .String ())
707
+ if amount <= 0 {
708
+ return selectedDeposits , nil
709
+ }
710
+ }
711
+
712
+ return nil , fmt .Errorf ("not enough deposits to cover amount" )
713
+ }
714
+
639
715
// startLoopInFsm initiates a loop-in state machine based on the user-provided
640
716
// swap information, sends that info to the server and waits for the server to
641
717
// return htlc signature information. It then creates the loop-in object in the
0 commit comments