fix: restore a Zeus-scannable LND REST connect QR

The LND-only rewrite of lndconnect.nix shipped a wrapper Zeus cannot
use: unknown flags (--cert/--macaroon), a non-existent onion path
(free/lnd.onion), and a REST hidden service that collided with LND's
P2P onion. Restore the nix-bitcoin contract — dedicated lnd-rest
onion on port 8080, --nocert over Tor, admin macaroon in the URI —
and only persist a valid lndconnect:// URI for the Hub QR.
This commit is contained in:
Sovran Systems
2026-08-17 18:22:36 -05:00
committed by naturallaw777
parent b35b327a07
commit 2ea1427766
6 changed files with 244 additions and 66 deletions
+34 -21
View File
@@ -487,9 +487,9 @@ SERVICE_DESCRIPTIONS: dict[str, str] = {
"Sovran_SystemsOS makes running a production-grade payment gateway as simple as flipping a switch."
),
"zeus-connect-setup.service": (
"Connect the Zeus mobile wallet to your Lightning node via LND REST. Send and receive "
"Connect the Zeus mobile wallet to your Lightning node via LND REST over Tor. Send and receive "
"Lightning payments from your phone using a direct node connection. "
"Scan the QR code to add your node to Zeus — this gives full node admin access."
"Scan the QR code to add your node to Zeus, then enable Use Tor — this gives full node admin access."
),
"mempool.service": (
"Your own blockchain explorer and mempool visualizer. Monitor transactions, "
@@ -1532,31 +1532,38 @@ def _evaluate_domain_checklist(
def _generate_qr_png_bytes(data: str, scale: int = 6, margin: int = 2) -> bytes | None:
"""Generate a QR code PNG and return the raw bytes.
Uses qrencode CLI (available on the system via credentials.nix)."""
try:
result = subprocess.run(
["qrencode", "-o", "-", "-t", "PNG", "-s", str(scale), "-m", str(margin), "-l", "H", data],
capture_output=True, timeout=10,
)
if result.returncode == 0 and result.stdout:
return result.stdout
except Exception:
pass
Uses qrencode CLI (available on the system via credentials.nix).
High error-correction (H) is preferred for short payloads. Long
lndconnect URIs can exceed version-40 capacity at H, so fall back to
quartile then low ECC — otherwise the Hub shows an empty Zeus QR.
"""
for ecc in ("H", "Q", "L"):
try:
result = subprocess.run(
["qrencode", "-o", "-", "-t", "PNG", "-s", str(scale), "-m", str(margin), "-l", ecc, data],
capture_output=True, timeout=10,
)
if result.returncode == 0 and result.stdout:
return result.stdout
except Exception:
pass
return None
def _generate_qr_svg(data: str, scale: int = 10, margin: int = 4) -> str | None:
"""Generate a QR code SVG document (resolution-independent, ideal if the
user wants to embed the QR in a website or print it at any size)."""
try:
result = subprocess.run(
["qrencode", "-o", "-", "-t", "SVG", "-s", str(scale), "-m", str(margin), "-l", "H", data],
capture_output=True, timeout=10,
)
if result.returncode == 0 and result.stdout:
return result.stdout.decode("utf-8", errors="replace")
except Exception:
pass
for ecc in ("H", "Q", "L"):
try:
result = subprocess.run(
["qrencode", "-o", "-", "-t", "SVG", "-s", str(scale), "-m", str(margin), "-l", ecc, data],
capture_output=True, timeout=10,
)
if result.returncode == 0 and result.stdout:
return result.stdout.decode("utf-8", errors="replace")
except Exception:
pass
return None
@@ -1680,6 +1687,9 @@ def _resolve_credential(cred: dict) -> dict | None:
qr_data = _generate_qr_base64(result["value"])
if qr_data:
result["qrcode"] = qr_data
else:
# Don't hide the URI if we could not render a scannable QR.
qronly = False
if qronly:
result["qronly"] = True
return result
@@ -1714,6 +1724,9 @@ def _resolve_credential(cred: dict) -> dict | None:
qr_data = _generate_qr_base64(value)
if qr_data:
result["qrcode"] = qr_data
else:
# Don't hide the URI if we could not render a scannable QR.
qronly = False
if qronly:
result["qronly"] = True
@@ -11,9 +11,9 @@ function _getZeusConnectGuideHtml() {
'<div class="nwc-connect-step"><div class="nwc-step-num">2</div><div>Open Zeus and open the <strong>Wallets</strong> screen.</div></div>' +
'<div class="nwc-connect-step"><div class="nwc-step-num">3</div><div>Tap the <strong>+ (Add Wallet)</strong> button in the top-right corner.</div></div>' +
'<div class="nwc-connect-step"><div class="nwc-step-num">4</div><div>On <strong>Wallet Configuration</strong>, tap the <strong>scan icon</strong> in the top-right corner, then scan the QR code above.</div></div>' +
'<div class="nwc-connect-step"><div class="nwc-step-num">5</div><div>Zeus detects the LND REST QR and fills in the connection details. Review them, then tap <strong>Save Wallet Config</strong>.</div></div>' +
'<div class="nwc-connect-step"><div class="nwc-step-num">5</div><div>Zeus detects the LND REST QR and fills in the connection details. Turn <strong>Use Tor</strong> on (the host is a .onion address), then tap <strong>Save Wallet Config</strong>.</div></div>' +
'</div>' +
'<div class="nwc-connect-note"><strong>💡 Note:</strong> This is <em>not</em> the same as the NWC pairing QR shown in Lightning Wallet Connections — that gives your wallet sandboxed, limited access for everyday spending. LND REST connects Zeus directly to your node for full admin control.</div>' +
'<div class="nwc-connect-note"><strong>💡 Note:</strong> This is <em>not</em> the same as the NWC pairing QR shown in Lightning Wallet Connections — that gives your wallet sandboxed, limited access for everyday spending. LND REST connects Zeus directly to your node for full admin control. The QR uses your dedicated LND REST Tor address (no TLS cert) so Zeus can scan and connect over Tor.</div>' +
'</div>';
}