aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/gifts.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/gifts.js')
-rw-r--r--src/features/gifts.js72
1 files changed, 36 insertions, 36 deletions
diff --git a/src/features/gifts.js b/src/features/gifts.js
index 42e794d..c6ccbd9 100644
--- a/src/features/gifts.js
+++ b/src/features/gifts.js
@@ -8,6 +8,7 @@ import { decryptWithCode, npubOf } from '../nostr.js';
import { installGiftWallet, previewGift, giftOutpoints, giftClaimTxs, isRetiredGift, giftMinimum, lockGift, previewLockedGift } from './gifts-wallet.js';
import { t } from '../i18n.js';
import { qrSvg } from '../qr.js';
+import { withdrawLinks } from './withdraw-links.js';
import { fmtBtc, timeAgo, shortAddr } from '../format.js';
import { getEventHash, verifyEvent } from 'nostr-tools/pure';
import { sha256 } from '@noble/hashes/sha256';
@@ -26,6 +27,7 @@ export function giftsFeature(ctx) {
// every call degrades to null in an on-chain-only build.
const hook = ctx.hook || (() => null);
const arkGiftOf = (code) => hook('arkGiftDecode', code);
+ const wl = withdrawLinks(ctx); // prepaid LNURL-withdraw links, the gift link's sibling on Send
// ================================================================ GIFT / CLAIM
// Read a gift link, returning the code (and scrubbing it from the URL so the
@@ -288,51 +290,47 @@ export function giftsFeature(ctx) {
h('h2', { style: 'margin:0' }, claimed ? t('claimedTitle') : t('newWalletTitle')),
h('p', { class: 'muted', style: 'margin:0' }, claimed ? t('claimedBody') : t('newWalletBody'))
),
- h('div', { class: 'card col' },
- h('h3', {}, t('recoveryPhrase')),
- h('div', { class: 'warn-box' }, t('writeDownWarn')),
- h('div', { class: 'words', style: '--rows-3:' + Math.ceil(words.length / 3) + ';--rows-2:' + Math.ceil(words.length / 2) },
- words.map((w, i) => h('div', { class: 'w' }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, w)))
- ),
- h('div', { class: 'row gap6' }, copyBtn(wallet.mnemonic, t('copyPhrase'))),
- h('button', { class: 'btn-primary btn-block', onClick: () => { ui.confirm = pickConfirm(words); ui.claimError = ''; ui.claimStep = 'verify'; render(); } }, t('verifyBackup')),
- h('button', { class: 'btn-block', onClick: finishClaim }, t('skipVerification'))
- )
+ // a heads-up before the words, then the words, then one word to
+ // check (Adam, 2026-11-07) — the same three steps as a new wallet
+ !ui.claimShown
+ ? h('div', { class: 'card col' },
+ h('h3', { style: 'margin-top:0' }, t('recoveryPhrase')),
+ h('p', { style: 'margin:0' }, t('seedIntroBody', { n: words.length })),
+ h('p', { class: 'muted', style: 'margin:0' }, t('seedIntroPrivate')),
+ h('button', { id: 'claim-seed-show', class: 'btn-primary btn-block', onClick: () => { ui.claimShown = true; render(); } }, t('seedIntroShow')))
+ : h('div', { class: 'card col' },
+ h('h3', { style: 'margin-top:0' }, t('recoveryPhrase')),
+ h('div', { class: 'words', style: '--rows-3:' + Math.ceil(words.length / 3) + ';--rows-2:' + Math.ceil(words.length / 2) },
+ words.map((w, i) => h('div', { class: 'w' }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, w)))
+ ),
+ h('div', { class: 'row gap6' }, copyBtn(wallet.mnemonic, t('copyPhrase'))),
+ h('button', { id: 'claim-seed-next', class: 'btn-primary btn-block', onClick: () => { ui.confirm = pickConfirm(words); ui.claimError = ''; ui.claimStep = 'verify'; render(); } }, t('next')))
);
}
if (ui.claimStep === 'verify') {
- // Same word-confirmation as new-wallet creation: prove the recipient wrote
- // the phrase down before sending them off into their freshly funded wallet.
const words = wallet.mnemonic.split(' ');
+ const q = ui.confirm;
+ if (!q || !q.options) { ui.claimStep = 'backup'; ui.claimShown = true; return claimScreen(); }
return h(
'div',
{ class: 'col', style: 'gap:16px' },
brandHeader(false),
h('div', { class: 'card col' },
h('h3', { style: 'margin-top:0' }, t('recoveryPhrase')),
- h('p', { class: 'muted', style: 'margin:0' }, t('confirmBackupIntro')),
- ...ui.confirm.map((c, i) =>
- h('label', { class: 'field' },
- h('span', { class: 'lab' }, t('wordN', { n: c.index + 1 })),
- h('input', {
- type: 'text', class: 'mono-input', autocapitalize: 'none', autocomplete: 'off', spellcheck: 'false',
- value: c.value, onInput: (e) => (ui.confirm[i].value = e.target.value.trim()),
- })
- )
- ),
- ui.claimError && h('div', { class: 'notice err' }, ui.claimError),
- h('div', { class: 'row gap6' },
- h('button', { class: 'btn-ghost', onClick: () => { ui.claimStep = 'backup'; ui.claimError = ''; render(); } }, t('back')),
+ h('p', { style: 'margin:0' }, t('quizPrompt', { n: q.index + 1 })),
+ h('div', { class: 'quiz-opts' }, q.options.map((w) =>
h('button', {
- class: 'btn-primary grow',
+ type: 'button',
+ class: 'quiz-opt' + (q.wrong.includes(w) ? ' wrong' : ''),
onClick: () => {
- const ok = ui.confirm.every((c) => c.value.toLowerCase() === words[c.index]);
- if (!ok) { ui.claimError = t('wordsMismatch'); render(); return; }
- finishClaim();
+ if (w === words[q.index]) { ui.claimError = ''; finishClaim(); return; }
+ if (!q.wrong.includes(w)) q.wrong.push(w);
+ ui.claimError = t('quizWrong'); render();
},
- }, t('openWallet'))
- ),
- h('button', { class: 'btn-block', onClick: finishClaim }, t('skipVerification'))
+ }, w))),
+ ui.claimError && h('div', { class: 'notice err' }, ui.claimError),
+ h('button', { id: 'claim-seed-skip', class: 'btn-ghost btn-block', onClick: () => { ui.claimError = ''; finishClaim(); } }, t('skip')),
+ h('button', { class: 'linklike small', style: 'align-self:center', onClick: () => { ui.claimStep = 'backup'; ui.claimShown = true; ui.claimError = ''; render(); } }, t('quizBack')),
)
);
}
@@ -366,7 +364,7 @@ export function giftsFeature(ctx) {
// view the claim on a block explorer, or head back to the home screen.
h('button', { class: 'btn-primary btn-block', onClick: () => {
ui.claimTaken = null; ui.claimedAmount = 0;
- if (activeAccount() && activeAccount().provisional) { ui.claimStep = 'backup'; commitAccount(); }
+ if (activeAccount() && activeAccount().provisional) { ui.claimStep = 'backup'; ui.claimShown = false; commitAccount(); }
else { ui.screen = 'wallet'; ui.claimStep = null; landAccount(); }
render();
} }, activeAccount() && activeAccount().provisional ? t('createWalletAnyway') : t('goToWallet')),
@@ -488,7 +486,7 @@ export function giftsFeature(ctx) {
commitAccount(); // claimed — keep a fresh gift wallet (no-op for an existing one)
if (ui.claimFrom) hook('followGiftSender', ui.claimFrom);
if (wasProvisional) {
- ui.claimStep = 'backup'; // fresh wallet — show the new seed to back up
+ ui.claimStep = 'backup'; ui.claimShown = false; // fresh wallet — show the new seed to back up
} else {
ui.screen = 'wallet'; // existing wallet — straight into it, no new seed to back up
ui.claimStep = null;
@@ -1089,9 +1087,11 @@ export function giftsFeature(ctx) {
return { blob, claimCode, url: `${location.origin}/lg/${blob}` };
},
receiveTakeover() { return claimCelebration(); },
- sendView() { return ui.giftMode ? giftView() : null; },
+ sendView() { return ui.giftMode === 'lnurl' ? wl.view() : ui.giftMode ? giftView() : null; },
sendFormExtras() {
- return h('button', { class: 'linklike small', style: 'align-self:center;margin-top:2px', onClick: () => { ui.giftMode = true; ui.sendError = ''; render(); } }, '🎁 ' + t('giftLink'));
+ return h('div', { class: 'row gap6 wrap', style: 'justify-content:center;column-gap:18px' },
+ h('button', { class: 'linklike small', style: 'margin-top:2px', onClick: () => { ui.giftMode = true; ui.sendError = ''; render(); } }, '🎁 ' + t('giftLink')),
+ wl.entry());
},
balanceLines() {
const locked = wallet.giftLockedValue();