Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1abe617
staticaddr/deposit: track unconfirmed deposits
hieblmi Jul 8, 2026
ac12d25
staticaddr/deposit: replay startup block after recovery
hieblmi Jul 8, 2026
e4bcc94
staticaddr/deposit: reconcile active deposits with wallet
hieblmi Jul 8, 2026
e8dd3aa
staticaddr: expose tracked deposit availability
hieblmi Jul 8, 2026
557ba99
staticaddr: require confirmed deposits for withdraws and channel opens
hieblmi Jul 8, 2026
0223caa
staticaddr/loopin: treat unconfirmed deposits as swappable
hieblmi Jul 8, 2026
1c89ff8
staticaddr/loopin: account for autoloop deposit expiry
hieblmi Jul 6, 2026
dc7da41
staticaddr: refresh deposits before spend selection
hieblmi Jul 8, 2026
1d935c6
loopd: filter static deposit views through active set
hieblmi Jul 8, 2026
ef78c85
staticaddr/loopin: cancel signing for unavailable deposits
hieblmi Jul 8, 2026
da96842
notifications: fan out static loop-in risk decisions
hieblmi Jul 8, 2026
b0f43bb
staticaddr/loopin: wait for risk decisions
hieblmi Jul 8, 2026
491fddb
loopdb: persist static loop-in risk decisions
hieblmi Jul 8, 2026
e53bb67
notifications: persist static loop-in risk decisions
hieblmi Jul 8, 2026
3e2b945
loopd: wire static risk decision persistence
hieblmi Jul 8, 2026
f01a1f0
staticaddr/loopin: clean up deposits after unpaid htlc timeout
hieblmi Jul 8, 2026
6593afc
staticaddr/loopin: recover risk-decision deadlines
hieblmi Jul 8, 2026
332aab7
staticaddr/loopin: add risk decision watcher
hieblmi Jul 9, 2026
6927c67
staticaddr/loopin: use risk decision watcher
hieblmi Jul 9, 2026
f338c9e
cmd/loop: warn for low-confirmation static deposits
hieblmi Jun 26, 2026
4cdcc53
swap: reserve multi-address key families
hieblmi Jul 10, 2026
564e238
loopdb: persist deposit address ownership
hieblmi Jul 10, 2026
cfbf3fa
staticaddr/address: activate derived addresses
hieblmi Jul 10, 2026
cab6768
staticaddr/deposit: discover all active addresses
hieblmi Jul 10, 2026
6cb1e0b
staticaddr/deposit: sweep with owning address keys
hieblmi Jul 10, 2026
2939abf
staticaddr: sign with per-deposit address keys
hieblmi Jul 10, 2026
7c8cbe7
swapserverrpc: add multi-address proofs
hieblmi May 6, 2026
c336bfc
staticaddr/loopin: send per-deposit address proofs
hieblmi Jul 10, 2026
639482e
staticaddr/withdraw: send per-deposit address proofs
hieblmi Jul 10, 2026
91fe4b7
staticaddr/deposit: restore owning address parameters
hieblmi May 6, 2026
6f3b7c5
staticaddr/loopin: use generated change addresses
hieblmi Jul 10, 2026
9a70b02
staticaddr/withdraw: use generated change addresses
hieblmi Jul 10, 2026
d4b116a
staticaddr: fund new addresses with sendcoins
hieblmi May 8, 2026
00336b2
staticaddr: expose addresses in deposit listings
hieblmi May 11, 2026
d191f18
backup: add encrypted L402 static address backups
hieblmi Jul 9, 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
692 changes: 692 additions & 0 deletions backup/service.go

Large diffs are not rendered by default.

762 changes: 762 additions & 0 deletions backup/service_test.go

Large diffs are not rendered by default.

492 changes: 478 additions & 14 deletions cmd/loop/staticaddr.go

Large diffs are not rendered by default.

251 changes: 251 additions & 0 deletions cmd/loop/staticaddr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package main

