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
)
@@ -457,6 +460,13 @@ var staticAddressLoopInCommand = cli.Command{
457
460
"The client can retry the swap with adjusted " +
458
461
"parameters after the payment timed out." ,
459
462
},
463
+ cli.IntFlag {
464
+ Name : "amount" ,
465
+ Usage : "the number of satoshis that should be " +
466
+ "swapped from the selected deposits. If there" +
467
+ "is change it is sent back to the static " +
468
+ "address." ,
469
+ },
460
470
lastHopFlag ,
461
471
labelFlag ,
462
472
routeHintsFlag ,
@@ -482,11 +492,14 @@ func staticAddressLoopIn(ctx *cli.Context) error {
482
492
ctxb = context .Background ()
483
493
isAllSelected = ctx .IsSet ("all" )
484
494
isUtxoSelected = ctx .IsSet ("utxo" )
495
+ isAmountSelected bool
496
+ selectedAmount = ctx .Int64 ("amount" )
485
497
label = ctx .String ("static-loop-in" )
486
498
hints []* swapserverrpc.RouteHint
487
499
lastHop []byte
488
500
paymentTimeoutSeconds = uint32 (loopin .DefaultPaymentTimeoutSeconds )
489
501
)
502
+ isAmountSelected = selectedAmount > 0
490
503
491
504
// Validate our label early so that we can fail before getting a quote.
492
505
if err := labels .Validate (label ); err != nil {
@@ -521,7 +534,9 @@ func staticAddressLoopIn(ctx *cli.Context) error {
521
534
return err
522
535
}
523
536
524
- if len (depositList .FilteredDeposits ) == 0 {
537
+ allDeposits := depositList .FilteredDeposits
538
+
539
+ if len (allDeposits ) == 0 {
525
540
errString := fmt .Sprintf ("no confirmed deposits available, " +
526
541
"deposits need at least %v confirmations" ,
527
542
deposit .MinConfs )
@@ -531,17 +546,25 @@ func staticAddressLoopIn(ctx *cli.Context) error {
531
546
532
547
var depositOutpoints []string
533
548
switch {
534
- case isAllSelected == isUtxoSelected :
535
- return errors .New ("must select either all or some utxos" )
549
+ case isAllSelected && isUtxoSelected :
550
+ return errors .New ("cannot select all and specific utxos" )
536
551
537
552
case isAllSelected :
538
- depositOutpoints = depositsToOutpoints (
539
- depositList .FilteredDeposits ,
540
- )
553
+ depositOutpoints = depositsToOutpoints (allDeposits )
541
554
542
555
case isUtxoSelected :
543
556
depositOutpoints = ctx .StringSlice ("utxo" )
544
557
558
+ case isAmountSelected :
559
+ // If there's only a swap amount specified we'll coin-select
560
+ // deposits to cover the swap amount.
561
+ depositOutpoints , err = selectDeposits (
562
+ allDeposits , selectedAmount ,
563
+ )
564
+ if err != nil {
565
+ return err
566
+ }
567
+
545
568
default :
546
569
return fmt .Errorf ("unknown quote request" )
547
570
}
@@ -551,6 +574,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
551
574
}
552
575
553
576
quoteReq := & looprpc.QuoteRequest {
577
+ Amt : selectedAmount ,
554
578
LoopInRouteHints : hints ,
555
579
LoopInLastHop : lastHop ,
556
580
Private : ctx .Bool (privateFlag .Name ),
@@ -563,15 +587,6 @@ func staticAddressLoopIn(ctx *cli.Context) error {
563
587
564
588
limits := getInLimits (quote )
565
589
566
- // populate the quote request with the sum of selected deposits and
567
- // prompt the user for acceptance.
568
- quoteReq .Amt , err = sumDeposits (
569
- depositOutpoints , depositList .FilteredDeposits ,
570
- )
571
- if err != nil {
572
- return err
573
- }
574
-
575
590
if ! (ctx .Bool ("force" ) || ctx .Bool ("f" )) {
576
591
err = displayInDetails (quoteReq , quote , ctx .Bool ("verbose" ))
577
592
if err != nil {
@@ -584,6 +599,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
584
599
}
585
600
586
601
req := & looprpc.StaticAddressLoopInRequest {
602
+ Amount : quoteReq .Amt ,
587
603
Outpoints : depositOutpoints ,
588
604
MaxSwapFeeSatoshis : int64 (limits .maxSwapFee ),
589
605
LastHop : lastHop ,
@@ -604,36 +620,61 @@ func staticAddressLoopIn(ctx *cli.Context) error {
604
620
return nil
605
621
}
606
622
607
- func containsDuplicates (outpoints []string ) bool {
608
- found := make (map [string ]struct {})
609
- for _ , outpoint := range outpoints {
610
- if _ , ok := found [outpoint ]; ok {
611
- return true
612
- }
613
- found [outpoint ] = struct {}{}
614
- }
623
+ // selectDeposits sorts the deposits by amount in descending order, then by
624
+ // blocks-until-expiry in ascending order. It then selects the deposits that
625
+ // are needed to cover the amount requested without leaving a dust change. It
626
+ // returns an error if the sum of deposits minus dust is less than the requested
627
+ // amount.
628
+ func selectDeposits (deposits []* looprpc.Deposit , amount int64 ) ([]string ,
629
+ error ) {
615
630
616
- return false
617
- }
631
+ // Check that sum of deposits covers the swap amount while leaving no
632
+ // dust change.
633
+ dustLimit := lnwallet .DustLimitForSize (input .P2TRSize )
634
+ var depositSum int64
635
+ for _ , deposit := range deposits {
636
+ depositSum += deposit .Value
637
+ }
638
+ if depositSum - int64 (dustLimit ) < amount {
639
+ return nil , fmt .Errorf ("insufficient funds to cover swap " +
640
+ "amount" )
641
+ }
618
642
619
- func sumDeposits (outpoints []string , deposits []* looprpc.Deposit ) (int64 ,
620
- error ) {
643
+ // Sort the deposits by amount in descending order, then by
644
+ // blocks-until-expiry in ascending order.
645
+ sort .Slice (deposits , func (i , j int ) bool {
646
+ if deposits [i ].Value == deposits [j ].Value {
647
+ return deposits [i ].BlocksUntilExpiry <
648
+ deposits [j ].BlocksUntilExpiry
649
+ }
650
+ return deposits [i ].Value > deposits [j ].Value
651
+ })
621
652
622
- var sum int64
623
- depositMap := make (map [string ]* looprpc.Deposit )
653
+ // Select the deposits that are needed to cover the swap amount without
654
+ // leaving a dust change.
655
+ var selectedDeposits []string
656
+ var selectedAmount int64
624
657
for _ , deposit := range deposits {
625
- depositMap [deposit .Outpoint ] = deposit
658
+ if selectedAmount >= amount + int64 (dustLimit ) {
659
+ break
660
+ }
661
+ selectedDeposits = append (selectedDeposits , deposit .Outpoint )
662
+ selectedAmount += deposit .Value
626
663
}
627
664
665
+ return selectedDeposits , nil
666
+ }
667
+
668
+ func containsDuplicates (outpoints []string ) bool {
669
+ found := make (map [string ]struct {})
628
670
for _ , outpoint := range outpoints {
629
- if _ , ok := depositMap [outpoint ]; ! ok {
630
- return 0 , fmt . Errorf ( "deposit %v not found" , outpoint )
671
+ if _ , ok := found [outpoint ]; ok {
672
+ return true
631
673
}
632
-
633
- sum += depositMap [outpoint ].Value
674
+ found [outpoint ] = struct {}{}
634
675
}
635
676
636
- return sum , nil
677
+ return false
637
678
}
638
679
639
680
func depositsToOutpoints (deposits []* looprpc.Deposit ) []string {
0 commit comments