5
5
"encoding/hex"
6
6
"errors"
7
7
"fmt"
8
+ "sort"
8
9
"strconv"
9
10
"strings"
10
11
@@ -14,6 +15,8 @@ import (
14
15
"github.com/lightninglabs/loop/staticaddr/deposit"
15
16
"github.com/lightninglabs/loop/staticaddr/loopin"
16
17
"github.com/lightninglabs/loop/swapserverrpc"
18
+ "github.com/lightningnetwork/lnd/input"
19
+ "github.com/lightningnetwork/lnd/lnwallet"
17
20
"github.com/lightningnetwork/lnd/routing/route"
18
21
"github.com/urfave/cli"
19
22
)
@@ -477,6 +480,13 @@ var staticAddressLoopInCommand = cli.Command{
477
480
"The client can retry the swap with adjusted " +
478
481
"parameters after the payment timed out." ,
479
482
},
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
+ },
480
490
lastHopFlag ,
481
491
labelFlag ,
482
492
routeHintsFlag ,
@@ -502,11 +512,14 @@ func staticAddressLoopIn(ctx *cli.Context) error {
502
512
ctxb = context .Background ()
503
513
isAllSelected = ctx .IsSet ("all" )
504
514
isUtxoSelected = ctx .IsSet ("utxo" )
515
+ isAmountSelected bool
516
+ selectedAmount = ctx .Int64 ("amount" )
505
517
label = ctx .String ("static-loop-in" )
506
518
hints []* swapserverrpc.RouteHint
507
519
lastHop []byte
508
520
paymentTimeoutSeconds = uint32 (loopin .DefaultPaymentTimeoutSeconds )
509
521
)
522
+ isAmountSelected = selectedAmount > 0
510
523
511
524
// Validate our label early so that we can fail before getting a quote.
512
525
if err := labels .Validate (label ); err != nil {
@@ -541,7 +554,9 @@ func staticAddressLoopIn(ctx *cli.Context) error {
541
554
return err
542
555
}
543
556
544
- if len (depositList .FilteredDeposits ) == 0 {
557
+ allDeposits := depositList .FilteredDeposits
558
+
559
+ if len (allDeposits ) == 0 {
545
560
errString := fmt .Sprintf ("no confirmed deposits available, " +
546
561
"deposits need at least %v confirmations" ,
547
562
deposit .MinConfs )
@@ -551,17 +566,25 @@ func staticAddressLoopIn(ctx *cli.Context) error {
551
566
552
567
var depositOutpoints []string
553
568
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" )
556
571
557
572
case isAllSelected :
558
- depositOutpoints = depositsToOutpoints (
559
- depositList .FilteredDeposits ,
560
- )
573
+ depositOutpoints = depositsToOutpoints (allDeposits )
561
574
562
575
case isUtxoSelected :
563
576
depositOutpoints = ctx .StringSlice ("utxo" )
564
577
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
+
565
588
default :
566
589
return fmt .Errorf ("unknown quote request" )
567
590
}
@@ -571,6 +594,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
571
594
}
572
595
573
596
quoteReq := & looprpc.QuoteRequest {
597
+ Amt : selectedAmount ,
574
598
LoopInRouteHints : hints ,
575
599
LoopInLastHop : lastHop ,
576
600
Private : ctx .Bool (privateFlag .Name ),
@@ -583,15 +607,6 @@ func staticAddressLoopIn(ctx *cli.Context) error {
583
607
584
608
limits := getInLimits (quote )
585
609
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
-
595
610
if ! (ctx .Bool ("force" ) || ctx .Bool ("f" )) {
596
611
err = displayInDetails (quoteReq , quote , ctx .Bool ("verbose" ))
597
612
if err != nil {
@@ -604,6 +619,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
604
619
}
605
620
606
621
req := & looprpc.StaticAddressLoopInRequest {
622
+ Amount : quoteReq .Amt ,
607
623
Outpoints : depositOutpoints ,
608
624
MaxSwapFeeSatoshis : int64 (limits .maxSwapFee ),
609
625
LastHop : lastHop ,
@@ -624,6 +640,47 @@ func staticAddressLoopIn(ctx *cli.Context) error {
624
640
return nil
625
641
}
626
642
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
+
627
684
func containsDuplicates (outpoints []string ) bool {
628
685
found := make (map [string ]struct {})
629
686
for _ , outpoint := range outpoints {
@@ -636,26 +693,6 @@ func containsDuplicates(outpoints []string) bool {
636
693
return false
637
694
}
638
695
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
-
659
696
func depositsToOutpoints (deposits []* looprpc.Deposit ) []string {
660
697
outpoints := make ([]string , 0 , len (deposits ))
661
698
for _ , deposit := range deposits {
0 commit comments