55 "encoding/hex"
66 "errors"
77 "fmt"
8+ "sort"
89 "strconv"
910 "strings"
1011
@@ -14,6 +15,8 @@ import (
1415 "github.com/lightninglabs/loop/staticaddr/deposit"
1516 "github.com/lightninglabs/loop/staticaddr/loopin"
1617 "github.com/lightninglabs/loop/swapserverrpc"
18+ "github.com/lightningnetwork/lnd/input"
19+ "github.com/lightningnetwork/lnd/lnwallet"
1720 "github.com/lightningnetwork/lnd/routing/route"
1821 "github.com/urfave/cli"
1922)
@@ -477,6 +480,13 @@ var staticAddressLoopInCommand = cli.Command{
477480 "The client can retry the swap with adjusted " +
478481 "parameters after the payment timed out." ,
479482 },
483+ cli.IntFlag {
484+ Name : "amount" ,
485+ Usage : "the number of satoshis that should be " +
486+ "swapped from the selected deposits. If there" +
487+ "is change it is sent back to the static " +
488+ "address." ,
489+ },
480490 lastHopFlag ,
481491 labelFlag ,
482492 routeHintsFlag ,
@@ -502,11 +512,14 @@ func staticAddressLoopIn(ctx *cli.Context) error {
502512 ctxb = context .Background ()
503513 isAllSelected = ctx .IsSet ("all" )
504514 isUtxoSelected = ctx .IsSet ("utxo" )
515+ isAmountSelected bool
516+ selectedAmount = ctx .Int64 ("amount" )
505517 label = ctx .String ("static-loop-in" )
506518 hints []* swapserverrpc.RouteHint
507519 lastHop []byte
508520 paymentTimeoutSeconds = uint32 (loopin .DefaultPaymentTimeoutSeconds )
509521 )
522+ isAmountSelected = selectedAmount > 0
510523
511524 // Validate our label early so that we can fail before getting a quote.
512525 if err := labels .Validate (label ); err != nil {
@@ -541,7 +554,9 @@ func staticAddressLoopIn(ctx *cli.Context) error {
541554 return err
542555 }
543556
544- if len (depositList .FilteredDeposits ) == 0 {
557+ allDeposits := depositList .FilteredDeposits
558+
559+ if len (allDeposits ) == 0 {
545560 errString := fmt .Sprintf ("no confirmed deposits available, " +
546561 "deposits need at least %v confirmations" ,
547562 deposit .MinConfs )
@@ -551,17 +566,25 @@ func staticAddressLoopIn(ctx *cli.Context) error {
551566
552567 var depositOutpoints []string
553568 switch {
554- case isAllSelected == isUtxoSelected :
555- return errors .New ("must select either all or some utxos" )
569+ case isAllSelected && isUtxoSelected :
570+ return errors .New ("cannot select all and specific utxos" )
556571
557572 case isAllSelected :
558- depositOutpoints = depositsToOutpoints (
559- depositList .FilteredDeposits ,
560- )
573+ depositOutpoints = depositsToOutpoints (allDeposits )
561574
562575 case isUtxoSelected :
563576 depositOutpoints = ctx .StringSlice ("utxo" )
564577
578+ case isAmountSelected :
579+ // If there's only a swap amount specified we'll coin-select
580+ // deposits to cover the swap amount.
581+ depositOutpoints , err = selectDeposits (
582+ allDeposits , selectedAmount ,
583+ )
584+ if err != nil {
585+ return err
586+ }
587+
565588 default :
566589 return fmt .Errorf ("unknown quote request" )
567590 }
@@ -571,6 +594,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
571594 }
572595
573596 quoteReq := & looprpc.QuoteRequest {
597+ Amt : selectedAmount ,
574598 LoopInRouteHints : hints ,
575599 LoopInLastHop : lastHop ,
576600 Private : ctx .Bool (privateFlag .Name ),
@@ -583,15 +607,6 @@ func staticAddressLoopIn(ctx *cli.Context) error {
583607
584608 limits := getInLimits (quote )
585609
586- // populate the quote request with the sum of selected deposits and
587- // prompt the user for acceptance.
588- quoteReq .Amt , err = sumDeposits (
589- depositOutpoints , depositList .FilteredDeposits ,
590- )
591- if err != nil {
592- return err
593- }
594-
595610 if ! (ctx .Bool ("force" ) || ctx .Bool ("f" )) {
596611 err = displayInDetails (quoteReq , quote , ctx .Bool ("verbose" ))
597612 if err != nil {
@@ -604,6 +619,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
604619 }
605620
606621 req := & looprpc.StaticAddressLoopInRequest {
622+ Amount : quoteReq .Amt ,
607623 Outpoints : depositOutpoints ,
608624 MaxSwapFeeSatoshis : int64 (limits .maxSwapFee ),
609625 LastHop : lastHop ,
@@ -624,6 +640,47 @@ func staticAddressLoopIn(ctx *cli.Context) error {
624640 return nil
625641}
626642
643+ // selectDeposits sorts the deposits by amount in descending order, then by
644+ // blocks-until-expiry in ascending order. It then selects the deposits that
645+ // are needed to cover the amount requested without leaving a dust change. It
646+ // returns an error if the sum of deposits minus dust is less than the requested
647+ // amount.
648+ func selectDeposits (deposits []* looprpc.Deposit , targetAmount int64 ) ([]string ,
649+ error ) {
650+
651+ // Sort the deposits by amount in descending order, then by
652+ // blocks-until-expiry in ascending order.
653+ sort .Slice (deposits , func (i , j int ) bool {
654+ if deposits [i ].Value == deposits [j ].Value {
655+ return deposits [i ].BlocksUntilExpiry <
656+ deposits [j ].BlocksUntilExpiry
657+ }
658+ return deposits [i ].Value > deposits [j ].Value
659+ })
660+
661+ // Select the deposits that are needed to cover the swap amount without
662+ // leaving a dust change.
663+ var selectedDeposits []string
664+ var selectedAmount int64
665+ dustLimit := lnwallet .DustLimitForSize (input .P2TRSize )
666+ for _ , deposit := range deposits {
667+ selectedDeposits = append (selectedDeposits , deposit .Outpoint )
668+ selectedAmount += deposit .Value
669+ if selectedAmount == targetAmount {
670+ return selectedDeposits , nil
671+ }
672+ if selectedAmount > targetAmount {
673+ if selectedAmount - targetAmount >= int64 (dustLimit ) {
674+ return selectedDeposits , nil
675+ }
676+ }
677+ }
678+
679+ return nil , fmt .Errorf ("not enough deposits to cover " +
680+ "requested amount, selected %d but need %d" ,
681+ selectedAmount , targetAmount )
682+ }
683+
627684func containsDuplicates (outpoints []string ) bool {
628685 found := make (map [string ]struct {})
629686 for _ , outpoint := range outpoints {
@@ -636,26 +693,6 @@ func containsDuplicates(outpoints []string) bool {
636693 return false
637694}
638695
639- func sumDeposits (outpoints []string , deposits []* looprpc.Deposit ) (int64 ,
640- error ) {
641-
642- var sum int64
643- depositMap := make (map [string ]* looprpc.Deposit )
644- for _ , deposit := range deposits {
645- depositMap [deposit .Outpoint ] = deposit
646- }
647-
648- for _ , outpoint := range outpoints {
649- if _ , ok := depositMap [outpoint ]; ! ok {
650- return 0 , fmt .Errorf ("deposit %v not found" , outpoint )
651- }
652-
653- sum += depositMap [outpoint ].Value
654- }
655-
656- return sum , nil
657- }
658-
659696func depositsToOutpoints (deposits []* looprpc.Deposit ) []string {
660697 outpoints := make ([]string , 0 , len (deposits ))
661698 for _ , deposit := range deposits {
0 commit comments