aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-25 16:50:21 -0700
committerAdam Soltys <[email protected]>2026-06-25 16:50:21 -0700
commitdae260d1a2cfb866d7b0f5c5620d5e0392bd4723 (patch)
tree064babaaff99e0e4249bfece63814b97e2b4c641 /src/app.js
parent654d029fba939027d9decec852ebb3efe6037c95 (diff)
downloadcoinosv3-dae260d1a2cfb866d7b0f5c5620d5e0392bd4723.tar.gz
coinosv3-dae260d1a2cfb866d7b0f5c5620d5e0392bd4723.zip
Compact gift links: base32 codec + uppercase /g/ path for a smaller QR
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/<code> 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/<code>; 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) <[email protected]>
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js28
1 files changed, 19 insertions, 9 deletions
diff --git a/src/app.js b/src/app.js
index fdc5b3f..708a56e 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1081,7 +1081,7 @@ function pubkeyCard() {
// Gift link: presign a chosen amount as a #gift= PSBT that whoever opens claims
// into a fresh wallet only they control. The coin is reserved until claimed.
function giftUrl() {
- return `${location.origin}/#gift=${ui.giftCode}`;
+ return `${location.origin}/g/${ui.giftCode}`;
}
function giftRate() {
return (wallet.feeRates && wallet.feeRates.halfHourFee) || 5;
@@ -1170,7 +1170,10 @@ function giftCard() {
h('div', { class: 'warn-box' }, t('giftLinkWarn')),
ui.giftCode
? h('div', { class: 'col', style: 'align-items:center;gap:10px' },
- h('div', { html: qrSvg(giftUrl(), { ec: 'L' }) }),
+ // Uppercase the whole URL + alphanumeric mode → much smaller QR. The
+ // domain is case-insensitive and the base32 code is already uppercase;
+ // the opener parses /g/ and the code case-insensitively.
+ h('div', { html: qrSvg(giftUrl().toUpperCase(), { ec: 'L', mode: 'Alphanumeric' }) }),
h('div', { class: 'addr-box break', style: 'width:100%;font-size:12px' }, giftUrl()),
h('div', { class: 'row gap6 wrap' },
copyBtn(giftUrl(), t('copyLink')),
@@ -1393,15 +1396,22 @@ function goHome() {
}
// ================================================================ GIFT / CLAIM
-// Read a #gift=<psbt> link, returning the code (and scrubbing it from the URL so
-// the bearer instrument doesn't linger in the address bar / history). Validates
-// that it decodes to a real gift.
+// Read a gift link, returning the code (and scrubbing it from the URL so the
+// bearer instrument doesn't linger in the address bar / history). New links are
+// /g/<base32-compact>; legacy links are #gift=<base64url-psbt>. Both validate by
+// decoding to a real gift.
function readGiftHash() {
try {
- const m = location.hash.match(/^#gift=([A-Za-z0-9_-]+)$/);
- if (!m) return null;
- history.replaceState(null, '', location.pathname + location.search);
- return previewGift(m[1]) ? m[1] : null;
+ let code = null;
+ const pm = location.pathname.match(/^\/g\/([A-Za-z0-9]+)\/?$/i); // new path form
+ if (pm) code = pm[1];
+ else {
+ const hm = location.hash.match(/^#gift=([A-Za-z0-9_-]+)$/); // legacy hash form
+ if (hm) code = hm[1];
+ }
+ if (!code) return null;
+ history.replaceState(null, '', '/');
+ return previewGift(code) ? code : null;
} catch {
return null;
}