Skip to content

Commit 3fc0128

Browse files
committed
cmd/loop: warn for auto-selected low-conf deposits
Remove the old "no confirmed deposits available" error now that mempool deposits are listed immediately and can be selected for static loop-ins. Reproduce the server static-address deposit selection order in the CLI using the already-returned deposit metadata. This keeps the low-confirmation warning focused on the deposits auto-selection would actually choose, so users only see it when the swap payment may wait for the server confirmation-risk policy.
1 parent 070eaa2 commit 3fc0128

2 files changed

Lines changed: 387 additions & 5 deletions

File tree

cmd/loop/staticaddr.go

Lines changed: 177 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
"\nWARNING: 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+
672844
func displayNewAddressWarning() error {
673845
fmt.Printf("\nWARNING: Be aware that loosing your l402.token file in " +
674846
".loop under your home directory will take your ability to " +

0 commit comments

Comments
 (0)