@@ -4,14 +4,17 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "sort"
8+ "strings"
79
810 "github.com/lightninglabs/loop/labels"
911 "github.com/lightninglabs/loop/looprpc"
10- "github.com/lightninglabs/loop/staticaddr/deposit"
1112 "github.com/lightninglabs/loop/staticaddr/loopin"
1213 "github.com/lightninglabs/loop/swapserverrpc"
1314 lndcommands "github.com/lightningnetwork/lnd/cmd/commands"
15+ "github.com/lightningnetwork/lnd/input"
1416 "github.com/lightningnetwork/lnd/lnrpc"
17+ "github.com/lightningnetwork/lnd/lnwallet"
1518 "github.com/lightningnetwork/lnd/routing/route"
1619 "github.com/urfave/cli/v3"
1720)
@@ -553,11 +556,14 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
553556 allDeposits := depositList .FilteredDeposits
554557
555558 if len (allDeposits ) == 0 {
556- errString := fmt .Sprintf ("no confirmed deposits available, " +
557- "deposits need at least %v confirmations" ,
558- deposit .MinConfs )
559+ return errors .New ("no deposited outputs available" )
560+ }
559561
560- return errors .New (errString )
562+ summary , err := client .GetStaticAddressSummary (
563+ ctx , & looprpc.StaticAddressSummaryRequest {},
564+ )
565+ if err != nil {
566+ return err
561567 }
562568
563569 var depositOutpoints []string
@@ -614,6 +620,21 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
614620 return err
615621 }
616622
623+ // Warn the user if any selected deposits have fewer than 6
624+ // confirmations, as the swap payment won't be received immediately
625+ // for those.
626+ depositsToCheck := warningDepositOutpoints (
627+ allDeposits , depositOutpoints , autoSelectDepositsForQuote ,
628+ quoteReq .Amt ,
629+ )
630+ warning := lowConfDepositWarning (
631+ allDeposits , depositsToCheck ,
632+ int64 (summary .RelativeExpiryBlocks ),
633+ )
634+ if warning != "" {
635+ fmt .Println (warning )
636+ }
637+
617638 if ! (cmd .Bool ("force" ) || cmd .Bool ("f" )) {
618639 err = displayInDetails (quoteReq , quote , cmd .Bool ("verbose" ))
619640 if err != nil {
@@ -669,6 +690,157 @@ func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
669690 return outpoints
670691}
671692
693+ var warningSelectionDustLimit = int64 (lnwallet .DustLimitForSize (input .P2TRSize ))
694+
695+ func warningDepositOutpoints (allDeposits []* looprpc.Deposit ,
696+ selectedOutpoints []string , autoSelect bool , targetAmount int64 ) []string {
697+
698+ if ! autoSelect {
699+ return selectedOutpoints
700+ }
701+
702+ return autoSelectedWarningOutpoints (allDeposits , targetAmount )
703+ }
704+
705+ func autoSelectedWarningOutpoints (allDeposits []* looprpc.Deposit ,
706+ targetAmount int64 ) []string {
707+
708+ if targetAmount <= 0 {
709+ return nil
710+ }
711+
712+ // KEEP IN SYNC with staticaddr/loopin.SelectDeposits.
713+ deposits := filterSwappableWarningDeposits (allDeposits )
714+ sort .Slice (deposits , func (i , j int ) bool {
715+ iConfirmed := deposits [i ].ConfirmationHeight > 0
716+ jConfirmed := deposits [j ].ConfirmationHeight > 0
717+ if iConfirmed != jConfirmed {
718+ return iConfirmed
719+ }
720+
721+ if deposits [i ].Value == deposits [j ].Value {
722+ return deposits [i ].BlocksUntilExpiry <
723+ deposits [j ].BlocksUntilExpiry
724+ }
725+
726+ return deposits [i ].Value > deposits [j ].Value
727+ })
728+
729+ selectedOutpoints := make ([]string , 0 , len (deposits ))
730+ var selectedAmount int64
731+ for _ , deposit := range deposits {
732+ selectedOutpoints = append (selectedOutpoints , deposit .Outpoint )
733+ selectedAmount += deposit .Value
734+ if selectedAmount == targetAmount {
735+ return selectedOutpoints
736+ }
737+
738+ if selectedAmount > targetAmount &&
739+ selectedAmount - targetAmount >= warningSelectionDustLimit {
740+
741+ return selectedOutpoints
742+ }
743+ }
744+
745+ return nil
746+ }
747+
748+ func filterSwappableWarningDeposits (
749+ allDeposits []* looprpc.Deposit ) []* looprpc.Deposit {
750+
751+ swappable := make ([]* looprpc.Deposit , 0 , len (allDeposits ))
752+ minBlocksUntilExpiry := int64 (
753+ loopin .DefaultLoopInOnChainCltvDelta + loopin .DepositHtlcDelta ,
754+ )
755+ for _ , deposit := range allDeposits {
756+ // Unconfirmed deposits remain swappable because their CSV timeout has
757+ // not started yet. This mirrors loopin.IsSwappable.
758+ if deposit .ConfirmationHeight > 0 &&
759+ deposit .BlocksUntilExpiry < minBlocksUntilExpiry {
760+
761+ continue
762+ }
763+
764+ swappable = append (swappable , deposit )
765+ }
766+
767+ return swappable
768+ }
769+
770+ // conservativeWarningConfs is the highest default confirmation tier used by
771+ // the server's dynamic confirmation-risk policy.
772+ //
773+ // The CLI does not currently know the server's exact policy, so we use this
774+ // conservative threshold for warnings without promising immediate execution.
775+ const conservativeWarningConfs = 6
776+
777+ // lowConfDepositWarning checks the selected deposits against a conservative
778+ // confirmation threshold and returns a warning string if any are found.
779+ func lowConfDepositWarning (allDeposits []* looprpc.Deposit ,
780+ selectedOutpoints []string , csvExpiry int64 ) string {
781+
782+ depositMap := make (map [string ]* looprpc.Deposit , len (allDeposits ))
783+ for _ , d := range allDeposits {
784+ depositMap [d .Outpoint ] = d
785+ }
786+
787+ var lowConfEntries []string
788+ for _ , op := range selectedOutpoints {
789+ d , ok := depositMap [op ]
790+ if ! ok {
791+ continue
792+ }
793+
794+ var confs int64
795+ switch {
796+ case d .ConfirmationHeight <= 0 :
797+ confs = 0
798+
799+ case csvExpiry > 0 :
800+ // For confirmed deposits we can compute
801+ // confirmations as CSVExpiry - BlocksUntilExpiry + 1.
802+ confs = csvExpiry - d .BlocksUntilExpiry + 1
803+
804+ default :
805+ // Can't determine confirmations without the CSV expiry.
806+ continue
807+ }
808+
809+ if confs >= conservativeWarningConfs {
810+ continue
811+ }
812+
813+ if confs == 0 {
814+ lowConfEntries = append (
815+ lowConfEntries ,
816+ fmt .Sprintf (" - %s (unconfirmed)" , op ),
817+ )
818+ } else {
819+ lowConfEntries = append (
820+ lowConfEntries ,
821+ fmt .Sprintf (
822+ " - %s (%d confirmations)" , op ,
823+ confs ,
824+ ),
825+ )
826+ }
827+ }
828+
829+ if len (lowConfEntries ) == 0 {
830+ return ""
831+ }
832+
833+ return fmt .Sprintf (
834+ "\n WARNING: The following deposits are below the " +
835+ "conservative %d-confirmation threshold:\n %s\n " +
836+ "The swap payment for these deposits may wait for " +
837+ "more confirmations depending on the server's " +
838+ "confirmation-risk policy.\n " ,
839+ conservativeWarningConfs ,
840+ strings .Join (lowConfEntries , "\n " ),
841+ )
842+ }
843+
672844func displayNewAddressWarning () error {
673845 fmt .Printf ("\n WARNING: Be aware that loosing your l402.token file in " +
674846 ".loop under your home directory will take your ability to " +
0 commit comments