Files
archy/.planning/phases/01-federation-mesh-hardening/01-19-PLAN.md

7.4 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
01-federation-mesh-hardening 19 execute 1
core/archipelago/src/api/rpc/lnd/wallet.rs
false
FED-08
truths prohibitions artifacts
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
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
path provides
core/archipelago/src/api/rpc/lnd/wallet.rs 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.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

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 <invoice>` → `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

<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>
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.