Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
802afcb
staticaddr/deposit: handle loop-in htlc timeout
hieblmi Jul 1, 2026
402279a
staticaddr/deposit: ignore expiry blocks in final states
hieblmi Jul 1, 2026
d6057d3
staticaddr/deposit: reject duplicate outpoints
hieblmi Jul 1, 2026
928d1b2
staticaddr/loopin: handle closed invoice updates
hieblmi Jul 1, 2026
4b67920
staticaddr/deposit: stop removed fsms
hieblmi Jul 1, 2026
5669aeb
staticaddr/loopin: preserve selected deposit outpoints
hieblmi Jun 26, 2026
8bef01c
staticaddr/loopin: add lnd txout checker
hieblmi Jun 26, 2026
415415b
staticaddr/deposit: document lock ordering
hieblmi Jul 1, 2026
cc487e8
staticaddr/deposit: factor active deposit notifications
hieblmi Jul 1, 2026
541a387
staticaddr/deposit: serialize deposit reconciliation
hieblmi Jul 1, 2026
a759272
staticaddr/deposit: reject invalid transitions
hieblmi Jul 1, 2026
92f3c79
staticaddr/loopin: default payment timeout duration
hieblmi Jul 1, 2026
f5d426e
staticaddr/deposit: ignore queued expiry in final states
hieblmi Jul 1, 2026
318a473
staticaddr/deposit: guard confirmation height access
hieblmi Jul 1, 2026
566c541
staticaddr/loopin: keep htlc timeout sweep resumable
hieblmi Jul 1, 2026
64f5cb0
staticaddr/loopin: keep htlc monitor resumable
hieblmi Jul 2, 2026
01c3f97
staticaddr/deposit: canonicalize multi-deposit locks
hieblmi Jul 2, 2026
f062b64
staticaddr/loopin: recover deposits by current outpoint
hieblmi Jul 2, 2026
5cea937
staticaddr/loopin: use payment timeout duration helper
hieblmi Jul 2, 2026
765e028
staticaddr/loopin: check deposits before htlc signing
hieblmi Jul 2, 2026
2735593
staticaddr/loopin: scan wallet txouts once
hieblmi Jul 2, 2026
fc8e98d
staticaddr/loopin: update payment timeout comment
hieblmi Jul 2, 2026
1901295
build: increase lint timeout
hieblmi Jun 26, 2026
0d6dddc
staticaddr/deposit: track unconfirmed deposits
hieblmi Jul 1, 2026
abc4bae
staticaddr: expose tracked deposit availability
hieblmi Jun 26, 2026
00f87c4
staticaddr/loopin: account for unconfirmed deposit expiry
hieblmi Jun 26, 2026
9ee39e2
staticaddr: sync active deposits with wallet
hieblmi Jun 26, 2026
09c6432
staticaddr/loopin: cancel signing for unavailable deposits
hieblmi Jun 26, 2026
999b13a
staticaddr/loopin: wait for risk decisions
hieblmi Jun 26, 2026
85ce881
staticaddr/loopin: persist risk decisions
hieblmi Jun 26, 2026
3c99d41
cmd/loop: warn for low-confirmation static deposits
hieblmi Jun 26, 2026
1661b2e
cmd/loop: update static loop-in replay fixtures
hieblmi Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ run:
go: "1.26"

# timeout for analysis
timeout: 4m
timeout: 8m

linters:
default: all
Expand Down
189 changes: 183 additions & 6 deletions cmd/loop/staticaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import (
"context"
"errors"
"fmt"
"sort"
"strings"

"github.com/lightninglabs/loop/labels"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/loopin"
"github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/urfave/cli/v3"
)
Expand Down Expand Up @@ -553,11 +556,7 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
allDeposits := depositList.FilteredDeposits

if len(allDeposits) == 0 {
errString := fmt.Sprintf("no confirmed deposits available, "+
"deposits need at least %v confirmations",
deposit.MinConfs)

return errors.New(errString)
return errors.New("no deposited outputs available")
}

var depositOutpoints []string
Expand Down Expand Up @@ -614,6 +613,28 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
return err
}

// Warn the user if any selected deposits have fewer than 6
// confirmations, as the swap payment won't be received immediately
// for those.
summary, err := client.GetStaticAddressSummary(
ctx, &looprpc.StaticAddressSummaryRequest{},
)
if err != nil {
return err
}

depositsToCheck := warningDepositOutpoints(
allDeposits, depositOutpoints, autoSelectDepositsForQuote,
quoteReq.Amt,
)
warning := lowConfDepositWarning(
allDeposits, depositsToCheck,
int64(summary.RelativeExpiryBlocks),
)
if warning != "" {
fmt.Println(warning)
}

