You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Config: expose negotiate_anchor_zero_fee_commitments so integrators can opt into 0FC channels #987
default_user_config sets only negotiate_anchors_zero_fee_htlc_tx (src/config.rs#L421 at current main); channel_handshake_config.negotiate_anchor_zero_fee_commitments stays at LDK's default false, and no Config/ChannelConfig surface exposes it — so two ldk-node peers can't open a zero-fee-commitment channel even experimentally, despite the pinned LDK carrying the full 0FC machinery (feerate-0 commitment, keyless P2A anchor, TRUC CPFP child).
Why we need it (not hypothetically)
We integrate ldk-node via orange-sdk in a sweep-everything mobile wallet (Phoenix model: on-chain balance ~0 at steady state). Verified on-device (Mutinynet, 2026-07-11): with the LSP offline, a force-close cannot confirm —
the legacy-anchor commitment is built at the 253 sat/kW floor and never relays alone;
no CPFP child can be built: with the LSP in trusted_peers_no_reserve the bump event is skipped by design, and de-trusting re-introduces the per-channel reserve — the exact thing a sweep-everything wallet can't hold (and without a reserve, coin selection finds no confirmed UTXO and aborts);
nothing external can rescue it: the legacy anchor is keyed, and the anyone-can-spend branch needs 16 confirmations of the very tx that can't confirm.
0FC + P2A resolves this class without a reserve: keyless anchor, bring-your-own-fees child, pure L1 recovery even with the counterparty dead. That makes the handshake flag the first gate to a real fix for us, not a nice-to-have — the only current workaround is a fork.
Ask
A config surface that sets negotiate_anchor_zero_fee_commitments (opt-in, default off — wherever it fits your config layering). If the actual gate is that 0FC needs more than the handshake flag (e.g. the reserve/test assumptions in #875), we'd genuinely like to know what the real blocker list is.
We control both channel ends (our LSP is ldk-node too) and run staging on Mutinynet, which already relays TRUC + ephemeral anchors — so we can validate end-to-end and publish before/after results as soon as a knob exists.
Related: the trusted-peer half of this failure is filed as lightningdevkit/orange-sdk#95; the rescue-tool export surface is a separate ask (one thread per topic).
Closing for now — we're first researching a client-side alternative that needs no changes here: a recovery tool that works from the user's own encrypted state backup (seed + channel state, held as a cloud/file backup) to complete the hostile/non-cooperative exit out of band. If that path proves unworkable we may ask to reopen this with our findings. Thanks for the consideration.
Thanks for the ping @tankyleo — great to see this land, and enable_zero_fee_commitments on AnchorChannelsConfig is exactly the surface we asked for. Taking up the standing offer from the issue body: we'll validate end-to-end on Mutinynet (we control both channel ends; TRUC + ephemeral anchor relay is available there) against the exact scenario that motivated this — counterparty offline, force-close, feerate-0 commitment + keyless P2A fee child confirming with no pre-funded reserve. Will post before/after results here.
Validated #660 end-to-end on Mutinynet — thanks again for the ping, @tankyleo. Two ldk-node main (056447c) nodes, esplora chain source, enable_zero_fee_commitments = true.
0FC negotiates and force-closes confirm as a package. A funded party force-closing with the counterparty offline: commitment 8fceb534…aac9 (fee 0, weight 648, P2A anchor + to_local) + child 6d5ee6cd…a448 (anchor + one wallet UTXO, 404 sat) via submitpackage → "success", block 3269710.
The result that matters for a sweep-everything wallet: a zero-on-chain user can recover from an unresponsive-LSP force-close using only a backup + any fee UTXO. We modelled the real recovery: a 0FC channel where the LSP pushed the user 40k and the user holds 0 on-chain; snapshot the user's channel state (seed + monitor), kill the LSP permanently, then restore that state onto a fresh wallet and force-close with the LSP still down. The restored wallet broadcast the fee-0 commitment and CPFP'd the keyless P2A anchor with a 25k user-provided UTXO as a package (submitpackage → "success"); commitment 2c055014 (+ child 786e074f, 692 sat) confirmed block 3270210, with the user's 40k landing in to_local. Reproduced on a second independent channel (commitment b2895207, block 3270239). This is exactly the unilateral-exit path that was previously blocked — 0FC's keyless anchor is what makes it work without a per-channel reserve or any keyed-anchor tooling.
One note for reproducers (no ask): a zero-on-chain node rejects an inbound 0FC channel on the anchor reserve (Available: 0/25000sats) and silently falls back to option_static_remotekey; trusted_peers_no_reserve is what lets it negotiate 0FC. Relatedly, a node that has its counterparty in trusted_peers_no_reserve doesn't broadcast its own force-close while that peer is offline (the BumpTransactionEvent::ChannelClose handler returns early for trusted peers — event.rs); we see the same for legacy anchors and 0FC. That's consistent with what opting into no-reserve means, and the recovery path above routes around it entirely (the restoring wallet doesn't trust the dead peer), so no change requested — just flagging in case the 0FC interaction is worth a doc note.
Net: #660 delivers what we needed — pending the knob surfacing through orange-sdk, the unilateral-exit gap for our wallet class is closed. Thanks to everyone who worked it.
Activity
hash-money commentedon Jul 16, 2026
Closing for now — we're first researching a client-side alternative that needs no changes here: a recovery tool that works from the user's own encrypted state backup (seed + channel state, held as a cloud/file backup) to complete the hostile/non-cooperative exit out of band. If that path proves unworkable we may ask to reopen this with our findings. Thanks for the consideration.
tankyleo commentedon Jul 17, 2026
@hash-money We just merged zero-fee commitments in LDK-node, see #660
hash-money commentedon Jul 17, 2026
Thanks for the ping @tankyleo — great to see this land, and
enable_zero_fee_commitmentsonAnchorChannelsConfigis exactly the surface we asked for. Taking up the standing offer from the issue body: we'll validate end-to-end on Mutinynet (we control both channel ends; TRUC + ephemeral anchor relay is available there) against the exact scenario that motivated this — counterparty offline, force-close, feerate-0 commitment + keyless P2A fee child confirming with no pre-funded reserve. Will post before/after results here.hash-money commentedon Jul 17, 2026
Validated #660 end-to-end on Mutinynet — thanks again for the ping, @tankyleo. Two ldk-node
main(056447c) nodes, esplora chain source,enable_zero_fee_commitments = true.0FC negotiates and force-closes confirm as a package. A funded party force-closing with the counterparty offline: commitment
8fceb534…aac9(fee 0, weight 648, P2A anchor +to_local) + child6d5ee6cd…a448(anchor + one wallet UTXO, 404 sat) viasubmitpackage→"success", block 3269710.The result that matters for a sweep-everything wallet: a zero-on-chain user can recover from an unresponsive-LSP force-close using only a backup + any fee UTXO. We modelled the real recovery: a 0FC channel where the LSP pushed the user 40k and the user holds 0 on-chain; snapshot the user's channel state (seed + monitor), kill the LSP permanently, then restore that state onto a fresh wallet and force-close with the LSP still down. The restored wallet broadcast the fee-0 commitment and CPFP'd the keyless P2A anchor with a 25k user-provided UTXO as a package (
submitpackage→"success"); commitment2c055014(+ child786e074f, 692 sat) confirmed block 3270210, with the user's 40k landing into_local. Reproduced on a second independent channel (commitmentb2895207, block 3270239). This is exactly the unilateral-exit path that was previously blocked — 0FC's keyless anchor is what makes it work without a per-channel reserve or any keyed-anchor tooling.One note for reproducers (no ask): a zero-on-chain node rejects an inbound 0FC channel on the anchor reserve (
Available: 0/25000sats) and silently falls back tooption_static_remotekey;trusted_peers_no_reserveis what lets it negotiate 0FC. Relatedly, a node that has its counterparty intrusted_peers_no_reservedoesn't broadcast its own force-close while that peer is offline (theBumpTransactionEvent::ChannelClosehandler returns early for trusted peers — event.rs); we see the same for legacy anchors and 0FC. That's consistent with what opting into no-reserve means, and the recovery path above routes around it entirely (the restoring wallet doesn't trust the dead peer), so no change requested — just flagging in case the 0FC interaction is worth a doc note.Net: #660 delivers what we needed — pending the knob surfacing through orange-sdk, the unilateral-exit gap for our wallet class is closed. Thanks to everyone who worked it.