--- phase: 01-federation-mesh-hardening plan: 19 type: execute wave: 1 depends_on: [] files_modified: - core/archipelago/src/api/rpc/lnd/wallet.rs autonomous: false requirements: [FED-08] must_haves: truths: - "An invoice created through the wallet UI's Receive flow embeds a route hint for every private/unannounced channel the node holds" - "`lncli decodepayreq ` on an invoice created via the wallet UI shows a non-empty `route_hints` array containing the private channel's chan_id" - "A real external wallet can pay an invoice created by the wallet UI on a node whose only channel is private — the HTLC arrives and the invoice reaches SETTLED" - "Nodes with public channels are unaffected — payments still route directly over the public channel" - "Every other invoice-creation call site in the codebase is audited for the same omission, and each is either fixed or documented as deliberately not needing route hints" prohibitions: - "MUST NOT change the amount, memo, expiry, or any other invoice field's existing behavior" - "MUST NOT log, echo, or commit any macaroon, invoice preimage, or node credential" artifacts: - path: "core/archipelago/src/api/rpc/lnd/wallet.rs" provides: "Invoice creation that sets LND's `private` flag so route hints are embedded" --- Fix Lightning receive on nodes whose channels are private/unannounced. `handle_lnd_createinvoice` posts to LND's REST `/v1/invoices` with only `value` and `memo`. LND defaults `private` to `false`, so the returned invoice carries `route_hints: []`. Private channels are not propagated through public gossip, so a sender has no way to find a route — the invoice is unpayable by anyone. Diagnosed on `archy-x250-mad2` (2026-07-31), whose single channel to "Olympus by ZEUS" is `private: true` with ~40.8k sats of usable inbound. Three wallet-UI invoices (10,000 / 5,000 / 500 sats) all had empty route hints and never received an HTLC. The one invoice that DID settle carries a memo ("Paid to Archipelago (Order ID: ...)") that appears nowhere in this Rust source — it came from a separate system (likely BTCPay) that builds invoices correctly. That is why "some payments have worked" while the wallet's own Receive flow never has. **This is not node-specific.** The bug is unconditional; it only *manifests* where a node lacks a public channel. Any user relying on a private channel has a broken Receive flow today. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md Task 1: Embed route hints in wallet-created invoices One field in one JSON body; revert is a single-line change. core/archipelago/src/api/rpc/lnd/wallet.rs - `core/archipelago/src/api/rpc/lnd/wallet.rs` around lines 554-557 — `handle_lnd_createinvoice`'s `invoice_body` construction - `core/archipelago/src/api/rpc/lnd/channels.rs:271,368` — the only current uses of a `private` field (channel OPEN, not invoice creation); confirms the omission is specific to invoices Add `"private": true` to `invoice_body` in `handle_lnd_createinvoice` so LND embeds hop hints for unannounced channels: ```rust let invoice_body = serde_json::json!({ "value": amount_sats.to_string(), "memo": memo, "private": true, }); ``` Setting it unconditionally is correct and safe: a route hint is harmless when the node also has public channels — LND still routes directly over a public channel when it can, and the hint merely offers an alternate path. Add a short comment stating why it is unconditional, so a future reader doesn't "optimize" it back to conditional and silently reintroduce the bug. Then audit every OTHER invoice-creation call site for the same omission — grep the tree for `/v1/invoices`, `addinvoice`, hold-invoice, LNURL and any keysend-adjacent flow. Fix each that should carry route hints; for any that deliberately should not, record the reason in the SUMMARY. Report the full list either way. cd core && cargo build --release 2>&1 | tail -5 && cargo test -p archipelago 2>&1 | tail -10 - `grep -A6 'let invoice_body' core/archipelago/src/api/rpc/lnd/wallet.rs` shows `"private": true` - `cargo build --release` succeeds - Existing tests pass - The SUMMARY lists every invoice-creation call site found, with fixed/not-needed and the reason Wallet-created invoices ask LND for route hints, and every other invoice path has been audited. Task 2: Confirm the fix, then hand off to post-OTA verification Invoice creation now sets LND's `private` flag at both call sites, so invoices embed a hop hint for the node's unannounced channel and become routable from outside. **`archy-x250-mad2` is a USER'S device holding real funds. Never deploy to it, SSH to it, or run any command against it.** This fix reaches it only via the normal OTA release, after the release is confirmed. Any verification involving that node is performed by its owner after the release lands — never by us, and never as a direct deploy. Verifiable now, without touching any user device: 1. Tests assert the request body sent to LND's `/v1/invoices` carries `"private": true` for BOTH `handle_lnd_createinvoice` (wallet Receive) and `create_invoice` (paid-content/peer-files seller flow). 2. On a node under our control with only PUBLIC channels, creating and paying an invoice still behaves exactly as before — no regression. Note the limitation honestly: a public-channel node shows empty route hints even when the fix is correct, so this checks non-regression only. Post-OTA-release, performed by the device owner: 3. Create an invoice through the **wallet UI** (not raw `lncli`) — e.g. 100 sats. 4. `lncli decodepayreq ` → `route_hints` populated with the Olympus channel's `chan_id` (was `[]` before this fix). 5. Pay it from a real external wallet; `lncli listinvoices` shows a non-empty `htlcs` array and `state: SETTLED`. Type "approved", or describe what you saw — which step, what happened instead. - `cargo build --release` and the existing test suite pass - On archy-x250-mad2, a wallet-UI invoice decodes with populated `route_hints` and settles when paid externally - A public-channel node is unaffected - Receiving Lightning payments works through the wallet UI on a node whose only channel is private - No regression for nodes with public channels - Any other invoice-creation path sharing this omission is fixed or explicitly cleared Create `.planning/phases/01-federation-mesh-hardening/01-19-SUMMARY.md`. It MUST record: the full list of invoice-creation call sites audited with each one's disposition, and the decoded `route_hints` before/after evidence from the node.