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) {

View file

@ -5874,7 +5874,17 @@ type StaticAddressLoopInSwap struct {
// fees.
PaymentRequestAmountSatoshis int64 `protobuf:"varint,5,opt,name=payment_request_amount_satoshis,json=paymentRequestAmountSatoshis,proto3" json:"payment_request_amount_satoshis,omitempty"`
// The deposits that were used for this swap.
Deposits []*Deposit `protobuf:"bytes,6,rep,name=deposits,proto3" json:"deposits,omitempty"`
Deposits []*Deposit `protobuf:"bytes,6,rep,name=deposits,proto3" json:"deposits,omitempty"`
// Initiation time of the swap.
InitiationTime int64 `protobuf:"varint,7,opt,name=initiation_time,json=initiationTime,proto3" json:"initiation_time,omitempty"`
// Last update time of the swap.
LastUpdateTime int64 `protobuf:"varint,8,opt,name=last_update_time,json=lastUpdateTime,proto3" json:"last_update_time,omitempty"`
// Swap server cost.
CostServer int64 `protobuf:"varint,9,opt,name=cost_server,json=costServer,proto3" json:"cost_server,omitempty"`
// On-chain transaction cost.
CostOnchain int64 `protobuf:"varint,10,opt,name=cost_onchain,json=costOnchain,proto3" json:"cost_onchain,omitempty"`
// Off-chain routing fees.
CostOffchain int64 `protobuf:"varint,11,opt,name=cost_offchain,json=costOffchain,proto3" json:"cost_offchain,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -5951,6 +5961,41 @@ func (x *StaticAddressLoopInSwap) GetDeposits() []*Deposit {
return nil
}
func (x *StaticAddressLoopInSwap) GetInitiationTime() int64 {
if x != nil {
return x.InitiationTime
}
return 0
}
func (x *StaticAddressLoopInSwap) GetLastUpdateTime() int64 {
if x != nil {
return x.LastUpdateTime
}
return 0
}
func (x *StaticAddressLoopInSwap) GetCostServer() int64 {
if x != nil {
return x.CostServer
}
return 0
}
func (x *StaticAddressLoopInSwap) GetCostOnchain() int64 {
if x != nil {
return x.CostOnchain
}
return 0
}
func (x *StaticAddressLoopInSwap) GetCostOffchain() int64 {
if x != nil {
return x.CostOffchain
}
return 0
}
type StaticAddressLoopInRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The outpoints of the deposits to loop-in.
@ -6972,14 +7017,21 @@ const file_client_proto_rawDesc = "" +
"\x1dtotal_deposit_amount_satoshis\x18\x03 \x01(\x03R\x1atotalDepositAmountSatoshis\x12:\n" +
"\x19withdrawn_amount_satoshis\x18\x04 \x01(\x03R\x17withdrawnAmountSatoshis\x124\n" +
"\x16change_amount_satoshis\x18\x05 \x01(\x03R\x14changeAmountSatoshis\x12/\n" +
"\x13confirmation_height\x18\x06 \x01(\rR\x12confirmationHeight\"\xc7\x02\n" +
"\x13confirmation_height\x18\x06 \x01(\rR\x12confirmationHeight\"\x83\x04\n" +
"\x17StaticAddressLoopInSwap\x12\x1b\n" +
"\tswap_hash\x18\x01 \x01(\fR\bswapHash\x12+\n" +
"\x11deposit_outpoints\x18\x02 \x03(\tR\x10depositOutpoints\x12;\n" +
"\x05state\x18\x03 \x01(\x0e2%.looprpc.StaticAddressLoopInSwapStateR\x05state\x120\n" +
"\x14swap_amount_satoshis\x18\x04 \x01(\x03R\x12swapAmountSatoshis\x12E\n" +
"\x1fpayment_request_amount_satoshis\x18\x05 \x01(\x03R\x1cpaymentRequestAmountSatoshis\x12,\n" +
"\bdeposits\x18\x06 \x03(\v2\x10.looprpc.DepositR\bdeposits\"\xef\x02\n" +
"\bdeposits\x18\x06 \x03(\v2\x10.looprpc.DepositR\bdeposits\x12'\n" +
"\x0finitiation_time\x18\a \x01(\x03R\x0einitiationTime\x12(\n" +
"\x10last_update_time\x18\b \x01(\x03R\x0elastUpdateTime\x12\x1f\n" +
"\vcost_server\x18\t \x01(\x03R\n" +
"costServer\x12!\n" +
"\fcost_onchain\x18\n" +
" \x01(\x03R\vcostOnchain\x12#\n" +
"\rcost_offchain\x18\v \x01(\x03R\fcostOffchain\"\xef\x02\n" +
"\x1aStaticAddressLoopInRequest\x12\x1c\n" +
"\toutpoints\x18\x01 \x03(\tR\toutpoints\x121\n" +
"\x15max_swap_fee_satoshis\x18\x02 \x01(\x03R\x12maxSwapFeeSatoshis\x12\x19\n" +

View file

@ -2143,6 +2143,31 @@ message StaticAddressLoopInSwap {
The deposits that were used for this swap.
*/
repeated Deposit deposits = 6;
/*
Initiation time of the swap.
*/
int64 initiation_time = 7;
/*
Last update time of the swap.
*/
int64 last_update_time = 8;
/*
Swap server cost.
*/
int64 cost_server = 9;
/*
On-chain transaction cost.
*/
int64 cost_onchain = 10;
/*
Off-chain routing fees.
*/
int64 cost_offchain = 11;
}
enum StaticAddressLoopInSwapState {

View file

@ -2807,6 +2807,31 @@
"$ref": "#/definitions/looprpcDeposit"
},
"description": "The deposits that were used for this swap."
},
"initiation_time": {
"type": "string",
"format": "int64",
"description": "Initiation time of the swap."
},
"last_update_time": {
"type": "string",
"format": "int64",
"description": "Last update time of the swap."
},
"cost_server": {
"type": "string",
"format": "int64",
"description": "Swap server cost."
},
"cost_onchain": {
"type": "string",
"format": "int64",
"description": "On-chain transaction cost."
},
"cost_offchain": {
"type": "string",
"format": "int64",
"description": "Off-chain routing fees."
}
}
},