Treat closed invoice update channels as terminal for the monitor loop.
This avoids spinning when lnd closes the subscription after invoice cancellation or shutdown.
Reject duplicate static-address deposit outpoints before creating
withdrawal, loop-in, or channel-open requests.
Use the shared outpoint duplicate helper so each flow reports the
same input validation failure.
Return early when block notifications reach deposits that already
moved into a terminal state.
This prevents final deposits from retrying expiry handling after
recovery or while their FSM is still draining block updates.
Keep deposits locked when the server publishes the loop-in HTLC
without paying the invoice.
This lets the client sweep through the HTLC timeout path instead
of making the same outputs available for another action.
Recorded session replay already normalizes timestamps before
comparing CLI output, but the time.String timestamp regex only
matched alphabetic zone names like UTC and EST. Some systems
render the same offset with a numeric zone name, for example
-0300 -03, which left the publication deadline unnormalized
and caused fixture comparisons to fail.
Match numeric zone names as well, and parse the timestamp using
the explicit numeric offset instead of the display zone name. Add a
regression test covering RFC3339, alphabetic time.String zones, and
numeric time.String zones.
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.