Files
client/plans/qr-code-large-data-fix.md
T
2026-04-17 16:52:51 -04:00

7.0 KiB
Raw Permalink Blame History

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,5003,000+ characters. Small payloads (short Lightning invoices, npubs) work fine.

Root Cause Analysis

Current Library

  • Library: datalog/qrcode-svg (GitHub), MIT license
  • File: www/js/qrcode-svg.min.js
  • Supports: QR versions 140, all 4 error correction levels (L/M/Q/H)

Current Usage in renderQrCode() (cashu.html:844877)

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.

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:

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 = '<div style="color:var(--accent-color);font-size:80%;padding:10px;text-align:center;">Token too large for QR code. Use the copy button instead.</div>';
    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:

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