looprpc: populate static swap timing and costs

This commit is contained in:
Slyghtning 2026-05-29 15:32:31 +02:00
parent 5818b986ae
commit 51f984f7c0
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
5 changed files with 207 additions and 3 deletions

View file

@ -1966,6 +1966,10 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
var clientSwaps []*looprpc.StaticAddressLoopInSwap
for _, swp := range swaps {
if swp == nil {
continue
}
chainParams, err := s.network.ChainParams()
if err != nil {
return nil, fmt.Errorf("error getting chain params")
@ -2005,6 +2009,9 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
if swp.SelectedAmount > 0 {
swapAmount = swp.SelectedAmount
}
costServer := staticAddressLoopInSwapServerCost(swp)
initiationTime := staticAddressLoopInTimestamp(swp.InitiationTime)
lastUpdateTime := staticAddressLoopInTimestamp(swp.LastUpdateTime)
swap := &looprpc.StaticAddressLoopInSwap{
SwapHash: swp.SwapHash[:],
DepositOutpoints: swp.DepositOutpoints,
@ -2012,6 +2019,9 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
SwapAmountSatoshis: int64(swapAmount),
PaymentRequestAmountSatoshis: payReqAmount,
Deposits: protoDeposits,
InitiationTime: initiationTime,
LastUpdateTime: lastUpdateTime,
CostServer: costServer,
}
clientSwaps = append(clientSwaps, swap)
@ -2022,6 +2032,31 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
}, nil
}
func staticAddressLoopInTimestamp(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.UnixNano()
}
// staticAddressLoopInSwapServerCost returns the paid server cost using the
// legacy ListSwaps cost semantics. Static loop-ins currently only persist the
// accepted quote fee, and that fee is paid once the swap invoice settles.
// Timeout-path miner fees are not persisted, so cost_onchain and cost_offchain
// remain zero instead of returning an estimate as an actual cost.
func staticAddressLoopInSwapServerCost(swp *loopin.StaticAddressLoopIn) int64 {
switch swp.GetState() {
case loopin.PaymentReceived, loopin.Succeeded,
loopin.SucceededTransitioningFailed:
return int64(swp.QuotedSwapFee)
default:
return 0
}
}
// GetStaticAddressSummary returns a summary of static address-related
// information. Amongst deposits and withdrawals and their total values, it also
// includes a list of detailed deposit information filtered by their state.

View file

@ -20,6 +20,7 @@ import (
"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/lightninglabs/loop/staticaddr/script"
"github.com/lightninglabs/loop/swap"
mock_lnd "github.com/lightninglabs/loop/test"
@ -306,6 +307,72 @@ func TestSetLiquidityParamsRejectsStaticAutoloopWithoutExperimental(
require.ErrorContains(t, err, "--experimental")
}
// TestStaticAddressLoopInTimestamp verifies that zero timestamps are omitted
// from static loop-in responses instead of passing a zero time to UnixNano.
func TestStaticAddressLoopInTimestamp(t *testing.T) {
require.Zero(t, staticAddressLoopInTimestamp(time.Time{}))
timestamp := time.Unix(1_234, 567).UTC()
require.Equal(
t, timestamp.UnixNano(),
staticAddressLoopInTimestamp(timestamp),
)
}
// TestStaticAddressLoopInSwapServerCost verifies that static loop-in server
// costs are only reported once the invoice payment was received. Timeout path
// costs are not persisted today, so they are intentionally not estimated here.
func TestStaticAddressLoopInSwapServerCost(t *testing.T) {
const quoteFee = btcutil.Amount(1_234)
tests := []struct {
name string
state fsm.StateType
wantServer int64
}{
{
name: "pending before payment",
state: loopin.SignHtlcTx,
},
{
name: "payment received",
state: loopin.PaymentReceived,
wantServer: int64(quoteFee),
},
{
name: "succeeded",
state: loopin.Succeeded,
wantServer: int64(quoteFee),
},
{
name: "succeeded transition failed",
state: loopin.SucceededTransitioningFailed,
wantServer: int64(quoteFee),
},
{
name: "timeout swept",
state: loopin.HtlcTimeoutSwept,
},
{
name: "failed",
state: loopin.Failed,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
swap := &loopin.StaticAddressLoopIn{
QuotedSwapFee: quoteFee,
}
swap.SetState(test.state)
costServer := staticAddressLoopInSwapServerCost(swap)
require.Equal(t, test.wantServer, costServer)
})
}
}
// TestRPCAutoloopReasonStaticLoopInNoCandidate verifies that the new planner
// reason is exposed over rpc.
func TestRPCAutoloopReasonStaticLoopInNoCandidate(t *testing.T) {