Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions swapwallet/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,10 @@ func (h *history) listOnchain(ctx context.Context,

txs := make([]*walletdkrpc.OnchainTx, 0, len(resp.GetTransactions()))
for _, t := range resp.GetTransactions() {
if isInternalLedgerSweep(t) {
continue
}

txs = append(txs, onchainTxFromLedgerRow(t))
}

Expand Down Expand Up @@ -1548,6 +1552,17 @@ func classifyLedgerRow(t *daemonrpc.TransactionHistoryEntry) (
return walletdkrpc.EntryKind_ENTRY_KIND_DEPOSIT, +1, true

case "sweep":
// Ledger-sourced sweep rows are internal fee-clearing legs:
// onchain_fee_paid for unilateral exits and
// boarding_sweep_fee_paid for boarding sweeps. User-facing
// sweep rows come from domain sources such as
// boarding_sweep, which carry the aggregate amount, miner fee,
// txid, and terminal sweep status.
if isInternalLedgerSweep(t) {
return walletdkrpc.EntryKind_ENTRY_KIND_UNSPECIFIED,
0, false
}

return walletdkrpc.EntryKind_ENTRY_KIND_EXIT, -1, true

case "oor":
Expand Down Expand Up @@ -1576,6 +1591,12 @@ func classifyLedgerRow(t *daemonrpc.TransactionHistoryEntry) (
}
}

// isInternalLedgerSweep returns true for ledger-sourced sweep rows that
// represent internal fee-clearing legs rather than user-facing exits.
func isInternalLedgerSweep(t *daemonrpc.TransactionHistoryEntry) bool {
return t.GetSource() == "ledger" && t.GetType() == "sweep"
}

// statusFromLedgerConfirmation maps the ledger row's confirmation_status
// string onto the flat wallet status.
func statusFromLedgerConfirmation(s string) walletdkrpc.EntryStatus {
Expand Down
80 changes: 80 additions & 0 deletions swapwallet/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,86 @@ func TestHistoryListMergesSwapAndLedgerSources(t *testing.T) {
)
}

// TestHistoryHidesBoardingSweepFeeLedgerLeg verifies that the internal
// boarding-sweep chain-cost ledger leg does not surface as a second pending
// EXIT. The canonical activity row is the boarding_sweeps row; the
// boarding_sweep_fee_paid row exists only to balance wallet_clearing.
func TestHistoryHidesBoardingSweepFeeLedgerLeg(t *testing.T) {
t.Parallel()

h, swap, rpc := newHistoryFixture(t)
swap.listSwapsResp = &swapclientrpc.ListSwapsResponse{}
rpc.listTxResp = &daemonrpc.ListTransactionsResponse{
Transactions: []*daemonrpc.TransactionHistoryEntry{
{
Source: "boarding_sweep",
Type: "sweep",
Subtype: "confirmed",
ConfirmationStatus: "confirmed",
AmountSat: 50_000,
FeeSat: 139,
Txid: "sweep-txid",
CreatedAtUnixS: 200,
},
{
Source: "ledger",
Type: "sweep",
Subtype: ledger.EventBoardingSweepFeePaid,
ConfirmationStatus: "recorded",
AmountSat: 469,
FeeSat: 0,
EntryId: 6,
CreatedAtUnixS: 201,
},
},
}

resp, err := h.List(t.Context(), &walletdkrpc.ListRequest{})
require.NoError(t, err)

entries := resp.GetActivity().GetEntries()
require.Len(t, entries, 1)
require.Equal(t, "sweep-txid", entries[0].GetId())
require.Equal(
t, walletdkrpc.EntryKind_ENTRY_KIND_EXIT, entries[0].GetKind(),
)
require.Equal(
t, walletdkrpc.EntryStatus_ENTRY_STATUS_COMPLETE,
entries[0].GetStatus(),
)
require.Equal(t, int64(-50_000), entries[0].GetAmountSat())
require.Equal(t, int64(139), entries[0].GetFeeSat())
}

// TestHistoryHidesUnilateralExitFeeLedgerLeg verifies that unilateral-exit
// fee accounting does not surface as a pending EXIT. The ledger's
// onchain_fee_paid row is internal bookkeeping and has no chain txid or
// confirmation lifecycle of its own.
func TestHistoryHidesUnilateralExitFeeLedgerLeg(t *testing.T) {
t.Parallel()

h, swap, rpc := newHistoryFixture(t)
swap.listSwapsResp = &swapclientrpc.ListSwapsResponse{}
rpc.listTxResp = &daemonrpc.ListTransactionsResponse{
Transactions: []*daemonrpc.TransactionHistoryEntry{
{
Source: "ledger",
Type: "sweep",
Subtype: ledger.EventOnchainFeePaid,
ConfirmationStatus: "recorded",
AmountSat: 812,
FeeSat: 0,
EntryId: 9,
CreatedAtUnixS: 300,
},
},
}

resp, err := h.List(t.Context(), &walletdkrpc.ListRequest{})
require.NoError(t, err)
require.Empty(t, resp.GetActivity().GetEntries())
}

// TestHistoryPendingFilterDropsTerminal confirms pending_only=true
// drops COMPLETE and FAILED rows from both sources.
func TestHistoryPendingFilterDropsTerminal(t *testing.T) {
Expand Down
19 changes: 19 additions & 0 deletions swapwallet/list_view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/lightninglabs/darepo-client/daemonrpc"
"github.com/lightninglabs/darepo-client/ledger"
"github.com/lightninglabs/darepo-client/rpc/walletdkrpc"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -158,6 +159,24 @@ func TestListViewOnchainFlattensLedgerRows(t *testing.T) {
CreatedAtUnixS: 600,
Description: "boarding sweep",
},
{
Source: "ledger",
Type: "sweep",
Subtype: ledger.EventBoardingSweepFeePaid,
ConfirmationStatus: "recorded",
AmountSat: 469,
CreatedAtUnixS: 601,
Description: "internal boarding sweep fee",
},
{
Source: "ledger",
Type: "sweep",
Subtype: ledger.EventOnchainFeePaid,
ConfirmationStatus: "recorded",
AmountSat: 812,
CreatedAtUnixS: 602,
Description: "internal exit fee",
},
},
HasMore: true,
}
Expand Down
Loading