@@ -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 ()
@@ -530,14 +534,30 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
530534 req * loop.StaticAddressLoopInRequest ) (* StaticAddressLoopIn , error ) {
531535
532536 // 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.
533548 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+ }
535555 }
536556
537557 // Retrieve all deposits referenced by the outpoints and ensure that
538558 // they are in state Deposited.
539559 deposits , active := m .cfg .DepositManager .AllStringOutpointsActiveDeposits ( //nolint:lll
540- req . DepositOutpoints , deposit .Deposited ,
560+ selectedOutpoints , deposit .Deposited ,
541561 )
542562 if ! active {
543563 return nil , fmt .Errorf ("one or more deposits are not in " +
@@ -550,8 +570,17 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
550570 }
551571 totalDepositAmount := tmp .TotalDepositAmount ()
552572
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+
553582 // Check that the label is valid.
554- err : = labels .Validate (req .Label )
583+ err = labels .Validate (req .Label )
555584 if err != nil {
556585 return nil , fmt .Errorf ("invalid label: %w" , err )
557586 }
@@ -617,6 +646,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
617646 }
618647
619648 swap := & StaticAddressLoopIn {
649+ SelectedAmount : req .SelectedAmount ,
620650 DepositOutpoints : req .DepositOutpoints ,
621651 Deposits : deposits ,
622652 Label : req .Label ,
@@ -636,6 +666,52 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
636666 return m .startLoopInFsm (ctx , swap )
637667}
638668
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+
639715// startLoopInFsm initiates a loop-in state machine based on the user-provided
640716// swap information, sends that info to the server and waits for the server to
641717// return htlc signature information. It then creates the loop-in object in the
0 commit comments