From dae260d1a2cfb866d7b0f5c5620d5e0392bd4723 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 25 Jun 2026 16:50:21 -0700 Subject: Compact gift links: base32 codec + uppercase /g/ path for a smaller QR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gift links now carry a compact binary encoding of just the claim-essential data (presigned inputs + the committed change output) instead of a full PSBT (~25% fewer bytes), base32-encoded and served at an uppercase /g/ path. The QR renders the uppercase URL in the QR alphanumeric mode (5.5 bits/char vs byte mode's 8), so the grid drops from ~73 to ~53 modules — much easier to scan. - wallet: _encodeCompactGift/_decodeCompactGift + _giftTx dispatcher; legacy base64url-PSBT links still claim (auto-detected by a magic byte). Verified round-trip + claim-byte-identical across 1/2/3-input and whole-balance gifts, against the real exported preview/claim/outpoint functions. - app: giftUrl uses /g/; opener parses /g/ (and legacy #gift=); QR uses uppercase + Alphanumeric mode (qrSvg gains a mode option w/ byte fallback). - nginx already SPA-routes /g/*; the SW serves the shell for the new path. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/qr.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/qr.js') diff --git a/src/qr.js b/src/qr.js index fe49a02..d02aed3 100644 --- a/src/qr.js +++ b/src/qr.js @@ -6,10 +6,20 @@ import qrcode from 'qrcode-generator'; // ec: error-correction level. 'M' by default; pass 'L' for long payloads (e.g. // gift links) where a smaller, chunkier grid scans more easily — on-screen QRs // are scanned immediately and don't need the damage tolerance of higher levels. -export function qrSvg(text, { margin = 2, ec = 'M' } = {}) { - const qr = qrcode(0, ec); // type 0 = auto-size - qr.addData(text); - qr.make(); +// mode: pass 'Alphanumeric' for an all-uppercase QR-alphanumeric string (0-9 A-Z +// space $%*+-./:) to pack it at 5.5 bits/char instead of byte mode's 8 — a much +// smaller grid for the same data. Falls back to byte mode if the text doesn't fit. +export function qrSvg(text, { margin = 2, ec = 'M', mode } = {}) { + let qr; + try { + qr = qrcode(0, ec); + qr.addData(text, mode); + qr.make(); + } catch { + qr = qrcode(0, ec); // mode mismatch (e.g. a non-alphanumeric char) — plain byte mode + qr.addData(text); + qr.make(); + } const count = qr.getModuleCount(); const size = count + margin * 2; -- cgit v1.2.3