import (
"context"
"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/address"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/loopin"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)

func TestStaticAddressDepositRequestAllowsNoUtxos(t *testing.T) {
t.Parallel()

var req *looprpc.NewStaticAddressRequest
cmd := &cli.Command{
Name: "deposit",
Flags: depositStaticAddressCommand.Flags,
Action: func(_ context.Context, cmd *cli.Command) error {
var err error
req, err = staticAddressDepositRequest(
cmd, "bcrt1ptestaddress",
)

return err
},
}

err := cmd.Run(context.Background(), []string{
"deposit", "--amt", "1000000",
})
require.NoError(t, err)
require.Equal(t, "bcrt1ptestaddress", req.GetSendCoinsRequest().Addr)
require.EqualValues(t, 1_000_000, req.GetSendCoinsRequest().Amount)
require.Empty(t, req.GetSendCoinsRequest().Outpoints)
}

// 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,
AddressParams: &address.Parameters{
Expiry: csvExpiry,
},
})
}

cliSelected := autoSelectedWarningOutpoints(
rpcDeposits, targetAmount,
)

loopInSelected, err := loopin.SelectDeposits(
btcutil.Amount(targetAmount), loopInDeposits, 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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
" \"id\": \"bb7f050df0b7c3e1fe61010e10ad45e30ddf7acd301fa6e05a2ddb825b5c2efb\",\n",
" \"outpoint\": \"56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0\",\n",
" \"state\": \"WITHDRAWING\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" }\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
" \"id\": \"68262a104c9ec325de6bec37b8e31bd875bbd2f5f0b9ce2da20cf0bd636fc448\",\n",
" \"outpoint\": \"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -101,6 +102,7 @@
" \"id\": \"86b5e2cdf9694c8e7398e42afde109766d7cd2142203905ba63fbd0eb1370ef3\",\n",
" \"outpoint\": \"bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -110,6 +112,7 @@
" \"id\": \"6c290f7536ea5097946afffac6a69906a26d775823ebbacedfe6f2d69c0745e4\",\n",
" \"outpoint\": \"5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -119,6 +122,7 @@
" \"id\": \"0182b4d895b1c467290ae7b5c6c42ff76b2a4225807a94211c973170d5a883eb\",\n",
" \"outpoint\": \"7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" }\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
" \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n",
" \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n",
" \"state\": \"LOOPED_IN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n",
" \"value\": \"500000\"\n",
" }\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
" \"id\": \"68262a104c9ec325de6bec37b8e31bd875bbd2f5f0b9ce2da20cf0bd636fc448\",\n",
" \"outpoint\": \"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -119,6 +120,7 @@
" \"id\": \"86b5e2cdf9694c8e7398e42afde109766d7cd2142203905ba63fbd0eb1370ef3\",\n",
" \"outpoint\": \"bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -128,6 +130,7 @@
" \"id\": \"6c290f7536ea5097946afffac6a69906a26d775823ebbacedfe6f2d69c0745e4\",\n",
" \"outpoint\": \"5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -137,6 +140,7 @@
" \"id\": \"0182b4d895b1c467290ae7b5c6c42ff76b2a4225807a94211c973170d5a883eb\",\n",
" \"outpoint\": \"7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0\",\n",
" \"state\": \"WITHDRAWN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -146,6 +150,7 @@
" \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n",
" \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n",
" \"state\": \"LOOPED_IN\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -155,6 +160,7 @@
" \"id\": \"bb7f050df0b7c3e1fe61010e10ad45e30ddf7acd301fa6e05a2ddb825b5c2efb\",\n",
" \"outpoint\": \"56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0\",\n",
" \"state\": \"WITHDRAWING\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" }\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
" \"id\": \"7a7cbe9b90f23d47aa92eb10a9d323f7ace6e9eaab5b77379c63422c15da19c8\",\n",
" \"outpoint\": \"0e70673c1da3343648c26f779555346f30d235314838b1160826d0d5c29b4fba:1\",\n",
" \"state\": \"CHANNEL_PUBLISHED\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"500000\"\n",
" },\n",
Expand All @@ -86,6 +87,7 @@
" \"id\": \"ff9a43b2082f906a2e2758934220c4ce32393eb2823b292517ae081e16daded9\",\n",
" \"outpoint\": \"d2d6e50f157f0d31b8688a4af4f064edf3454714e92369b2c8c4d82477edbaca:0\",\n",
" \"state\": \"CHANNEL_PUBLISHED\",\n",
" \"static_address\": \"\",\n",
" \"swap_hash\": \"\",\n",
" \"value\": \"1000000\"\n",
" }\n",
Expand Down
Loading
Loading