# QR Code Large Data Rendering Fix ## Problem QR codes fail to render (or render unscannable) when encoding large data payloads like eCash tokens (`cashuA...` / `cashuB...`). These tokens can be 1,500–3,000+ characters. Small payloads (short Lightning invoices, npubs) work fine. ## Root Cause Analysis ### Current Library - **Library**: `datalog/qrcode-svg` ([GitHub](https://github.com/datalog/qrcode-svg)), MIT license - **File**: `www/js/qrcode-svg.min.js` - **Supports**: QR versions 1–40, all 4 error correction levels (L/M/Q/H) ### Current Usage in `renderQrCode()` (cashu.html:844–877) ```javascript const svg = QRCode({ msg: value, pad }); ``` **Only `msg` and `pad` are passed.** The library's other options are not used: - `ecl` — Error Correction Level (defaults to `M` if not specified) - `dim` — SVG dimension - `pal` — Color palette ### Why It Fails The `datalog/qrcode-svg` library has a **hard limit of QR Version 40** (177×177 modules). The maximum data capacity depends on the error correction level: | ECL | Binary (bytes) | Alphanumeric (chars) | |-----|---------------|---------------------| | L (Low) | 2,953 | 4,296 | | M (Medium) | 2,331 | 3,391 | | Q (Quartile) | 1,663 | 2,420 | | H (High) | 1,273 | 1,852 | Cashu tokens are **mixed-case base64** strings, which means they **cannot use alphanumeric mode** (which only supports uppercase A-Z, 0-9, and a few symbols). They must be encoded in **byte mode**, where the Version 40 / ECL M limit is **2,331 bytes**. With the default `ecl: 'M'`, tokens longer than ~2,331 characters will fail. Switching to `ecl: 'L'` raises the limit to **2,953 bytes** — a 27% increase. However, even with `ecl: 'L'`, tokens exceeding ~2,953 characters will still fail because that is the absolute maximum for any standard QR code. ## Recommended Fix: Two-Phase Approach ### Phase 1: Quick Fix — Set `ecl: 'L'` for Cashu Tokens (Do This First) Modify `renderQrCode()` in `www/cashu.html` to use the lowest error correction level for cashu tokens, maximizing data capacity: ```javascript function renderQrCode(container, text) { if (!container) return; const raw = String(text || '').trim(); if (!raw) { container.innerHTML = ''; container.classList.add('clsHidden'); return; } const isLightningInvoice = /^ln[a-z0-9]+/i.test(raw); const isCashuToken = /^cashu[AB]/i.test(raw); const value = isLightningInvoice ? raw.toUpperCase() : raw; const pad = isLightningInvoice ? 8 : (isCashuToken ? 1 : 4); const ecl = isCashuToken ? 'L' : (isLightningInvoice ? 'L' : 'M'); if (typeof QRCode !== 'function') { console.warn('[cashu.html] QRCode generator not available'); container.innerHTML = ''; container.classList.add('clsHidden'); return; } try { container.innerHTML = ''; const svg = QRCode({ msg: value, pad, ecl }); svg.removeAttributeNS(null, 'width'); svg.removeAttributeNS(null, 'height'); container.appendChild(svg); container.classList.remove('clsHidden'); } catch (error) { console.error('[cashu.html] QR render failed:', error); container.innerHTML = '
Token too large for QR code. Use the copy button instead.
'; container.classList.remove('clsHidden'); } } ``` Key changes: 1. **Detect cashu tokens** with `/^cashu[AB]/i` 2. **Set `ecl: 'L'`** for cashu tokens and lightning invoices (max capacity) 3. **Reduce `pad` to 1** for cashu tokens (more room for data modules) 4. **Show a helpful error message** instead of hiding the container on failure ### Phase 2: If Tokens Still Exceed 2,953 Bytes — Consider Alternatives If eCash tokens regularly exceed ~2,953 characters, no standard QR code library can help — this is a QR specification limit. Options: #### Option A: Uppercase the Cashu Token (if protocol allows) If the cashu token can be uppercased without breaking it, the QR encoder can use **alphanumeric mode** which supports up to **4,296 characters** at ECL L. However, cashuA/cashuB tokens are base64-encoded, so uppercasing would corrupt them. **This is NOT viable for standard base64.** #### Option B: Replace the Library with One That Has Better Diagnostics The current library silently fails or throws. A more robust library could provide better error handling. Good open-source alternatives: | Library | URL | Size | Format | Notes | |---------|-----|------|--------|-------| | **qrcode-generator** | https://github.com/niclas/qrcode-generator | ~15KB | JS | Mature, supports all versions/ECL, canvas + SVG | | **qr-creator** | https://github.com/niclas/qr-creator | ~8KB | JS | Lightweight, SVG output, good error messages | | **nayuki/QR-Code-generator** | https://github.com/nayuki/QR-Code-generator | ~20KB | JS/TS | Reference implementation, very well tested, supports all versions | | **soldair/node-qrcode** | https://github.com/soldair/node-qrcode | ~30KB | JS | Most popular, canvas/SVG/terminal, excellent options | **Recommendation**: The current `datalog/qrcode-svg` library is actually fine for the job — it supports all QR versions and ECL levels. The issue is purely that `ecl` is not being passed. Switching libraries is unnecessary unless we need features like: - Structured Append (splitting data across multiple QR codes) - Better error diagnostics - Canvas rendering for performance #### Option C: Multi-QR / Structured Append For tokens exceeding 2,953 bytes, split the data across **multiple QR codes** using QR Structured Append (up to 16 QR codes). This requires: - A library that supports structured append (most don't) - UI changes to show multiple QR codes or an animated sequence - The scanning app to support structured append (many don't) #### Option D: Animated QR Codes (UR Protocol) Use the **UR (Uniform Resources)** protocol from Blockchain Commons, which encodes large data as a sequence of animated QR frames. Libraries: - **bc-ur** (https://github.com/niclas/bc-ur) — JavaScript implementation - Used by many Bitcoin/crypto wallets (BlueWallet, Sparrow, etc.) This is the most robust solution for very large payloads but requires both sender and receiver to support the UR protocol. ## Files to Modify | File | Change | |------|--------| | `www/cashu.html` | Update `renderQrCode()` function (~lines 844-877) | ## Files Using QR Codes (for reference, no changes needed) | File | Usage | |------|-------| | `www/npub.html:428` | `QRCode({ msg: currentNpub, pad: 0 })` — short data, fine | | `www/index.html:563` | `QRCode({ msg: pubkey, dim: 170 })` — short data, fine | | `www/skills-demo.html:1794` | `QRCode({ msg: value, dim: 220, pad: 1 })` — variable data | | `www/ai.html:1555` | `QRCode({ msg: value, dim: 220, pad: 1 })` — variable data | ## Implementation Steps 1. Update `renderQrCode()` in `www/cashu.html` to pass `ecl: 'L'` for cashu tokens 2. Reduce padding for cashu tokens to maximize scannable area 3. Add graceful error handling with user-friendly message when token exceeds QR capacity 4. Test with real cashu tokens of various sizes 5. If tokens regularly exceed 2,953 bytes, evaluate UR protocol or multi-QR approach