if !(cmd.Bool("force") || cmd.Bool("f")) {
err = displayInDetails(quoteReq, quote, cmd.Bool("verbose"))
if err != nil {
Expand Down Expand Up @@ -669,6 +690,162 @@ func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
return outpoints
}

var warningSelectionDustLimit = int64(lnwallet.DustLimitForSize(input.P2TRSize))

// warningDepositOutpoints returns the deposit outpoints to check for
// low-confirmation warnings.
func warningDepositOutpoints(allDeposits []*looprpc.Deposit,
selectedOutpoints []string, autoSelect bool, targetAmount int64) []string {

if !autoSelect {
return selectedOutpoints
}

return autoSelectedWarningOutpoints(allDeposits, targetAmount)
}

// autoSelectedWarningOutpoints returns the outpoints selected by the same
// ordering used for automatic static loop-in deposit selection.
func autoSelectedWarningOutpoints(allDeposits []*looprpc.Deposit,
targetAmount int64) []string {

if targetAmount <= 0 {
return nil
}

// KEEP IN SYNC with staticaddr/loopin.SelectDeposits.
Comment thread
hieblmi marked this conversation as resolved.
deposits := filterSwappableWarningDeposits(allDeposits)
sort.Slice(deposits, func(i, j int) bool {
iConfirmed := deposits[i].ConfirmationHeight > 0
jConfirmed := deposits[j].ConfirmationHeight > 0
if iConfirmed != jConfirmed {
return iConfirmed
}

if deposits[i].Value == deposits[j].Value {
return deposits[i].BlocksUntilExpiry <
deposits[j].BlocksUntilExpiry
}

return deposits[i].Value > deposits[j].Value
})

selectedOutpoints := make([]string, 0, len(deposits))
var selectedAmount int64
for _, deposit := range deposits {
selectedOutpoints = append(selectedOutpoints, deposit.Outpoint)
selectedAmount += deposit.Value
if selectedAmount == targetAmount {
return selectedOutpoints
}

if selectedAmount > targetAmount &&
selectedAmount-targetAmount >= warningSelectionDustLimit {

return selectedOutpoints
}
}

return nil
}

// filterSwappableWarningDeposits filters deposits for CLI warning selection.
func filterSwappableWarningDeposits(
allDeposits []*looprpc.Deposit) []*looprpc.Deposit {

swappable := make([]*looprpc.Deposit, 0, len(allDeposits))
minBlocksUntilExpiry := int64(
loopin.DefaultLoopInOnChainCltvDelta + loopin.DepositHtlcDelta,
)
for _, deposit := range allDeposits {
// Unconfirmed deposits remain swappable because their CSV timeout has
// not started yet. This mirrors loopin.IsSwappable.
if deposit.ConfirmationHeight > 0 &&
deposit.BlocksUntilExpiry < minBlocksUntilExpiry {

continue
}

swappable = append(swappable, deposit)
}

return swappable
}

// conservativeWarningConfs is the highest default confirmation tier used by
// the server's dynamic confirmation-risk policy.
//
// The CLI does not currently know the server's exact policy, so we use this
// conservative threshold for warnings without promising immediate execution.
const conservativeWarningConfs = 6

// lowConfDepositWarning checks the selected deposits against a conservative
// confirmation threshold and returns a warning string if any are found.
func lowConfDepositWarning(allDeposits []*looprpc.Deposit,
selectedOutpoints []string, csvExpiry int64) string {

depositMap := make(map[string]*looprpc.Deposit, len(allDeposits))
for _, d := range allDeposits {
depositMap[d.Outpoint] = d
}

var lowConfEntries []string
for _, op := range selectedOutpoints {
d, ok := depositMap[op]
if !ok {
continue
}

var confs int64
switch {
case d.ConfirmationHeight <= 0:
confs = 0

case csvExpiry > 0:
// For confirmed deposits we can compute
// confirmations as CSVExpiry - BlocksUntilExpiry + 1.
confs = csvExpiry - d.BlocksUntilExpiry + 1

default:
// Can't determine confirmations without the CSV expiry.
continue
}

if confs >= conservativeWarningConfs {
continue
}

if confs == 0 {
lowConfEntries = append(
lowConfEntries,
fmt.Sprintf(" - %s (unconfirmed)", op),
)
} else {
lowConfEntries = append(
lowConfEntries,
fmt.Sprintf(
" - %s (%d confirmations)", op,
confs,
),
)
}
}

if len(lowConfEntries) == 0 {
return ""
}

return fmt.Sprintf(
"\nWARNING: The following deposits are below the "+
"conservative %d-confirmation threshold:\n%s\n"+
"The swap payment for these deposits may wait for "+
"more confirmations depending on the server's "+
"confirmation-risk policy.\n",
conservativeWarningConfs,
strings.Join(lowConfEntries, "\n"),
)
}

func displayNewAddressWarning() error {
fmt.Printf("\nWARNING: Be aware that loosing your l402.token file in " +
".loop under your home directory will take your ability to " +
Expand Down
Loading
Loading