aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/gifts.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-04 12:35:15 -0700
committerAdam Soltys <[email protected]>2026-07-04 12:35:15 -0700
commitcc2c05d2a1b7a9601eb3cb0df998483099e590cc (patch)
tree53f388790f8cca263c15d6d870a36ea283541995 /src/features/gifts.js
parentb7c5abd93868b97c25f52610510544764a3e44eb (diff)
downloadcoinosv3-cc2c05d2a1b7a9601eb3cb0df998483099e590cc.tar.gz
coinosv3-cc2c05d2a1b7a9601eb3cb0df998483099e590cc.zip
Features phase 2b: silent payments + nostr sync leave the core wallet
wallet.js drops from 2,374 to ~1,970 lines, holding only BIP84 on-chain logic plus generic feature seams. New seams added this pass: registerBalanceExtra extra confirmed/pending buckets registerInputProvider extra spendable inputs for buildTx registerSendPreparer destinations needing post-selection derivation registerInputSigner non-BIP84 input signing registerHistoryProvider / registerScanHook / registerRealtimeHook / registerLoadHook / registerCacheExtension / registerCacheSavedHook src/features/sp-wallet.js: the whole BIP-352 engine — key derivation, worker-accelerated indexer scanning, the live indexer WebSocket, SP outputs as spendable inputs, sp1 send preparation, and per-output-key signing — installs via those seams (installSpWallet). src/features/sync.js: nostr identity + encrypted cross-device state sync plus its Settings card, installed via load/cache-saved/realtime hooks. profileChip/nostrProfile move here-adjacent (into gifts, the only other nostr consumer), so core app.js no longer imports nostr at all. buildTx/sign are now feature-agnostic (input providers + preparers + signers); _recomputeBalance* use the balance-extra registry, so a core build with no SP no longer calls a missing spBalance() (that regression dropped the minimal build into offline mode — caught by the smoke test). New coverage: tools/sp-test.js drives two browser "devices" through an SP send, indexer discovery, and an SP-input spend on the ark regtest chain (needs tools/sp-indexer.js pointed at bitcoind:18543). Minimal smoke now also asserts no SP/nostr code ships. Minimal profile: 314KB, down from 412KB (full: 580KB). All suites green: minimal smoke, sp-test, ark UI, gift lifecycle. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
Diffstat (limited to 'src/features/gifts.js')
-rw-r--r--src/features/gifts.js32
1 files changed, 30 insertions, 2 deletions
diff --git a/src/features/gifts.js b/src/features/gifts.js
index 9562b9f..b17f5da 100644
--- a/src/features/gifts.js
+++ b/src/features/gifts.js
@@ -13,7 +13,7 @@ export function giftsFeature(ctx) {
const {
h, ui, render, wallet, toast, copyBtn, pasteBtn, blankSend, goBack, openExternal,
fmtAmount, unitLabel, unitTag, parseAmount, getUnit, toggleUnit, download,
- brandHeader, profileChip, activeAccount, setAccounts, getAccounts, claimTargets,
+ brandHeader, activeAccount, setAccounts, getAccounts, claimTargets,
enterWallet, activateAccount, commitAccount,
} = ctx;
installGiftWallet(wallet); // gift primitives live outside the core wallet
@@ -441,7 +441,11 @@ export function giftsFeature(ctx) {
ui.giftCode = blob; ui.giftLocked = true; ui.giftClaimCode = claimCode; ui.giftDmStatus = 'sending';
wallet.recordGift({ code: blob, locked: true, amount, claimCode, outpoints: g.reserved });
const dmText = t('giftDmText', { amount: fmtAmount(amount) + ' ' + unitLabel(), link: giftUrl(), code: claimCode });
- wallet.sendNostrDM(pk, dmText).then((ok) => { ui.giftDmStatus = ok ? 'sent' : 'failed'; render(); }).catch(() => { ui.giftDmStatus = 'failed'; render(); });
+ if (wallet.sendNostrDM) {
+ wallet.sendNostrDM(pk, dmText).then((ok) => { ui.giftDmStatus = ok ? 'sent' : 'failed'; render(); }).catch(() => { ui.giftDmStatus = 'failed'; render(); });
+ } else {
+ ui.giftDmStatus = 'failed'; // no sync/nostr feature in this build — the claim code fallback shows
+ }
}
function giftCard() {
@@ -699,6 +703,30 @@ export function giftsFeature(ctx) {
render();
}
+ // --- nostr profiles (avatar + name) — cached, lazily fetched, re-renders ----
+ const _profileCache = new Map(); // pubkeyHex -> profile | null | 'loading'
+ function nostrProfile(pkHex) {
+ if (_profileCache.has(pkHex)) return _profileCache.get(pkHex);
+ _profileCache.set(pkHex, 'loading');
+ fetchNostrProfile(pkHex).then((p) => { _profileCache.set(pkHex, p || null); render(); }).catch(() => { _profileCache.set(pkHex, null); render(); });
+ return 'loading';
+ }
+ // A row showing the recipient's avatar + name (or a shortened npub fallback).
+ function profileChip(pkHex, { size = 30 } = {}) {
+ const p = nostrProfile(pkHex);
+ const npub = npubOf(pkHex) || pkHex;
+ const short = npub.slice(0, 12) + '…' + npub.slice(-4);
+ const avatar = (pic) =>
+ pic
+ ? h('img', { src: pic, style: `width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;flex:0 0 auto`, onError: (e) => { e.target.style.visibility = 'hidden'; } })
+ : h('div', { style: `width:${size}px;height:${size}px;border-radius:50%;background:#9993;flex:0 0 auto` });
+ if (p === 'loading') return h('div', { class: 'row gap6', style: 'align-items:center' }, h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, short));
+ const name = (p && p.name) || short;
+ return h('div', { class: 'row gap6', style: 'align-items:center;min-width:0' },
+ avatar(p && p.picture),
+ h('span', { class: p && p.name ? '' : 'small muted', style: 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, name));
+ }
+
// "Payment received!" takeover right after a gift claim (no scan wait).
function claimCelebration() {
// A gift was just claimed into this wallet — celebrate immediately (the