Skip to content

staticaddr: populate static Loop-In on-chain costs from deposit fees #1174

Description

@hieblmi

Problem

Static Loop-In responses expose cost_onchain, but the value is currently zero for every swap.

The current implementation only derives cost_server from the accepted quote after the invoice is paid. It does not populate an on-chain cost in either:

  • ListStaticAddressSwaps
  • the generic ListSwaps / monitor representation

This is also reflected in TestListStaticAddressSwapsPopulatesTimingAndCosts, which currently asserts that CostOnchain is zero.

For cost accounting equivalent to a legacy Loop-In, the fee paid by the transaction that funds the static-address deposit is the client-side on-chain cost. It is analogous to the legacy Loop-In HTLC funding transaction fee, even though the static-address funding transaction happens before the swap is initiated.

Expected behavior

When a static Loop-In consumes one or more deposits:

  • report the funding transaction fee as cost_onchain;
  • aggregate the fees for all selected deposits;
  • deduplicate by funding transaction ID, so two selected outputs from the same transaction do not count the full transaction fee twice;
  • populate the value consistently in ListStaticAddressSwaps, generic ListSwaps, and monitor updates;
  • persist the actual cost so terminal autoloop budget accounting can use it instead of estimates.

For deposits funded by an external wallet, Loop may not know the sender's fee. Those should be treated explicitly as unavailable/client-external rather than being confused with a known zero-fee transaction.

Straightforward implementation

The existing lnd client already provides the required data:

txs, err := lndClient.ListTransactions(ctx, startHeight, -1)

Each lndclient.Transaction includes TxHash and Fee.

An initial implementation can:

  1. Parse the selected deposit outpoints and collect their unique funding transaction hashes.
  2. Fetch wallet transactions once and build a txid -> fee map.
  3. Sum each unique funding transaction fee once.
  4. Store the result in the existing base swaps.onchain_cost column. Static Loop-Ins already have a row in swaps, so a new cost column should not be necessary.
  5. Add the persisted cost to StaticAddressLoopIn when reconstructing it from the joined SQL row.
  6. Use that cost in both static-specific and generic RPC representations.

The common loop static new --sendcoins flow normally creates one static deposit output, so attributing the full wallet transaction fee is straightforward. If a single transaction creates multiple static deposits that can be consumed by different swaps, the implementation should use a deterministic per-deposit allocation or a transaction-level accounting record so the fee is not charged in full to multiple swaps.

The cost should be attached when the deposits are definitively consumed (successful Loop-In or a confirmed HTLC timeout path). A failed attempt that unlocks and reuses the same deposits should not cause the original funding fee to be counted again on a later swap.

Acceptance criteria

  • A single-deposit static Loop-In reports its known wallet funding fee in cost_onchain.
  • A swap using deposits from different funding transactions reports the sum of their fees.
  • Multiple selected deposits from the same funding transaction count that transaction fee only once.
  • Failed attempts that release reusable deposits do not cause duplicate accounting.
  • Static-specific and generic swap APIs return the same persisted on-chain cost.
  • Tests cover single-deposit, multiple-transaction, same-transaction/multiple-output, unknown external funding, and restart behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions