@@ -4,10 +4,12 @@ import (
44 "bytes"
55 "context"
66 "fmt"
7+ "sort"
78 "sync/atomic"
89 "time"
910
1011 "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
12+ "github.com/btcsuite/btcd/btcutil"
1113 "github.com/btcsuite/btcd/btcutil/psbt"
1214 "github.com/btcsuite/btcd/chaincfg"
1315 "github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -20,7 +22,9 @@ import (
2022 "github.com/lightninglabs/loop/staticaddr/deposit"
2123 "github.com/lightninglabs/loop/swapserverrpc"
2224 looprpc "github.com/lightninglabs/loop/swapserverrpc"
25+ "github.com/lightningnetwork/lnd/input"
2326 "github.com/lightningnetwork/lnd/lntypes"
27+ "github.com/lightningnetwork/lnd/lnwallet"
2428 "github.com/lightningnetwork/lnd/routing/route"
2529)
2630
@@ -205,8 +209,8 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
205209 case request .respChan <- resp :
206210
207211 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.
210214 close (m .exitChan )
211215
212216 return ctx .Err ()
@@ -529,14 +533,30 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
529533 req * loop.StaticAddressLoopInRequest ) (* StaticAddressLoopIn , error ) {
530534
531535 // 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.
532547 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+ }
534554 }
535555
536556 // Retrieve all deposits referenced by the outpoints and ensure that
537557 // they are in state Deposited.
538558 deposits , active := m .cfg .DepositManager .AllStringOutpointsActiveDeposits ( //nolint:lll
539- req . DepositOutpoints , deposit .Deposited ,
559+ selectedOutpoints , deposit .Deposited ,
540560 )
541561 if ! active {
542562 return nil , fmt .Errorf ("one or more deposits are not in " +
@@ -549,8 +569,17 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
549569 }
550570 totalDepositAmount := tmp .TotalDepositAmount ()
551571
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+
552581 // Check that the label is valid.
553- err : = labels .Validate (req .Label )
582+ err = labels .Validate (req .Label )
554583 if err != nil {
555584 return nil , fmt .Errorf ("invalid label: %w" , err )
556585 }
@@ -616,6 +645,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
616645 }
617646
618647 swap := & StaticAddressLoopIn {
648+ SelectedAmount : req .SelectedAmount ,
619649 DepositOutpoints : req .DepositOutpoints ,
620650 Deposits : deposits ,
621651 Label : req .Label ,
@@ -635,6 +665,52 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
635665 return m .startLoopInFsm (ctx , swap )
636666}
637667
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+
638714// startLoopInFsm initiates a loop-in state machine based on the user-provided
639715// swap information, sends that info to the server and waits for the server to
640716// return htlc signature information. It then creates the loop-in object in the
0 commit comments