aboutsummaryrefslogtreecommitdiffstats
path: root/build.js
Commit message (Collapse)AuthorAgeFilesLines
* Actually ship the NWC feature, and guard against the drift that hid itAdam Soltys2026-08-011-2/+4
| | | | | | | | | | | | | | build.js swaps src/features/index.js for a generated module listing only the features in its own ALL_FEATURES registry, so registering nwc in index.js alone was not enough: its i18n strings shipped (they live elsewhere) but its code never entered the bundle, and the settings card silently never rendered. - add nwc to build.js ALL_FEATURES, after ark since it drives ark's headless pay/balance seam - tools/feature-registry-check.js asserts the two lists agree on membership AND on construction order, since hook precedence depends on the order Co-Authored-By: Claude Fable 5 <[email protected]>
* Add NIP-57 Lightning zaps (npub / lightning address / lnurl)Adam Soltys2026-07-301-2/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | Send a Lightning zap to a nostr npub, a lightning address (name@domain), or an lnurl. New src/lnurl.js does the resolution half — target → LNURL-pay params → signed kind-9734 zap request → callback → bolt11 — and the new zaps feature (src/features/zaps.js) drives the amount/comment UI, then hands the invoice to the existing Boltz submarine-swap flow via a new swaps.startLnPay hook, so there's one tested pay/review/confirm/success path. Routing: a bolt11 is still caught by swaps; an npub still prefers an instant free Ark zap when Ark can serve it, and only falls through to a Lightning zap otherwise — ark's "no Ark address" screen also offers an explicit "Zap over Lightning" fallback (gated on the zaps feature's canLnZap). A real zap receipt is attributed only when the server advertises allowsNostr and we know the recipient's pubkey; otherwise it degrades to a plain LNURL-pay. Nostr seam gains nostrSign (finalize without publishing), nostrProfile (kind-0 with lud16/lud06), and nostrRelays. Lightning addresses are debounced in matchSendText since their regex matches mid-typing. Verified: unit tests for parse/encode/zap-request; a live resolve→signed 9734→payable mainnet bolt11 against coinos; and an in-browser DOM smoke (zap form + LNURL params over CORS + allowsNostr hint + handoff). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Gift cross-device sync, snapshot size cap, reliable SW updates, gift-detail ↵Adam Soltys2026-07-061-1/+8
| | | | | | | | | | | | | | | | | | | | | layout - Gifts now sync across devices: gift records + reserved/reclaimed coin sets ride the encrypted snapshot as a merge-safe extension (like ark), and every gift state change (create/reclaim/revoke) triggers a saveCache so it actually publishes. A gift made on one device now shows on the others. - Cap the published snapshot at ~40KB JSON (relay rejects >64KB events, nip44 expands ~1.5x): trim oldest on-chain txs when oversized; another device backfills via rescan. Typical wallets untouched. - Service worker: register with updateViaCache:'none' so a new build is detected immediately (not held by the 4h HTTP cache), and reload once on controllerchange so the fresh app runs without a manual second refresh. - Gift detail: View/Reclaim share a row, Revoke on its own full-width line so 'Revoke now' stops wrapping. Co-Authored-By: Claude Fable 5 <[email protected]>
* Features phase 2b: silent payments + nostr sync leave the core walletAdam Soltys2026-07-041-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
* Features phase 2: gift/swap/ark primitives leave the core walletAdam Soltys2026-07-041-2/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | wallet.js drops from 2,762 to 2,374 lines and now exposes two generic seams instead of feature code: - registerCoinLock()/lockedCoinIds(): features contribute coin ids that spending and the balance treat as unavailable (gift reservations). - loadFeatureState()/saveFeatureState(): namespaced per-wallet persisted state. src/features/gifts-wallet.js takes the whole gift engine — reservation sets, gift records, coin selection, the SIGHASH_SINGLE/NONE PSBT builder, the compact codec, claim building, nostr-locked gifts — as an installGiftWallet(wallet) instance install plus standalone exports. Swap storage/key helpers (loadSwaps/saveSwaps/swapNode/nextSwapIndex) install from swap.js; ark state storage installs from the ark feature. The silent-payment scan worker only embeds when the sp feature ships (wallet.js already tolerated its absence). Minimal profile: 412KB, down from 476KB (full: 578KB). All suites pass: ark UI e2e, ArkManager crash-resume, gift lifecycle, minimal smoke. SP's engine (scanning, BIP-352 signing) stays in wallet.js — extracting it safely wants an SP e2e harness on this stack first. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
* Build profiles: HAL_FEATURES selects which features shipAdam Soltys2026-07-041-1/+29
| | | | | | | | | | | | | | | | | | | | | | build.js swaps src/features/index.js for a generated module importing only the enabled features (gifts,swaps,ark,sp — unset means all, "none" means a plain on-chain wallet), so excluded feature code and its network endpoints never enter the bundle. Minimal profile: 476KB vs 577KB, with the Ark/gRPC, Boltz, and MuSig2 code verifiably absent. `bun run build:minimal` builds it. tools/minimal-smoke.js proves the minimal profile end-to-end: no receive-mode dropdown, no gift button, no swap/Ark/SP settings cards, no page errors, and a plain on-chain receive still lands. The two blessed profiles are full (everything, the existing e2e suites) and minimal (this smoke test); other combos should work but aren't CI'd. Gift and SP primitives still live inside wallet.js, so those bytes remain in the minimal bundle for now — extracting them is the next step if we want the core leaner still. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
* Run silent-payment scanning in a Web WorkerAdam Soltys2026-06-281-0/+17
| | | | | | | | | | | | | | | | | The EC crypto (candidate derivation + full scan) now runs in a worker, off the main thread, so even a large mainnet scan can't jank the UI at all. The worker is bundled separately and embedded as base64 (single-file app), spawned from a Blob URL; it holds the keys + per-tweak cache. If workers are unavailable or the bundle is missing, scanning falls back to the main thread (still cached + yielding). scanSilentPayments / _scanSpItems / _scanSpMempool now go through _spHitBlocks and _spScanItems, which dispatch to the worker (or the fallback). +27KB to index.html for the worker's crypto bundle. Verified via CDP: worker runs keys/candidates/scan correctly in-browser; a real reload + mainnet scan threw 0 exceptions and the SP address renders. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rename the app title from 'Hal Wallet' to just 'Hal'Adam Soltys2026-06-251-3/+3
| | | | | | | Header, browser tab title, PWA name, and apple web-app title. Tribute prose (explaining the name honors Hal Finney) left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add dark mode; gift amount unit toggle; multi-input giftsAdam Soltys2026-06-241-1/+2
| | | | | | | | | | | | | | - Dark mode: theme variables + a footer "Dark mode/Light mode" toggle, persisted and applied pre-paint via a small head script (defaults to the OS preference). Hardcoded surfaces moved to CSS variables. - Gift amount: the unit label is now the clickable sats/BTC toggle, and the amount is parsed in the selected unit. - Gifts can now span multiple coins: createGift selects several confirmed coins (first input SIGHASH_SINGLE to lock the change, the rest SIGHASH_NONE), so a gift larger than any single coin works. Claim/preview sum all inputs and size the fee by input count. Verified end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Split translations into per-language files, lazy-loadedAdam Soltys2026-06-241-3/+22
| | | | | | | | | | | | | | | | Only English is bundled in index.html now; the other 19 languages are emitted as dist/locales/<code>.json and fetched on demand when a visitor actually uses one (via auto-detect, the picker, or ?lang=xx). This drops index.html from ~511KB to ~318KB; a non-English visitor fetches just their one ~8-10KB locale, which the service worker then caches for offline use. t() falls back to English until a locale resolves. - i18n.js keeps only en + a loadLocale() fetcher; locale data moved to src/locales/*.json. - build.js emits dist/locales/; dev.js serves /locales/<code>.json. - Deploy hook publishes the locales/ subdir. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rebrand to Hal Wallet; roomier language-select caretAdam Soltys2026-06-241-4/+4
| | | | | | | | | | | - Name the app "Hal Wallet" (header, title, PWA manifest, package.json). A nod to Hal Finney — just the name, no taglines or tributes. - appTitle is a brand name now, so it's constant across locales (dropped the translated overrides; they fall back to "Hal Wallet"). - Replace the native <select> caret with a custom chevron spaced off the right border (the native one is crammed against the edge in Chrome). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add QR scanner to the send page (lazy-loaded decoder)Adam Soltys2026-06-231-2/+22
| | | | | | | | | | | | | | | | | | | Scan a Bitcoin address, a BIP21 URI (fills address + amount), or a signed-transaction QR (opens a broadcast confirmation, then relays it) from a viewfinder button on the Send form. Decoder strategy keeps the main bundle small: - Prefer the native BarcodeDetector (Chrome desktop/Android) — no lib. - Otherwise lazy-load jsQR from a separate dist/jsqr.js, only when the scanner is opened. jsQR stays out of index.html (~424 KB vs ~551 KB) and off the initial load. - The service worker precaches jsqr.js so offline scanning works for installed PWAs on browsers without BarcodeDetector. The scanner needs a camera + secure context, so it never shows on a standalone file:// page; offline air-gapped signing is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix false "payment received" on import; PWA; send-page fontAdam Soltys2026-06-221-4/+91
| | | | | | | | | | | | | | | - Don't celebrate historical payments on import. receiveSeenIndex was baselined to 0 before the scan ran, so when the scan advanced the index every past payment looked new and flashed "payment received". Leave it null until the post-scan ack logic baselines it. - PWA: add a web manifest, ₿ icons (192/512 + maskable), theme/apple meta tags, and a service worker that precaches the shell so the installed app works offline. index.html stays self-contained; the sidecar files just 404 harmlessly from file://. build.js emits them. - Send page: drop btn-sm from the amount unit toggle so "sats" matches the "Max" button size (both 16px). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rebuild as single-file offline BIP84 walletAdam Soltys2026-06-141-0/+53
Replace the SvelteKit/daisyUI prototype with a vanilla JS + hand-rolled CSS app that Bun bundles (code + scure/noble crypto + QR + CSS) into one self-contained dist/index.html runnable offline from file://. Wallet core (src/wallet.js): - BIP84 native-segwit HD wallet from BIP39 seed (+optional passphrase) - gap-limit scan of receive/change chains, aggregate balance, tx history - fresh receive/change addresses; signs with witnessUtxo only (enables offline signing with no prev-tx fetch) - coin selection + fee estimation via btc-signer selectUTXO; send-max; manual coin control - offline JSON snapshot export/import (keyless) + signed-tx hex/QR export - realtime mempool.space WebSocket: track-addresses -> debounced auto refresh of balances/history (no polling); live status badge UI (src/app.js): clean light paper-wallet; create (with optional, skippable backup verification) / import flow; receive (QR) / send / history / coins / tools tabs; clickable logo returns home. API (src/api.js): mempool.space wrapper with retry/backoff for scan-burst rate limiting; single network choke-point for offline enforcement. Verified: BIP84 spec vectors, build/sign/parse for auto/max/manual flows, snapshot roundtrip, live scan + WebSocket, headless-Chrome UI smoke tests. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>