Merge pull request #350 from naturallaw777/arena/019faaea-sovran-systemsos
NWC: downloadable & printable LNURL QR for wallet connections
This commit is contained in:
@@ -26,10 +26,11 @@ import urllib.request
|
||||
from threading import Lock
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.requests import Request
|
||||
from html import escape as _html_escape
|
||||
from pydantic import BaseModel
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
@@ -1341,22 +1342,101 @@ def _evaluate_domain_checklist(
|
||||
|
||||
# ── QR code helper ────────────────────────────────────────────────
|
||||
|
||||
def _generate_qr_base64(data: str) -> str | None:
|
||||
"""Generate a QR code PNG and return it as a base64-encoded data URI.
|
||||
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", "6", "-m", "2", "-l", "H", data],
|
||||
["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:
|
||||
b64 = base64.b64encode(result.stdout).decode("ascii")
|
||||
return f"data:image/png;base64,{b64}"
|
||||
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
|
||||
return None
|
||||
|
||||
|
||||
def _generate_qr_base64(data: str) -> str | None:
|
||||
"""Generate a QR code PNG and return it as a base64-encoded data URI."""
|
||||
png = _generate_qr_png_bytes(data, scale=6, margin=2)
|
||||
if png:
|
||||
b64 = base64.b64encode(png).decode("ascii")
|
||||
return f"data:image/png;base64,{b64}"
|
||||
return None
|
||||
|
||||
|
||||
# ── Bech32 encoding (BIP-173) — used for LNURL strings (LUD-01) ──
|
||||
|
||||
_BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||
_BECH32_GENERATOR = (0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3)
|
||||
|
||||
|
||||
def _bech32_polymod(values: list[int]) -> int:
|
||||
chk = 1
|
||||
for value in values:
|
||||
top = chk >> 25
|
||||
chk = ((chk & 0x1FFFFFF) << 5) ^ value
|
||||
for i in range(5):
|
||||
if (top >> i) & 1:
|
||||
chk ^= _BECH32_GENERATOR[i]
|
||||
return chk
|
||||
|
||||
|
||||
def _bech32_hrp_expand(hrp: str) -> list[int]:
|
||||
return [ord(c) >> 5 for c in hrp] + [0] + [ord(c) & 31 for c in hrp]
|
||||
|
||||
|
||||
def _bech32_create_checksum(hrp: str, data: list[int]) -> list[int]:
|
||||
values = _bech32_hrp_expand(hrp) + data
|
||||
polymod = _bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
|
||||
return [(polymod >> (5 * (5 - i))) & 31 for i in range(6)]
|
||||
|
||||
|
||||
def _bech32_convertbits(data: bytes, frombits: int, tobits: int) -> list[int]:
|
||||
acc = 0
|
||||
bits = 0
|
||||
ret: list[int] = []
|
||||
maxv = (1 << tobits) - 1
|
||||
for value in data:
|
||||
acc = (acc << frombits) | value
|
||||
bits += frombits
|
||||
while bits >= tobits:
|
||||
bits -= tobits
|
||||
ret.append((acc >> bits) & maxv)
|
||||
if bits:
|
||||
ret.append((acc << (tobits - bits)) & maxv)
|
||||
return ret
|
||||
|
||||
|
||||
def _bech32_encode(hrp: str, payload: bytes) -> str:
|
||||
"""Encode payload bytes as a bech32 string with the given HRP (BIP-173)."""
|
||||
data = _bech32_convertbits(payload, 8, 5)
|
||||
combined = data + _bech32_create_checksum(hrp, data)
|
||||
return hrp + "1" + "".join(_BECH32_CHARSET[d] for d in combined)
|
||||
|
||||
|
||||
def _nwc_lnurl_bech32(alias: str, domain: str) -> str:
|
||||
"""Return the LUD-01 bech32 LNURL for a wallet connection alias."""
|
||||
url = f"https://{domain}/.well-known/lnurlp/{alias}"
|
||||
return _bech32_encode("lnurl", url.encode("utf-8"))
|
||||
|
||||
|
||||
# ── Update helpers (file-based, no systemctl) ────────────────────
|
||||
|
||||
def _read_update_status() -> str:
|
||||
@@ -4550,6 +4630,159 @@ async def api_nwc_test(alias: str):
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ── LNURL QR sharing (download / print) ───────────────────────────
|
||||
|
||||
|
||||
def _nwc_lnurl_context(wallet_identifier: str) -> tuple[dict | None, str | None, JSONResponse | None]:
|
||||
"""Resolve a wallet connection + domain for the LNURL QR endpoints.
|
||||
|
||||
Returns ``(wallet, domain, error_response)``. On success the error is
|
||||
``None``; on failure the wallet is ``None`` and the error is a ready-made
|
||||
``JSONResponse``. Blocking — call via ``loop.run_in_executor``.
|
||||
"""
|
||||
domain = _nwc_domain()
|
||||
if not domain:
|
||||
return None, None, _nwc_error(503, "domain_not_configured", "Lightning Address domain is not configured.")
|
||||
needle = wallet_identifier.strip().lower()
|
||||
try:
|
||||
wallets = _nwc_mgr.get_manager().list_wallets(domain)
|
||||
except _nwc_mgr.AlbyHubError as exc:
|
||||
return None, domain, _nwc_error(503, exc.code, exc.args[0])
|
||||
for wallet in wallets:
|
||||
wid = str(wallet.get("id", "")).lower()
|
||||
wpk = str(wallet.get("pubkey", "")).lower()
|
||||
if needle and needle in (wid, wpk):
|
||||
if not wallet.get("alias"):
|
||||
return None, domain, _nwc_error(404, "wallet_not_found", "Wallet connection has no Lightning Address alias.")
|
||||
return wallet, domain, None
|
||||
return None, domain, _nwc_error(404, "wallet_not_found", "Wallet connection not found.")
|
||||
|
||||
|
||||
def _nwc_lnurl_error_png() -> JSONResponse:
|
||||
return _nwc_error(500, "qr_unavailable", "QR code generation failed on this system.")
|
||||
|
||||
|
||||
@app.get("/api/nwc/wallets/{wallet_identifier}/lnurl")
|
||||
async def api_nwc_wallet_lnurl(wallet_identifier: str):
|
||||
"""Return the bech32 LNURL + Lightning Address for a wallet connection."""
|
||||
loop = asyncio.get_event_loop()
|
||||
wallet, domain, err = await loop.run_in_executor(None, _nwc_lnurl_context, wallet_identifier)
|
||||
if err:
|
||||
return err
|
||||
return {
|
||||
"alias": wallet["alias"],
|
||||
"lightning_address": wallet.get("lightning_address"),
|
||||
"lnurl": _nwc_lnurl_bech32(wallet["alias"], domain),
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/nwc/wallets/{wallet_identifier}/lnurl-qr.png")
|
||||
async def api_nwc_wallet_lnurl_qr_png(wallet_identifier: str, download: bool = False, scale: int = 10):
|
||||
"""Serve the LNURL QR code as a PNG (uppercase LNURL for scannability).
|
||||
|
||||
``?download=1`` switches Content-Disposition to attachment so the browser
|
||||
saves it with a meaningful filename. ``scale`` (qrencode pixel size) may be
|
||||
raised for high-resolution display, e.g. the print page.
|
||||
"""
|
||||
loop = asyncio.get_event_loop()
|
||||
wallet, domain, err = await loop.run_in_executor(None, _nwc_lnurl_context, wallet_identifier)
|
||||
if err:
|
||||
return err
|
||||
scale = max(1, min(scale, 40))
|
||||
lnurl_qr = _nwc_lnurl_bech32(wallet["alias"], domain).upper()
|
||||
png = await loop.run_in_executor(None, lambda: _generate_qr_png_bytes(lnurl_qr, scale=scale, margin=4))
|
||||
if png is None:
|
||||
return _nwc_lnurl_error_png()
|
||||
filename = f"lightning-{wallet['alias']}-{domain}.png"
|
||||
disposition = "attachment" if download else "inline"
|
||||
return Response(
|
||||
content=png,
|
||||
media_type="image/png",
|
||||
headers={
|
||||
"Content-Disposition": f'{disposition}; filename="{filename}"',
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/nwc/wallets/{wallet_identifier}/lnurl-qr.svg")
|
||||
async def api_nwc_wallet_lnurl_qr_svg(wallet_identifier: str, download: bool = False):
|
||||
"""Serve the LNURL QR code as an SVG — resolution-independent, so it stays
|
||||
perfectly sharp when printed at any size or embedded on a website."""
|
||||
loop = asyncio.get_event_loop()
|
||||
wallet, domain, err = await loop.run_in_executor(None, _nwc_lnurl_context, wallet_identifier)
|
||||
if err:
|
||||
return err
|
||||
lnurl_qr = _nwc_lnurl_bech32(wallet["alias"], domain).upper()
|
||||
svg = await loop.run_in_executor(None, lambda: _generate_qr_svg(lnurl_qr, margin=4))
|
||||
if svg is None:
|
||||
return _nwc_error(500, "qr_unavailable", "QR code generation failed on this system.")
|
||||
filename = f"lightning-{wallet['alias']}-{domain}.svg"
|
||||
disposition = "attachment" if download else "inline"
|
||||
return Response(
|
||||
content=svg.encode("utf-8"),
|
||||
media_type="image/svg+xml",
|
||||
headers={
|
||||
"Content-Disposition": f'{disposition}; filename="{filename}"',
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/nwc/wallets/{wallet_identifier}/lnurl-qr/print")
|
||||
async def api_nwc_wallet_lnurl_qr_print(wallet_identifier: str):
|
||||
"""Print-ready LNURL payment card — opens the browser print dialog."""
|
||||
loop = asyncio.get_event_loop()
|
||||
wallet, domain, err = await loop.run_in_executor(None, _nwc_lnurl_context, wallet_identifier)
|
||||
if err:
|
||||
return err
|
||||
address = wallet.get("lightning_address") or f"{wallet['alias']}@{domain}"
|
||||
name = _html_escape(str(wallet.get("name") or "Wallet"))
|
||||
addr_html = _html_escape(address)
|
||||
img_src = f"/api/nwc/wallets/{urllib.parse.quote(wallet_identifier, safe='')}/lnurl-qr.png?scale=24"
|
||||
html_doc = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{addr_html}</title>
|
||||
<style>
|
||||
body {{ font-family: system-ui, sans-serif; display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:95vh; margin:0; color:#111; }}
|
||||
.card {{ text-align:center; padding:28px 36px; border:2px solid #111; border-radius:18px; max-width:90vw; }}
|
||||
h1 {{ font-size:20px; margin:0 0 18px; font-weight:700; }}
|
||||
img {{ width:64mm; max-width:70vw; image-rendering:pixelated; }}
|
||||
.addr {{ font-family:ui-monospace, monospace; font-size:15px; margin-top:18px; word-break:break-all; }}
|
||||
.hint {{ font-size:12px; color:#555; margin-top:10px; }}
|
||||
.actions {{ margin-top:22px; display:flex; gap:12px; }}
|
||||
.actions button {{ font:inherit; padding:10px 22px; border-radius:10px; border:1px solid #111; background:#111; color:#fff; cursor:pointer; }}
|
||||
.actions button.secondary {{ background:#fff; color:#111; }}
|
||||
@media print {{ .actions {{ display:none; }} body {{ min-height:auto; }} }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Pay {name} with Bitcoin ⚡</h1>
|
||||
<img src="{img_src}" alt="LNURL QR code for {addr_html}">
|
||||
<div class="addr">{addr_html}</div>
|
||||
<div class="hint">Scan with any Lightning wallet — this QR never expires.</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button onclick="window.print()">🖨 Print</button>
|
||||
<button class="secondary" onclick="window.close()">Close</button>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener("load", function () {{
|
||||
var img = document.querySelector("img");
|
||||
function go() {{ setTimeout(function () {{ window.print(); }}, 250); }}
|
||||
if (img && !img.complete) {{ img.addEventListener("load", go); img.addEventListener("error", go); }}
|
||||
else {{ go(); }}
|
||||
}});
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(content=html_doc, headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
|
||||
# ── Security endpoints ────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -517,7 +517,32 @@ button.btn-reboot:hover:not(:disabled) {
|
||||
}
|
||||
|
||||
.nwc-wallet-actions .matrix-form-back,
|
||||
.nwc-wallet-actions .btn-close-modal {
|
||||
.nwc-wallet-actions .btn-close-modal,
|
||||
.nwc-wallet-actions .matrix-action-btn {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── NWC LNURL share / download view ───────────────────────────── */
|
||||
|
||||
.nwc-share-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.nwc-share-actions .matrix-form-back,
|
||||
.nwc-share-actions .matrix-form-submit {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.nwc-lnurl-value {
|
||||
font-size: 0.72rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.nwc-share-formats-hint {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -178,6 +178,64 @@ function _nwcRenderWalletState() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.view === "share" && state.shareWallet) {
|
||||
var sw = state.shareWallet;
|
||||
var swId = encodeURIComponent(String(sw.id || sw.pubkey || ""));
|
||||
var swAddress = sw.lightning_address || ((sw.alias && state.domain) ? sw.alias + "@" + state.domain : "");
|
||||
var pngUrl = "/api/nwc/wallets/" + swId + "/lnurl-qr.png";
|
||||
var shareAddrId = "nwc-share-addr-" + Math.random().toString(36).substring(2, 8);
|
||||
var shareLnurlId = "nwc-share-lnurl-" + Math.random().toString(36).substring(2, 8);
|
||||
html += '<p class="svc-detail-desc">Share the Lightning Address for <strong>' + escHtml(sw.name || "Wallet") + '</strong>. This QR never expires — anyone can scan it with any Lightning wallet to send sats, as many times as they like.</p>' +
|
||||
'<div class="creds-qr-wrap">' +
|
||||
'<img class="creds-qr-img" src="' + pngUrl + '" alt="LNURL QR code for ' + escHtml(swAddress) + '">' +
|
||||
'<div class="creds-qr-hint">Scan with any Lightning wallet to pay ' + escHtml(swAddress) + '</div>' +
|
||||
'</div>' +
|
||||
(swAddress
|
||||
? '<div class="creds-row"><div class="creds-label">Lightning Address</div><div class="creds-value-wrap"><div class="creds-value" id="' + shareAddrId + '">' + escHtml(swAddress) + '</div><button class="creds-copy-btn" data-target="' + shareAddrId + '">Copy</button></div></div>'
|
||||
: "") +
|
||||
'<div class="creds-row"><div class="creds-label">LNURL</div><div class="creds-value-wrap"><div class="creds-value nwc-lnurl-value" id="' + shareLnurlId + '">Loading…</div><button class="creds-copy-btn" data-target="' + shareLnurlId + '">Copy</button></div></div>' +
|
||||
'<div class="nwc-share-actions">' +
|
||||
'<button class="matrix-form-submit" id="nwc-share-dl-png-btn">⬇ Download PNG</button>' +
|
||||
'<button class="matrix-form-back" id="nwc-share-dl-svg-btn">⬇ Download SVG</button>' +
|
||||
'<button class="matrix-form-back" id="nwc-share-print-btn">🖨 Print…</button>' +
|
||||
'<button class="matrix-form-back" id="nwc-share-back-btn">← Back</button>' +
|
||||
'</div>' +
|
||||
'<div class="creds-qr-hint nwc-share-formats-hint">PNG is ideal for websites and social posts. SVG stays sharp at any print size.</div>';
|
||||
host.innerHTML = html;
|
||||
_attachCopyHandlers(host);
|
||||
apiFetch("/api/nwc/wallets/" + swId + "/lnurl").then(function(payload) {
|
||||
var el = document.getElementById(shareLnurlId);
|
||||
if (el) el.textContent = payload.lnurl || "Unavailable";
|
||||
}).catch(function() {
|
||||
var el = document.getElementById(shareLnurlId);
|
||||
if (el) el.textContent = "Unavailable";
|
||||
});
|
||||
function _nwcShareDownload(url) {
|
||||
var a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.rel = "noopener";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
var dlPngBtn = document.getElementById("nwc-share-dl-png-btn");
|
||||
if (dlPngBtn) dlPngBtn.addEventListener("click", function() { _nwcShareDownload(pngUrl + "?download=1"); });
|
||||
var dlSvgBtn = document.getElementById("nwc-share-dl-svg-btn");
|
||||
if (dlSvgBtn) dlSvgBtn.addEventListener("click", function() { _nwcShareDownload("/api/nwc/wallets/" + swId + "/lnurl-qr.svg?download=1"); });
|
||||
var printBtn = document.getElementById("nwc-share-print-btn");
|
||||
if (printBtn) printBtn.addEventListener("click", function() { window.open("/api/nwc/wallets/" + swId + "/lnurl-qr/print", "_blank", "noopener"); });
|
||||
var shareBackBtn = document.getElementById("nwc-share-back-btn");
|
||||
if (shareBackBtn) {
|
||||
shareBackBtn.addEventListener("click", function() {
|
||||
state.view = "list";
|
||||
state.shareWallet = null;
|
||||
_nwcClearMessage();
|
||||
_nwcRenderWalletState();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var canCreate = !!state.domain && !state.busy;
|
||||
html += '<div class="matrix-actions-row">' +
|
||||
'<button class="matrix-action-btn" id="nwc-open-create-btn"' + (canCreate ? "" : " disabled") + '>' + (state.domain ? '➕ Create Wallet Connection' : 'Configure Domain First') + '</button>' +
|
||||
@@ -219,6 +277,7 @@ function _nwcRenderWalletState() {
|
||||
'<div class="creds-row"><div class="creds-label">Balance</div><div class="creds-value-wrap"><div class="creds-value">' + escHtml(String(wallet.balance_sats || 0)) + ' sats</div></div></div>' +
|
||||
'<div class="creds-row"><div class="creds-label">Pending TX</div><div class="creds-value-wrap"><div class="creds-value">' + escHtml(String(wallet.pending_transactions || 0)) + '</div></div></div>' +
|
||||
'<div class="nwc-wallet-actions">' +
|
||||
'<button class="matrix-action-btn nwc-wallet-action-btn" data-action="share" data-wallet-id="' + escHtml(id) + '"' + ((state.busy || !wallet.lightning_address) ? " disabled" : "") + '>⚡ Share QR</button>' +
|
||||
'<button class="matrix-form-back nwc-wallet-action-btn" data-action="test" data-wallet-alias="' + escHtml(wallet.alias || "") + '"' + (state.busy ? " disabled" : "") + '>Verify Address</button>' +
|
||||
'<button class="matrix-form-back nwc-wallet-action-btn" data-action="drain" data-wallet-id="' + escHtml(id) + '"' + (state.busy ? " disabled" : "") + '>Drain</button>' +
|
||||
'<button class="btn btn-close-modal nwc-wallet-action-btn" data-action="delete" data-wallet-id="' + escHtml(id) + '"' + (state.busy ? " disabled" : "") + '>Delete</button>' +
|
||||
@@ -245,13 +304,32 @@ function _nwcRenderWalletState() {
|
||||
host.querySelectorAll(".nwc-wallet-action-btn").forEach(function(btn) {
|
||||
btn.addEventListener("click", function() {
|
||||
var action = btn.getAttribute("data-action");
|
||||
if (action === "test") _nwcVerifyWallet(btn.getAttribute("data-wallet-alias"));
|
||||
if (action === "share") _nwcOpenShare(btn.getAttribute("data-wallet-id"));
|
||||
else if (action === "test") _nwcVerifyWallet(btn.getAttribute("data-wallet-alias"));
|
||||
else if (action === "drain") _nwcDrainWallet(btn.getAttribute("data-wallet-id"));
|
||||
else if (action === "delete") _nwcDeleteWallet(btn.getAttribute("data-wallet-id"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _nwcOpenShare(walletId) {
|
||||
if (!_nwcModalState || _nwcModalState.busy || !walletId) return;
|
||||
var wallet = null;
|
||||
for (var i = 0; i < _nwcModalState.wallets.length; i++) {
|
||||
var w = _nwcModalState.wallets[i];
|
||||
if (String(w.id || w.pubkey || "") === String(walletId)) { wallet = w; break; }
|
||||
}
|
||||
if (!wallet) {
|
||||
_nwcSetMessage("error", "Wallet connection not found.");
|
||||
_nwcRenderWalletState();
|
||||
return;
|
||||
}
|
||||
_nwcClearMessage();
|
||||
_nwcModalState.shareWallet = wallet;
|
||||
_nwcModalState.view = "share";
|
||||
_nwcRenderWalletState();
|
||||
}
|
||||
|
||||
async function _nwcRefreshWallets() {
|
||||
if (!_nwcModalState) return;
|
||||
var host = _nwcWalletSectionEl();
|
||||
@@ -260,7 +338,7 @@ async function _nwcRefreshWallets() {
|
||||
var payload = await apiFetch("/api/nwc/wallets");
|
||||
_nwcModalState.wallets = Array.isArray(payload.wallets) ? payload.wallets : [];
|
||||
_nwcModalState.domain = payload.domain || null;
|
||||
if (_nwcModalState.view !== "create" && _nwcModalState.view !== "created") {
|
||||
if (_nwcModalState.view !== "create" && _nwcModalState.view !== "created" && _nwcModalState.view !== "share") {
|
||||
_nwcModalState.view = _nwcModalState.wallets.length > 0 ? "list" : "empty";
|
||||
}
|
||||
_nwcRenderWalletState();
|
||||
@@ -409,6 +487,7 @@ async function _nwcInitWalletFlow(unit, name, icon) {
|
||||
message: "",
|
||||
messageKind: "",
|
||||
lastCreated: null,
|
||||
shareWallet: null,
|
||||
createForm: {
|
||||
name: "",
|
||||
alias: "",
|
||||
|
||||
Reference in New Issue
Block a user