diff --git a/cmd/loop/staticaddr.go b/cmd/loop/staticaddr.go index f4f72d7f..c973a002 100644 --- a/cmd/loop/staticaddr.go +++ b/cmd/loop/staticaddr.go @@ -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 " + diff --git a/cmd/loop/staticaddr_test.go b/cmd/loop/staticaddr_test.go new file mode 100644 index 00000000..2cc88ad6 --- /dev/null +++ b/cmd/loop/staticaddr_test.go @@ -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) +} diff --git a/cmd/loop/testdata/sessions/AGENTS.md b/cmd/loop/testdata/sessions/AGENTS.md index d4b27293..b5a69573 100644 --- a/cmd/loop/testdata/sessions/AGENTS.md +++ b/cmd/loop/testdata/sessions/AGENTS.md @@ -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) | diff --git a/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json b/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json index ea600c31..5acb8ddf 100644 --- a/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json +++ b/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json @@ -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", diff --git a/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json b/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json index 5a6b7c1c..b997322b 100644 --- a/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json +++ b/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json @@ -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", diff --git a/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json b/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json index 073bcb3b..d562f2ef 100644 --- a/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json +++ b/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json @@ -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", diff --git a/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json b/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json index 329b8e5b..02b5a3ac 100644 --- a/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json +++ b/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json @@ -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", diff --git a/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json b/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json index e108e966..f2994fde 100644 --- a/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json +++ b/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json @@ -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", diff --git a/cmd/loop/testdata/sessions/static-loop-in/25_loop-static-in-low-conf-utxo.json b/cmd/loop/testdata/sessions/static-loop-in/25_loop-static-in-low-conf-utxo.json new file mode 100644 index 00000000..3093fb9d --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/25_loop-static-in-low-conf-utxo.json @@ -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": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/26_loop-static-in-auto-unconfirmed.json b/cmd/loop/testdata/sessions/static-loop-in/26_loop-static-in-auto-unconfirmed.json new file mode 100644 index 00000000..dae0c0a0 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/26_loop-static-in-auto-unconfirmed.json @@ -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": {} + } + ] +}