Skip to content

Commit b59d0dc

Browse files
hieblmiclaude
andcommitted
staticaddr: support dynamic deposit confirmation requirements
- Reduce MinConfs from 6 to 1 to allow faster swap attempts for small amounts while the server enforces risk-based confirmation requirements - Update SelectDeposits to prioritize more-confirmed deposits first, increasing the likelihood of server acceptance - Add client-side logging of insufficient confirmation details from server errors, helping users understand when deposits need more confirmations Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 764f45b commit b59d0dc

File tree

4 files changed

+70
-17
lines changed

4 files changed

+70
-17
lines changed

staticaddr/deposit/manager.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import (
2222
const (
2323
// MinConfs is the minimum number of confirmations we require for a
2424
// deposit to be considered available for loop-ins, coop-spends and
25-
// timeouts.
26-
MinConfs = 6
25+
// timeouts. The server enforces dynamic confirmation requirements
26+
// based on risk assessment, so the client uses 1 conf as the minimum
27+
// to enable faster swap attempts for small amounts.
28+
MinConfs = 1
2729

2830
// MaxConfs is unset since we don't require a max number of
2931
// confirmations for deposits.

staticaddr/loopin/actions.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/lightningnetwork/lnd/lnwallet"
3131
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
3232
"github.com/lightningnetwork/lnd/lnwire"
33+
"google.golang.org/grpc/status"
3334
)
3435

3536
const (
@@ -146,6 +147,10 @@ func (f *FSM) InitHtlcAction(ctx context.Context,
146147
ctx, loopInReq,
147148
)
148149
if err != nil {
150+
// Check if this is an insufficient confirmations error and log
151+
// the details to help the user understand what's needed.
152+
logInsufficientConfirmationsDetails(err)
153+
149154
err = fmt.Errorf("unable to initiate the loop-in with the "+
150155
"server: %w", err)
151156

@@ -910,3 +915,29 @@ func byteSliceTo66ByteSlice(b []byte) ([musig2.PubNonceSize]byte, error) {
910915

911916
return res, nil
912917
}
918+
919+
// logInsufficientConfirmationsDetails extracts and logs the per-deposit
920+
// confirmation details from a gRPC error if present.
921+
func logInsufficientConfirmationsDetails(err error) {
922+
st, ok := status.FromError(err)
923+
if !ok {
924+
return
925+
}
926+
927+
for _, detail := range st.Details() {
928+
confDetails, ok := detail.(*swapserverrpc.InsufficientConfirmationsDetails)
929+
if !ok {
930+
continue
931+
}
932+
933+
log.Warnf("Insufficient deposit confirmations, max wait: "+
934+
"%d blocks", confDetails.MaxBlocksToWait)
935+
936+
for _, dep := range confDetails.Deposits {
937+
log.Warnf(" Deposit %s: %d/%d confirmations "+
938+
"(need %d more blocks)",
939+
dep.Outpoint, dep.CurrentConfirmations,
940+
dep.RequiredConfirmations, dep.BlocksToWait)
941+
}
942+
}
943+
}

staticaddr/loopin/manager.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -888,18 +888,31 @@ func SelectDeposits(targetAmount btcutil.Amount,
888888
deposits = append(deposits, d)
889889
}
890890

891-
// Sort the deposits by amount in descending order, then by
892-
// blocks-until-expiry in ascending order.
891+
// Sort deposits to optimize for successful swaps with dynamic
892+
// confirmation requirements:
893+
// 1. More confirmations first (higher chance of server acceptance)
894+
// 2. Larger amounts first (to minimize number of deposits used)
895+
// 3. Expiring sooner first (to use time-sensitive deposits)
893896
sort.Slice(deposits, func(i, j int) bool {
894-
if deposits[i].Value == deposits[j].Value {
895-
iExp := uint32(deposits[i].ConfirmationHeight) +
896-
csvExpiry - blockHeight
897-
jExp := uint32(deposits[j].ConfirmationHeight) +
898-
csvExpiry - blockHeight
897+
// Primary: more confirmations first.
898+
iConfs := blockHeight - uint32(deposits[i].ConfirmationHeight)
899+
jConfs := blockHeight - uint32(deposits[j].ConfirmationHeight)
900+
if iConfs != jConfs {
901+
return iConfs > jConfs
902+
}
899903

900-
return iExp < jExp
904+
// Secondary: larger amounts first.
905+
if deposits[i].Value != deposits[j].Value {
906+
return deposits[i].Value > deposits[j].Value
901907
}
902-
return deposits[i].Value > deposits[j].Value
908+
909+
// Tertiary: expiring sooner first.
910+
iExp := uint32(deposits[i].ConfirmationHeight) +
911+
csvExpiry - blockHeight
912+
jExp := uint32(deposits[j].ConfirmationHeight) +
913+
csvExpiry - blockHeight
914+
915+
return iExp < jExp
903916
})
904917

905918
// Select the deposits that are needed to cover the swap amount without

staticaddr/loopin/manager_test.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ type testCase struct {
2626

2727
// TestSelectDeposits tests the selectDeposits function, which selects
2828
// deposits that can cover a target value while respecting the dust limit.
29+
// Sorting priority: 1) more confirmations first, 2) larger amounts first,
30+
// 3) expiring sooner first.
2931
func TestSelectDeposits(t *testing.T) {
32+
// Note: confirmations = blockHeight - ConfirmationHeight
33+
// Lower ConfirmationHeight means more confirmations at a given block.
3034
d1, d2, d3, d4 := &deposit.Deposit{
3135
Value: 1_000_000,
32-
ConfirmationHeight: 5_000,
36+
ConfirmationHeight: 5_000, // most confs at height 5100
3337
}, &deposit.Deposit{
3438
Value: 2_000_000,
3539
ConfirmationHeight: 5_001,
@@ -38,7 +42,7 @@ func TestSelectDeposits(t *testing.T) {
3842
ConfirmationHeight: 5_002,
3943
}, &deposit.Deposit{
4044
Value: 3_000_000,
41-
ConfirmationHeight: 5_003,
45+
ConfirmationHeight: 5_003, // fewest confs at height 5100
4246
}
4347
d1.Hash = chainhash.Hash{1}
4448
d1.Index = 0
@@ -58,17 +62,20 @@ func TestSelectDeposits(t *testing.T) {
5862
expectedErr: "",
5963
},
6064
{
61-
name: "prefer larger deposit when both cover",
65+
// d1 has more confirmations, so it's preferred even
66+
// though d2 is larger.
67+
name: "prefer more confirmed deposit over larger",
6268
deposits: []*deposit.Deposit{d1, d2},
6369
targetValue: 1_000_000,
64-
expected: []*deposit.Deposit{d2},
70+
expected: []*deposit.Deposit{d1},
6571
expectedErr: "",
6672
},
6773
{
68-
name: "prefer largest among three when one is enough",
74+
// d1 has the most confirmations among d1, d2, d3.
75+
name: "prefer most confirmed among three",
6976
deposits: []*deposit.Deposit{d1, d2, d3},
7077
targetValue: 1_000_000,
71-
expected: []*deposit.Deposit{d3},
78+
expected: []*deposit.Deposit{d1},
7279
expectedErr: "",
7380
},
7481
{

0 commit comments

Comments
 (0)