Skip to content

Commit 14d52be

Browse files
committed
cmd/loop: warn user about low-confirmation deposits before loop-in
Display a warning when selected deposits have fewer than 6 confirmations, since the swap payment for those won't be received immediately. Works for both manually selected and auto-selected deposits by deriving confirmation count from the CSV expiry and blocks-until-expiry fields.
1 parent e00219d commit 14d52be

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

cmd/loop/staticaddr.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
78

89
"github.com/lightninglabs/loop/labels"
910
"github.com/lightninglabs/loop/looprpc"
@@ -555,6 +556,13 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
555556
return errors.New("no deposited outputs available")
556557
}
557558

559+
summary, err := client.GetStaticAddressSummary(
560+
ctx, &looprpc.StaticAddressSummaryRequest{},
561+
)
562+
if err != nil {
563+
return err
564+
}
565+
558566
var depositOutpoints []string
559567
switch {
560568
case isAllSelected && isUtxoSelected:
@@ -609,6 +617,22 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
609617
return err
610618
}
611619

620+
// Warn the user if any selected deposits have fewer than 6
621+
// confirmations, as the swap payment won't be received immediately
622+
// for those.
623+
depositsToCheck := depositOutpoints
624+
if autoSelectDepositsForQuote {
625+
// When auto-selecting, any deposit could be chosen.
626+
depositsToCheck = depositsToOutpoints(allDeposits)
627+
}
628+
warning := lowConfDepositWarning(
629+
allDeposits, depositsToCheck,
630+
int64(summary.RelativeExpiryBlocks),
631+
)
632+
if warning != "" {
633+
fmt.Println(warning)
634+
}
635+
612636
if !(cmd.Bool("force") || cmd.Bool("f")) {
613637
err = displayInDetails(quoteReq, quote, cmd.Bool("verbose"))
614638
if err != nil {
@@ -664,6 +688,80 @@ func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
664688
return outpoints
665689
}
666690

691+
// conservativeWarningConfs is the highest default confirmation tier used by
692+
// the server's dynamic confirmation-risk policy.
693+
//
694+
// The CLI does not currently know the server's exact policy, so we use this
695+
// conservative threshold for warnings without promising immediate execution.
696+
const conservativeWarningConfs = 6
697+
698+
// lowConfDepositWarning checks the selected deposits against a conservative
699+
// confirmation threshold and returns a warning string if any are found.
700+
func lowConfDepositWarning(allDeposits []*looprpc.Deposit,
701+
selectedOutpoints []string, csvExpiry int64) string {
702+
703+
depositMap := make(map[string]*looprpc.Deposit, len(allDeposits))
704+
for _, d := range allDeposits {
705+
depositMap[d.Outpoint] = d
706+
}
707+
708+
var lowConfEntries []string
709+
for _, op := range selectedOutpoints {
710+
d, ok := depositMap[op]
711+
if !ok {
712+
continue
713+
}
714+
715+
var confs int64
716+
switch {
717+
case d.ConfirmationHeight <= 0:
718+
confs = 0
719+
720+
case csvExpiry > 0:
721+
// For confirmed deposits we can compute
722+
// confirmations as CSVExpiry - BlocksUntilExpiry + 1.
723+
confs = csvExpiry - d.BlocksUntilExpiry + 1
724+
725+
default:
726+
// Can't determine confirmations without the CSV expiry.
727+
continue
728+
}
729+
730+
if confs >= conservativeWarningConfs {
731+
continue
732+
}
733+
734+
if confs == 0 {
735+
lowConfEntries = append(
736+
lowConfEntries,
737+
fmt.Sprintf(" - %s (unconfirmed)", op),
738+
)
739+
} else {
740+
lowConfEntries = append(
741+
lowConfEntries,
742+
fmt.Sprintf(
743+
" - %s (%d confirmations)", op,
744+
confs,
745+
),
746+
)
747+
}
748+
}
749+
750+
if len(lowConfEntries) == 0 {
751+
return ""
752+
}
753+
754+
return fmt.Sprintf(
755+
"\nWARNING: The following deposits are below the "+
756+
"conservative %d-confirmation threshold:\n%s\n"+
757+
"The swap payment for these deposits may wait for "+
758+
"more confirmations depending on the server's "+
759+
"confirmation-risk policy.\n",
760+
conservativeWarningConfs,
761+
strings.Join(lowConfEntries, "\n"),
762+
)
763+
}
764+
667765
func displayNewAddressWarning() error {
668766
fmt.Printf("\nWARNING: Be aware that loosing your l402.token file in " +
669767
".loop under your home directory will take your ability to " +

cmd/loop/staticaddr_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/lightninglabs/loop/looprpc"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestLowConfDepositWarningConfirmedOnly(t *testing.T) {
12+
t.Parallel()
13+
14+
deposits := []*looprpc.Deposit{
15+
{
16+
Outpoint: "confirmed-low",
17+
ConfirmationHeight: 100,
18+
BlocksUntilExpiry: 140,
19+
},
20+
{
21+
Outpoint: "confirmed-high",
22+
ConfirmationHeight: 95,
23+
BlocksUntilExpiry: 139,
24+
},
25+
}
26+
27+
warning := lowConfDepositWarning(
28+
deposits, []string{"confirmed-low", "confirmed-high"}, 144,
29+
)
30+
31+
require.Contains(t, warning, "confirmed-low (5 confirmations)")
32+
require.NotContains(t, warning, "confirmed-high")
33+
}
34+
35+
func TestLowConfDepositWarningUnconfirmed(t *testing.T) {
36+
t.Parallel()
37+
38+
deposits := []*looprpc.Deposit{
39+
{
40+
Outpoint: "mempool",
41+
ConfirmationHeight: 0,
42+
BlocksUntilExpiry: 144,
43+
},
44+
}
45+
46+
warning := lowConfDepositWarning(deposits, []string{"mempool"}, 144)
47+
48+
require.Contains(t, warning, "mempool (unconfirmed)")
49+
require.True(
50+
t,
51+
strings.Contains(
52+
warning,
53+
"conservative 6-confirmation threshold",
54+
),
55+
)
56+
require.NotContains(t, warning, "executed immediately")
57+
}

0 commit comments

Comments
 (0)