Check the originally selected deposit outpoints before signing a static
loop-in HTLC transaction. If any selected outpoint is no longer
available, cancel the swap invoice and fail the signing action instead
of producing signatures for stale inputs.
Wire the lnd-backed checker through loopd and make invoice-monitoring
handle closed subscription channels without spinning.
Add a TxOutChecker interface for testing whether an original deposit
outpoint is still available before a static loop-in signs its HTLC
transaction.
Implement the checker using lnd's wallet transaction view so wallet-known
spends, including mempool spends when requested, cause the outpoint to be
reported unavailable.
Store an independent snapshot of the deposit outpoints selected for a
static loop-in. This keeps recovered swaps tied to the original funding
outputs even if the deposit records later reflect replacement or
confirmation changes.
Avoid recovering an empty database outpoint string as a synthetic
outpoint entry, and cover recovery of the stored snapshot separately
from the current deposit metadata.
Treat lnd wallet view as the source of spendable static-address
outputs while keeping historical deposit records in the DB. Reconcile
active FSMs against the current wallet view and reactivate known
deposits when their outpoints are visible again.
Refresh deposits before selection, withdrawal, loop-in, and channel-open
paths, and filter list and summary responses through the live active set
so stale Deposited records are not exposed as available funds.
Treat unconfirmed static-address deposits as swappable because their CSV
timeout has not started yet. Keep confirmed deposits ahead of
unconfirmed ones during automatic selection, then sort by value and
remaining lifetime within each confirmation group.
Share the expiry calculation with the dynamic-programming selector so
unconfirmed deposits do not look like the earliest-expiring candidates.
Build list and summary responses from tracked deposit records instead of
raw wallet UTXOs so RPC clients see the manager availability state.
Split unconfirmed value from confirmed deposited value in summaries.
Keep withdrawal and channel-open flows on confirmed inputs by rejecting
unconfirmed selected deposits in those paths.
Retain static-address deposits as soon as lnd reports the UTXO,
even when the output is still unconfirmed. Store the first
confirmation height once the output confirms.
Replay the startup block to recovered deposit FSMs so expiry handling
can run immediately after restart. Derive confirmation heights from a
stable wallet view because lnd reports confirmation counts.
Log the static address loop-in HTLC weight, fee rates, computed fees, and configured fee caps before fee validation. This keeps the existing fee guard behavior intact while making validation failures easier to diagnose.
LND v0.21 added the production TAPROOT commitment type while
SIMPLE_TAPROOT remains available as the legacy enum.
The static open-channel CLI previously used "taproot" for
SIMPLE_TAPROOT. Keep both choices available by renaming that legacy
spelling to "simple-taproot" and mapping "taproot" to TAPROOT. This
makes the CLI spelling match the channel type it requests while still
leaving an explicit path for users that need SIMPLE_TAPROOT.
Enable staticcheck's SA1019 check in golangci-lint so deprecated
identifiers are caught in CI.
Replace deprecated standard library and bbolt APIs with their current
equivalents. Keep intentional compatibility reads and writes of
deprecated Loop RPC fields behind narrow nolint annotations, because
older clients and persisted liquidity parameters still depend on those
fields.
The cost cleanup migration pages through LND payments only to build
a payment-hash to fee map. It does not inspect HTLC attempts,
routes, or per-hop data; pagination still uses the top-level index
offsets returned by ListPayments.
Setting OmitHops is safe for this migration because LND only strips
hop-level route data from HTLC attempts, while preserving the top-level
payment fields the migration reads: hash, fee, and response offsets.
This reduces response size and query cost for nodes with many or large
MPP payments without changing the calculated swap costs.
The migration test records the mocked ListPayments requests and asserts
that OmitHops is set.
LND v0.21 exposes CommitmentType_TAPROOT as the production taproot
channel commitment type, while SIMPLE_TAPROOT remains a legacy taproot
enum. Static address channel opens previously rejected TAPROOT and only
classified SIMPLE_TAPROOT as a taproot output for fee and weight
estimates.
Accept TAPROOT in the static address open-channel validator and keep
accepting SIMPLE_TAPROOT for compatibility. Treat both taproot
commitment enums as P2TR outputs for deposit-selection and withdrawal
fee estimates. Callers using the production enum then get the same
weight accounting as the legacy taproot enum.
This does not change the CLI mapping for user-facing
channel_type=taproot. It only makes the static address path compatible
with callers that already send LND production taproot commitment type.
Required notification fanout should not block the manager lock, but subscribers still need ordered delivery once brief backpressure clears. Sending must-deliver notifications directly can couple manager progress to subscriber receive timing, while queueing optional reservation notifications would contradict their best-effort delivery semantics.
Add bounded per-subscriber queues for must-deliver notifications, let those queues own channel shutdown instead of relying on recover for closed-channel sends, keep reservation fanout best-effort, and cover queued delivery, queue cleanup, and capacity drops in manager tests.
A slow optional notification subscriber must not stall the manager lock, while recovery and sweep work requests still need reliable local delivery to keep daemon progress deterministic. Treating all subscribers as blocking makes best-effort fanout a backpressure source for unrelated required work.
Make reservation fanout nonblocking for slow subscribers, keep recovery and sweep notifications on a required cancellation-aware delivery path, and cover both slow best-effort subscribers and required subscribers in manager tests.
The final-state query previously formatted the state list with braces, which broke the comma-delimited match for both boundary entries: the first state was compared with a leading brace and the last state with a trailing brace. Final-state callers need swaps at both ends of the list, including HtlcTimeoutSwept and Failed, to remain visible once they reach a terminal status.
Return the final-state list as plain comma-separated state names, update the store comment to match the query format, and extend the store test so it inserts and retrieves swaps in the first final state, a middle final state, and the last final state without relying on result order.
Final deposit states should not stall while deposit locks are held, because a blocked manager receive loop can otherwise hold up the deposit FSM; if shutdown happens before notification delivery, startup recovery can still resume from the final state.
Send finalization notifications from a goroutine so final states are recorded without waiting on the manager receive loop, and add tests for blocked manager delivery and shutdown races.
A static address loop-in that creates an invoice but then fails before storage cannot be recovered after restart, and persisted error paths that unlock deposits should not leave the swap invoice live either. The payment-deadline path also needs to report the real deposit unlock error instead of checking the FSM event returned by UnlockDepositsAction, because that action returns OnError on both success and failure.
Cancel private swap invoices on pre-storage init failures, monitor timeouts, and stored unlock paths using a detached timeout-limited helper. Factor deposit unlocking into an error-returning helper so deadline handling can log transition failures, and add coverage for early-init cancellation plus generic unlock invoice cleanup and error propagation.
LoopMinRequiredLndVersion was 0.17.0, a value that only ever tracked the
go.mod lnd dependency rounded down and was never updated as the client
started depending on newer lnd RPC APIs. The client today uses RPC
fields that do not exist in 0.17.0:
- routerrpc.SendPaymentRequest.first_hop_custom_records and
lnrpc.Route.custom_channel_data, used by asset loop outs in
loopout.go: both added in lnd v0.18.4-beta.
- walletrpc.EstimateFeeResponse.min_relay_fee_sat_per_kw, read by the
sweep batcher fee floor via lndclient WalletKit.MinRelayFee
(sweepbatcher/, loopd/sweep_htlc.go): added in lnd v0.18.3-beta. On
older lnd it silently decodes to 0, disabling the min-relay floor.
Raise the floor to the highest of these (v0.18.4-beta) so loopd fails
fast at startup rather than misbehaving at runtime, and document in
AGENTS.md the rule to keep this value pinned to the lnd APIs the client
actually uses instead of tracking go.mod.
lndclient now forwards route hints through AddInvoice. These tests
assert that Loop preserves explicit hints on generated loop-in invoices.
Remove stale comments that described the old wrapper behavior as
dropping hints.
Loop no longer pays invoices through LightningClient.PayInvoice.
Production payment paths use the router client wrappers. lndclient now
also implements PayInvoice on top of router payment APIs, so the old
SendPaymentChannel mock path is no longer exercised.
Remove the dead PayInvoice mock, its SendPaymentChannel plumbing, and
the loop-in resume assertion that watched that channel.
Add NotificationTypeHtlcConfirmed and SubscribeHtlcConfirmed so consumers
can subscribe to HTLC-confirmed notifications. Also add dispatch in
handleNotification to forward htlc_confirmed messages to subscribers.
Add ServerHtlcConfirmedNotification message to SubscribeNotificationsResponse
oneof. This new notification type allows the server to inform the client that
a loop out HTLC has been confirmed on-chain, including the confirmed outpoint.
Loop-in and loop-out responses carry compressed server public keys that are copied into fixed-size fields and later used for HTLC construction. Validate the length and parse each compressed key before storing it, and validate the MuSig2 loop-in receiver internal key as well.
This turns short or unparsable server keys into explicit errors instead of silently zero-padding short responses or accepting an invalid internal key. Update root test mocks to return size-correct MuSig2 signing data under the stricter checks.
The cooperative batch sweep path receives a server nonce and partial signature before constructing a keyspend witness. Validate both byte slice lengths before registering the nonce or combining signatures, so malformed server responses fail explicitly instead of being zero-padded into fixed-size MuSig2 buffers.
Update batcher test helpers to return size-correct placeholder signing data under the stricter validation.
Server-supplied nonces and partial signatures are consumed by the static address loop-in and withdrawal MuSig2 signing paths. Reject nil signing info, wrong nonce lengths, and wrong partial signature lengths before registering nonces or combining signatures, so malformed responses cannot be silently zero-padded into signing attempts.
Add withdrawal coverage for nil and malformed server signing data.