7.0 KiB
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), 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)
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 toMif not specified)dim— SVG dimensionpal— 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:
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:
- Detect cashu tokens with
/^cashu[AB]/i - Set
ecl: 'L'for cashu tokens and lightning invoices (max capacity) - Reduce
padto 1 for cashu tokens (more room for data modules) - 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
- Update
renderQrCode()inwww/cashu.htmlto passecl: 'L'for cashu tokens - Reduce padding for cashu tokens to maximize scannable area
- Add graceful error handling with user-friendly message when token exceeds QR capacity
- Test with real cashu tokens of various sizes
- If tokens regularly exceed 2,953 bytes, evaluate UR protocol or multi-QR approach