docs(01): add FED-08 — wallet invoices must embed route hints (private-channel receive)

Diagnosed on archy-x250-mad2: handle_lnd_createinvoice omits LND's private
flag, so invoices carry route_hints: [] and are unroutable for any node whose
channels are unannounced. Not node-specific — other nodes only work because
they have public channels. Planned as 01-19 in wave 1 so it ships early.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-31 07:56:04 -04:00
parent 0eff666a6c
commit 1f3e56147f
3 changed files with 139 additions and 1 deletions

View File

@ -18,6 +18,7 @@ declared exit criteria (multinode pass + workstreams B/C/F), `.planning/codebase
- [ ] **FED-04**: Mesh messaging parity — attachment send (and the rest of the mesh chat surface) behaves identically on the demo and on real nodes: the demo backend implements the same RPC surface the UI calls, transport decisions mirror the real size-based tier logic, and no demo-only modals exist
- [ ] **FED-05**: Inter-node Lightning channel opening UX — the UI shows the node's shareable Lightning URI; lists trusted (federated) nodes by hostname for one-click channel opening; and lets the user browse/request channels with public nodes — using the existing design system and components, verified on the :8100 dev preview against archi-dev before deploy
- [ ] **FED-06**: On-brand payment success animation — the invoice "paid" tick's circle uses the screensaver-style ring with outer EQ-segment lines (reuse `ScreensaverRing.vue`'s compact size) in place of the current success burst, applied consistently everywhere the paid tick shows
- [ ] **FED-08**: Lightning invoices created by the wallet embed route hints (LND `private` flag) so nodes whose channels are unannounced can actually receive payments — diagnosed on archy-x250-mad2 2026-07-31, where every wallet-UI invoice had `route_hints: []` and was unroutable; the bug is unconditional and affects any node without a public channel
- [ ] **FED-07**: Fedimint gateway never installs with a pre-set password — gateway credentials are generated per-install via manifest-declared `generated_secrets` (or explicitly set by the user), never baked into the image/manifest; existing installs with the default password get a migration path (BLOCKER — default credentials are a security hole)
### UI Fixes (UIFIX) — user-reported blockers, added 2026-07-30
@ -111,6 +112,7 @@ Which phases cover which requirements. Updated during roadmap creation.
| FED-05 | Phase 1 | Pending |
| FED-06 | Phase 1 | Pending |
| FED-07 | Phase 1 | Pending |
| FED-08 | Phase 1 | Pending |
| UIFIX-01 | Phase 1 | Pending |
| UIFIX-02 | Phase 1 | Pending |
| UIFIX-03 | Phase 1 | Pending |

View File

@ -35,7 +35,7 @@ signed/decentralized registry and a user installs it on their node.
**Goal**: Federation and mesh are tight — a structured review of the fleet/federation and mesh code feeds fixes so node removal sticks, sync converges, and mesh messaging (including attachments) behaves identically everywhere it runs
**Depends on**: Nothing (first phase)
**Requirements**: FED-01, FED-02, FED-03, FED-04, FED-05, FED-06, FED-07, UIFIX-01, UIFIX-02, UIFIX-03, UIFIX-04, UIFIX-05, UIFIX-06
**Requirements**: FED-01, FED-02, FED-03, FED-04, FED-05, FED-06, FED-07, UIFIX-01, UIFIX-02, UIFIX-03, UIFIX-04, UIFIX-05, UIFIX-06, FED-08
**Success Criteria** (what must be TRUE):
1. A structured code review of the federation/fleet area (`core/archipelago/src/federation`, node sync, FIPS/transport dial layer) and the mesh area (`core/archipelago/src/mesh`, mesh RPC surface) produces a findings list, and every finding is fixed or explicitly deferred with a reason
@ -57,6 +57,7 @@ signed/decentralized registry and a user installs it on their node.
Plans:
- [ ] 01-19-PLAN.md — URGENT wave 1: wallet invoices embed route hints so private-channel nodes can receive (FED-08)
- [ ] 01-01-PLAN.md — Serialize the federation node store and make removal stick (FED-01)
- [ ] 01-02-PLAN.md — Demo mesh/federation RPC parity + automated parity harness (FED-04)
- [ ] 01-03-PLAN.md — On-brand paid tick: ScreensaverRing badge variant on both success surfaces (FED-06)

View File

@ -0,0 +1,135 @@
---
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 <invoice>` 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"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>
<tasks>
<task type="auto">
<name>Task 1: Embed route hints in wallet-created invoices</name>
<reversibility rating="reversible">One field in one JSON body; revert is a single-line change.</reversibility>
<files>core/archipelago/src/api/rpc/lnd/wallet.rs</files>
<read_first>
- `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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd core && cargo build --release 2>&1 | tail -5 && cargo test -p archipelago 2>&1 | tail -10</automated>
</verify>
<acceptance_criteria>
- `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
</acceptance_criteria>
<done>Wallet-created invoices ask LND for route hints, and every other invoice path has been audited.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2: Confirm a real payment arrives on archy-x250-mad2</name>
<what-built>
Invoice creation now sets LND's `private` flag, so invoices embed a hop hint
for the node's unannounced channel and become routable from outside.
</what-built>
<how-to-verify>
1. Deploy the patched `archipelago` binary to `archy-x250-mad2`.
2. Create a test invoice through the **wallet UI** (not raw `lncli`) — e.g. 100 sats.
3. `lncli decodepayreq <invoice>``route_hints` is populated with the
Olympus channel's `chan_id` (was `[]` before this fix).
4. Pay it from a real external wallet. Expected: the payment succeeds.
5. `lncli listinvoices` → that invoice shows a non-empty `htlcs` array and
`state: SETTLED`.
6. Sanity-check a node WITH public channels (e.g. archi-dev-box): creating and
paying an invoice there still works exactly as before.
</how-to-verify>
<resume-signal>Type "approved", or describe what you saw — which step, what happened instead.</resume-signal>
</task>
</tasks>
<verification>
- `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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
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.
</output>