cmd/loop: warn for low-confirmation static deposits

Warn before dispatching a static loop-in that selects deposits below
the conservative six-confirmation threshold. Mirror automatic coin
selection before prompting so the warning reflects both manual and
auto-selected deposits.

Cover manual and auto-selected warning paths in CLI tests.
This commit is contained in:
Slyghtning 2026-06-26 12:55:03 +02:00
parent c718e9a9fb
commit 43b16ff3e1
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
10 changed files with 1032 additions and 7 deletions

View file

@ -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"
)
@ -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
@ -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 {
@ -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.
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 " +

220
cmd/loop/staticaddr_test.go Normal file
View file

@ -0,0 +1,220 @@
package main
import (
"strings"
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/loopin"
"github.com/stretchr/testify/require"
)
// TestLowConfDepositWarningConfirmedOnly verifies confirmed deposits below the
// conservative warning threshold are included in the warning text.
func TestLowConfDepositWarningConfirmedOnly(t *testing.T) {
t.Parallel()
deposits := []*looprpc.Deposit{
{
Outpoint: "confirmed-low",
ConfirmationHeight: 100,
BlocksUntilExpiry: 140,
},
{
Outpoint: "confirmed-high",
ConfirmationHeight: 95,
BlocksUntilExpiry: 139,
},
}
warning := lowConfDepositWarning(
deposits, []string{"confirmed-low", "confirmed-high"}, 144,
)
require.Contains(t, warning, "confirmed-low (5 confirmations)")
require.NotContains(t, warning, "confirmed-high")
}
// TestLowConfDepositWarningUnconfirmed verifies unconfirmed deposits get a
// warning that the swap may wait for confirmation-risk acceptance.
func TestLowConfDepositWarningUnconfirmed(t *testing.T) {
t.Parallel()
deposits := []*looprpc.Deposit{
{
Outpoint: "mempool",
ConfirmationHeight: 0,
BlocksUntilExpiry: 144,
},
}
warning := lowConfDepositWarning(deposits, []string{"mempool"}, 144)
require.Contains(t, warning, "mempool (unconfirmed)")
require.True(
t,
strings.Contains(
warning,
"conservative 6-confirmation threshold",
),
)
require.NotContains(t, warning, "executed immediately")
}
// TestWarningDepositOutpointsAutoSelectPrefersConfirmed verifies automatic
// warning selection keeps the loop-in preference for confirmed outputs.
func TestWarningDepositOutpointsAutoSelectPrefersConfirmed(t *testing.T) {
t.Parallel()
const csvExpiry = 1100
deposits := []*looprpc.Deposit{
{
Outpoint: "mempool-large",
Value: 2_000_000,
ConfirmationHeight: 0,
BlocksUntilExpiry: csvExpiry,
},
{
Outpoint: "confirmed",
Value: 1_500_000,
ConfirmationHeight: 100,
BlocksUntilExpiry: csvExpiry - 5,
},
}
selected := warningDepositOutpoints(deposits, nil, true, 1_000_000)
require.Equal(t, []string{"confirmed"}, selected)
require.Empty(t, lowConfDepositWarning(deposits, selected, csvExpiry))
}
// TestWarningDepositOutpointsAutoSelectIncludesNeededUnconfirmed verifies the
// warning path includes mempool deposits when they are needed for the target.
func TestWarningDepositOutpointsAutoSelectIncludesNeededUnconfirmed(t *testing.T) {
t.Parallel()
const csvExpiry = 1100
deposits := []*looprpc.Deposit{
{
Outpoint: "confirmed-small",
Value: 500_000,
ConfirmationHeight: 100,
BlocksUntilExpiry: csvExpiry - 5,
},
{
Outpoint: "mempool-large",
Value: 2_000_000,
ConfirmationHeight: 0,
BlocksUntilExpiry: csvExpiry,
},
}
selected := warningDepositOutpoints(deposits, nil, true, 1_000_000)
require.Equal(
t, []string{"confirmed-small", "mempool-large"}, selected,
)
warning := lowConfDepositWarning(deposits, selected, csvExpiry)
require.Contains(t, warning, "mempool-large (unconfirmed)")
require.NotContains(t, warning, "confirmed-small")
}
// TestWarningDepositSelectionMatchesLoopInSelection verifies CLI warning
// selection matches the loop-in selector.
func TestWarningDepositSelectionMatchesLoopInSelection(t *testing.T) {
t.Parallel()
const (
blockHeight = uint32(10_000)
csvExpiry = uint32(1_200)
targetAmount = int64(2_500_000)
)
type fixture struct {
name string
value int64
confirmationHeight int64
}
fixtures := []fixture{
{
name: "mempool-huge",
value: 3_000_000,
confirmationHeight: 0,
},
{
name: "confirmed-later-expiry",
value: 2_000_000,
confirmationHeight: 9_900,
},
{
name: "confirmed-earlier-expiry",
value: 2_000_000,
confirmationHeight: 9_890,
},
{
name: "confirmed-small",
value: 600_000,
confirmationHeight: 9_900,
},
{
name: "confirmed-too-close-to-expiry",
value: 5_000_000,
confirmationHeight: 9_849,
},
}
rpcDeposits := make([]*looprpc.Deposit, 0, len(fixtures))
loopInDeposits := make([]*deposit.Deposit, 0, len(fixtures))
for idx, fixture := range fixtures {
hash := chainhash.Hash{byte(idx + 1)}
outpoint := wire.OutPoint{
Hash: hash,
Index: uint32(idx),
}
blocksUntilExpiry := int64(0)
if fixture.confirmationHeight > 0 {
blocksUntilExpiry = fixture.confirmationHeight +
int64(csvExpiry) - int64(blockHeight)
}
rpcDeposits = append(rpcDeposits, &looprpc.Deposit{
Outpoint: outpoint.String(),
Value: fixture.value,
ConfirmationHeight: fixture.confirmationHeight,
BlocksUntilExpiry: blocksUntilExpiry,
})
loopInDeposits = append(loopInDeposits, &deposit.Deposit{
OutPoint: outpoint,
Value: btcutil.Amount(fixture.value),
ConfirmationHeight: fixture.confirmationHeight,
})
}
cliSelected := autoSelectedWarningOutpoints(
rpcDeposits, targetAmount,
)
loopInSelected, err := loopin.SelectDeposits(
btcutil.Amount(targetAmount), loopInDeposits, csvExpiry,
blockHeight,
)
require.NoError(t, err)
loopInSelectedOutpoints := make([]string, 0, len(loopInSelected))
for _, selected := range loopInSelected {
loopInSelectedOutpoints = append(
loopInSelectedOutpoints, selected.OutPoint.String(),
)
}
require.Equal(t, loopInSelectedOutpoints, cliSelected)
}

View file

@ -71,7 +71,7 @@ Base URL: `http://127.0.0.1:12345`
| `quote/` | `loop quote out` (success + verbose), `loop quote in` (help + verbose), `loop quote out` (help), `loop quote in` (deposit_outpoint success), `loop quote in` (positional + last_hop) |
| `static/` | `loop static withdraw` (no selection error), `loop static withdraw` (invalid utxo), `loop static withdraw` (all success), `loop static withdraw` (utxo + dest_addr success), `loop static listwithdrawals`, `loop static listswaps` |
| `static-autoloop/` | `loop setparams --loopinsource static-address` (success + no-experimental error), `loop getparams` (static-address loop-in source), `loop suggestswaps` (static loop-in suggestion) |
| `static-loop-in/` | `loop static new`, `loop static` (help), `loop static listunspent` (incl alias), `loop static listdeposits`, `loop static summary`, `loop static in` (multiple args/flags cases), `loop static in` (duplicate outpoints), `loop static in` (positional low amount error), `loop static in` (positional + last_hop + payment_timeout), `loop static in` (all cancel) |
| `static-loop-in/` | `loop static new`, `loop static` (help), `loop static listunspent` (incl alias), `loop static listdeposits`, `loop static summary`, `loop static in` (multiple args/flags cases), `loop static in` (duplicate outpoints), `loop static in` (positional low amount error), `loop static in` (positional + last_hop + payment_timeout), `loop static in` (all cancel), `loop static in` (explicit and automatically selected low-confirmation warnings) |
| `static-filters/` | `loop static listdeposits --filter ...` for each state (deposited/withdrawing/withdrawn/looping_in/looped_in/publish_expired_deposit/sweep_htlc_timeout/htlc_timeout_swept/wait_for_expiry_sweep/expired/failed) |
| `swaps/` | `loop listswaps` (success + conflicting filters + loop_out_only filters + loop_in_only), `loop swapinfo` (success + invalid id + id flag errors), `loop abandonswap` (help + invalid id + success) |

View file

@ -91,6 +91,37 @@
}
}
},
{
"time_ms": 65,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 65,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
"relative_expiry_blocks": "14400",
"total_num_deposits": 1,
"value_unconfirmed_satoshis": "0",
"value_deposited_satoshis": "2500000",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 65,
"kind": "stdout",

View file

@ -94,6 +94,37 @@
}
}
},
{
"time_ms": 500,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 500,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
"relative_expiry_blocks": "14400",
"total_num_deposits": 1,
"value_unconfirmed_satoshis": "0",
"value_deposited_satoshis": "500000",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 500,
"kind": "grpc",

View file

@ -99,6 +99,37 @@
}
}
},
{
"time_ms": 446,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 446,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
"relative_expiry_blocks": "14400",
"total_num_deposits": 1,
"value_unconfirmed_satoshis": "0",
"value_deposited_satoshis": "500000",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 446,
"kind": "stdout",

View file

@ -127,6 +127,37 @@
}
}
},
{
"time_ms": 50,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 50,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1p604kzzh28764kkw45yps48weergwljggamhhe7tqfglzjzang6cs43f2m2",
"relative_expiry_blocks": "14400",
"total_num_deposits": 4,
"value_unconfirmed_satoshis": "0",
"value_deposited_satoshis": "2546150",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 50,
"kind": "grpc",

View file

@ -118,6 +118,37 @@
}
}
},
{
"time_ms": 45,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 45,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1p604kzzh28764kkw45yps48weergwljggamhhe7tqfglzjzang6cs43f2m2",
"relative_expiry_blocks": "14400",
"total_num_deposits": 3,
"value_unconfirmed_satoshis": "0",
"value_deposited_satoshis": "2046150",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 45,
"kind": "grpc",

View file

@ -0,0 +1,242 @@
{
"metadata": {
"args": [
"loop",
"static",
"in",
"--amt",
"500000",
"--utxo",
"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0",
"--network",
"regtest"
],
"env": {},
"version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6",
"duration": 2826015164,
"clock_start_unix": 1769407086
},
"events": [
{
"time_ms": 3,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
"event": "request",
"message_type": "looprpc.ListStaticAddressDepositsRequest",
"payload": {
"state_filter": "DEPOSITED",
"outpoints": []
}
}
},
{
"time_ms": 22,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
"event": "response",
"message_type": "looprpc.ListStaticAddressDepositsResponse",
"payload": {
"filtered_deposits": [
{
"id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=",
"state": "DEPOSITED",
"outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0",
"value": "2500000",
"confirmation_height": "131",
"blocks_until_expiry": "14396",
"swap_hash": ""
}
]
}
}
},
{
"time_ms": 22,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetLoopInQuote",
"event": "request",
"message_type": "looprpc.QuoteRequest",
"payload": {
"amt": "500000",
"conf_target": 0,
"external_htlc": false,
"swap_publication_deadline": "0",
"loop_in_last_hop": "",
"loop_in_route_hints": [],
"private": false,
"deposit_outpoints": [
"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0"
],
"asset_info": null,
"auto_select_deposits": false,
"fast": false
}
}
},
{
"time_ms": 65,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetLoopInQuote",
"event": "response",
"message_type": "looprpc.InQuoteResponse",
"payload": {
"swap_fee_sat": "1824",
"htlc_publish_fee_sat": "0",
"cltv_delta": 0,
"conf_target": 0,
"quoted_amt": "500000"
}
}
},
{
"time_ms": 65,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 65,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
"relative_expiry_blocks": "14400",
"total_num_deposits": 1,
"value_unconfirmed_satoshis": "0",
"value_deposited_satoshis": "2500000",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 65,
"kind": "stdout",
"data": {
"lines": [
"\n",
"WARNING: The following deposits are below the conservative 6-confirmation threshold:\n",
" - 188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0 (5 confirmations)\n",
"The swap payment for these deposits may wait for more confirmations depending on the server's confirmation-risk policy.\n",
"\n",
"Previously deposited on-chain: 500000 sat\n",
"Receive off-chain: 498176 sat\n",
"Estimated total fee: 1824 sat\n",
"\n",
"CONTINUE SWAP? (y/n): {\n",
" \"amount\": \"2500000\",\n",
" \"change\": \"2000000\",\n",
" \"fast\": false,\n",
" \"htlc_cltv\": 1136,\n",
" \"initiation_height\": 136,\n",
" \"initiator\": \"loop-cli\",\n",
" \"label\": \"\",\n",
" \"max_swap_fee_satoshis\": \"1824\",\n",
" \"payment_timeout_seconds\": 60,\n",
" \"protocol_version\": \"V0\",\n",
" \"quoted_swap_fee_satoshis\": \"1824\",\n",
" \"state\": \"SignHtlcTx\",\n",
" \"swap_amount\": \"500000\",\n",
" \"swap_hash\": \"9f19fb5042a5de6da2f1ce183c5e224fd7802db408e9afdd598ee8174b2bce3f\",\n",
" \"used_deposits\": [\n",
" {\n",
" \"blocks_until_expiry\": \"14396\",\n",
" \"confirmation_height\": \"131\",\n",
" \"id\": \"ea6abbf0571c0ba82117ae9f2086614eacea8b2913dc0544b70c00de78353e71\",\n",
" \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n",
" \"state\": \"LOOPING_IN\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"2500000\"\n",
" }\n",
" ]\n",
"}\n"
]
}
},
{
"time_ms": 2358,
"kind": "stdin",
"data": {
"text": "y\n"
}
},
{
"time_ms": 2358,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticAddressLoopIn",
"event": "request",
"message_type": "looprpc.StaticAddressLoopInRequest",
"payload": {
"outpoints": [
"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0"
],
"max_swap_fee_satoshis": "1824",
"last_hop": "",
"label": "",
"initiator": "loop-cli",
"route_hints": [],
"private": false,
"payment_timeout_seconds": 60,
"amount": "500000",
"fast": false
}
}
},
{
"time_ms": 2824,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticAddressLoopIn",
"event": "response",
"message_type": "looprpc.StaticAddressLoopInResponse",
"payload": {
"swap_hash": "nxn7UEKl3m2i8c4YPF4iT9eALbQI6a/dWY7oF0srzj8=",
"state": "SignHtlcTx",
"amount": "2500000",
"htlc_cltv": 1136,
"quoted_swap_fee_satoshis": "1824",
"max_swap_fee_satoshis": "1824",
"initiation_height": 136,
"protocol_version": "V0",
"label": "",
"initiator": "loop-cli",
"payment_timeout_seconds": 60,
"used_deposits": [
{
"id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=",
"state": "LOOPING_IN",
"outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0",
"value": "2500000",
"confirmation_height": "131",
"blocks_until_expiry": "14396",
"swap_hash": ""
}
],
"swap_amount": "500000",
"change": "2000000",
"fast": false
}
}
},
{
"time_ms": 2826,
"kind": "exit",
"data": {}
}
]
}

