# 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