View file

@ -0,0 +1,231 @@
{
"metadata": {
"args": [
"/home/user/bin/loop",
"static",
"in",
"--network",
"regtest",
"500000",
"--payment_timeout",
"30s",
"--last_hop",
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7",
"--force"
],
"env": {
"HOME": "/home/user"
},
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
"duration": 1078774451,
"clock_start_unix": 1769407086
},
"events": [
{
"time_ms": 4,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
"event": "request",
"message_type": "looprpc.ListStaticAddressDepositsRequest",
"payload": {
"state_filter": "DEPOSITED",
"outpoints": []
}
}
},
{
"time_ms": 179,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
"event": "response",
"message_type": "looprpc.ListStaticAddressDepositsResponse",
"payload": {
"filtered_deposits": [
{
"id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=",
"state": "DEPOSITED",
"outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0",
"value": "500000",
"confirmation_height": "0",
"blocks_until_expiry": "14400",
"swap_hash": ""
}
]
}
}
},
{
"time_ms": 180,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetLoopInQuote",
"event": "request",
"message_type": "looprpc.QuoteRequest",
"payload": {
"amt": "500000",
"conf_target": 0,
"external_htlc": false,
"swap_publication_deadline": "0",
"loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
"loop_in_route_hints": [],
"private": false,
"deposit_outpoints": [],
"asset_info": null,
"auto_select_deposits": true,
"fast": false
}
}
},
{
"time_ms": 500,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetLoopInQuote",
"event": "response",
"message_type": "looprpc.InQuoteResponse",
"payload": {
"swap_fee_sat": "1824",
"htlc_publish_fee_sat": "0",
"cltv_delta": 0,
"conf_target": 0,
"quoted_amt": "500000"
}
}
},
{
"time_ms": 500,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "request",
"message_type": "looprpc.StaticAddressSummaryRequest",
"payload": {}
}
},
{
"time_ms": 500,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/GetStaticAddressSummary",
"event": "response",
"message_type": "looprpc.StaticAddressSummaryResponse",
"payload": {
"static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
"relative_expiry_blocks": "14400",
"total_num_deposits": 1,
"value_unconfirmed_satoshis": "500000",
"value_deposited_satoshis": "0",
"value_expired_satoshis": "0",
"value_withdrawn_satoshis": "0",
"value_looped_in_satoshis": "0",
"value_htlc_timeout_sweeps_satoshis": "0",
"value_channels_opened": "0"
}
}
},
{
"time_ms": 500,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticAddressLoopIn",
"event": "request",
"message_type": "looprpc.StaticAddressLoopInRequest",
"payload": {
"outpoints": [],
"max_swap_fee_satoshis": "1824",
"last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
"label": "",
"initiator": "loop-cli",
"route_hints": [],
"private": false,
"payment_timeout_seconds": 30,
"amount": "500000",
"fast": false
}
}
},
{
"time_ms": 1078,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticAddressLoopIn",
"event": "response",
"message_type": "looprpc.StaticAddressLoopInResponse",
"payload": {
"swap_hash": "hDAjN0JANkGTlqt5ZN14uFsaSBqfHbc9tc3e5XwkQ+c=",
"state": "SignHtlcTx",
"amount": "500000",
"htlc_cltv": 1165,
"quoted_swap_fee_satoshis": "1824",
"max_swap_fee_satoshis": "1824",
"initiation_height": 165,
"protocol_version": "V0",
"label": "",
"initiator": "loop-cli",
"payment_timeout_seconds": 30,
"used_deposits": [
{
"id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=",
"state": "LOOPING_IN",
"outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0",
"value": "500000",
"confirmation_height": "0",
"blocks_until_expiry": "14400",
"swap_hash": ""
}
],
"swap_amount": "500000",
"change": "0",
"fast": false
}
}
},
{
"time_ms": 1078,
"kind": "stdout",
"data": {
"lines": [
"\n",
"WARNING: The following deposits are below the conservative 6-confirmation threshold:\n",
" - 9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0 (unconfirmed)\n",
"The swap payment for these deposits may wait for more confirmations depending on the server's confirmation-risk policy.\n",
"\n",
"{\n",
" \"amount\": \"500000\",\n",
" \"change\": \"0\",\n",
" \"fast\": false,\n",
" \"htlc_cltv\": 1165,\n",
" \"initiation_height\": 165,\n",
" \"initiator\": \"loop-cli\",\n",
" \"label\": \"\",\n",
" \"max_swap_fee_satoshis\": \"1824\",\n",
" \"payment_timeout_seconds\": 30,\n",
" \"protocol_version\": \"V0\",\n",
" \"quoted_swap_fee_satoshis\": \"1824\",\n",
" \"state\": \"SignHtlcTx\",\n",
" \"swap_amount\": \"500000\",\n",
" \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n",
" \"used_deposits\": [\n",
" {\n",
" \"blocks_until_expiry\": \"14400\",\n",
" \"confirmation_height\": \"0\",\n",
" \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n",
" \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n",
" \"state\": \"LOOPING_IN\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" }\n",
" ]\n",
"}\n"
]
}
},
{
"time_ms": 1078,
"kind": "exit",
"data": {}
}
]
}