aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
* Point GitHub footer link at renamed repo (asoltys/halwallet)Adam Soltys2026-06-241-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Trim blockstream mention from explorer setting descriptionAdam Soltys2026-06-241-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add Hal Finney tribute line to How it worksAdam Soltys2026-06-243-2/+8
| | | | | | | | | A footnote at the bottom: "The name Hal Wallet is a tribute to legendary cypherpunk and Bitcoin pioneer, Hal Finney (1956–2014)", with his name linking to his Wikipedia article. The contextual nod, rather than leaning on his name up front. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Choose your own block explorer; flatten How it worksAdam Soltys2026-06-245-65/+113
| | | | | | | | | | | | | | | - Settings → Block explorer: pick mempool.space (default, with blockstream.info as a silent failover), blockstream.info only, or a custom Esplora/electrs REST URL (e.g. your own node). The realtime WebSocket only applies to mempool.space; other explorers fall back to polling. Switching rebuilds the API and rescans. Replaces the old undocumented explorer-proxy hack. - How it works: merge the "For the technically curious" disclosure into the main body so it's all visible under "The basics", and dedupe the overlapping no-server/offline sentences. Mentions the new explorer setting. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rebrand to Hal Wallet; roomier language-select caretAdam Soltys2026-06-242-20/+26
| | | | | | | | | | | - 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]>
* Configurable Nostr sync; fully reconcile wallet state on loadAdam Soltys2026-06-244-53/+179
| | | | | | | | | | | | | | | | | | | | | Device sync: - Add a "Device sync" settings card: toggle (on by default) + editable relay list (defaults to wss://relay.coinos.io). When off, the wallet never contacts a relay. nostr.js is now multi-relay and reads an enabled/relays preference from localStorage. Reconciliation (fixes a stray "payment received" + stale history on import, especially via the Nostr/cache load path): - Always full-scan on load instead of a frontier-only refreshLive, so balance, history confirmations and spends are reconciled and the celebration baselines against everything that already exists (cache/ Nostr still shows instantly first). - Actually start the periodic full-scan safety net (_deepTimer, 2 min); it was declared but never wired up. - WS pushes now refresh an existing tx's pending→confirmed status, not only add new transactions. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Reconcile spends of held coins on refreshAdam Soltys2026-06-231-0/+58
| | | | | | | | | | | | refreshLive only walked the frontier forward, so a coin spent from an already-scanned address never reconciled — the balance stayed stale until a full rescan, even after the spend confirmed (e.g. a tx broadcast from another device). Add _reconcileHeld: re-verify every address that currently holds a UTXO and rebuild its balance/UTXOs from the explorer, catching spends whether pending or confirmed. Runs in the refreshLive path (load, poll, WS), so a refresh now self-corrects. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Debit balance optimistically on pending send; pointer cursor on summariesAdam Soltys2026-06-232-1/+4
| | | | | | | | | | | | | - Headline balance now shows wallet.total (confirmed + mempool) instead of confirmed, so a pending outgoing spend is debited immediately rather than waiting for confirmation. The chain_stats-based confirmed balance counts a coin as yours until the spend is mined, which is why it didn't drop after broadcasting (incl. scanned-tx broadcasts). Incoming pending is still surfaced by the "+X pending" line. - summary { cursor: pointer } so the "Show QR" disclosure (and any other <summary>) shows the hand cursor. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show full recipient address below the input for verificationAdam Soltys2026-06-232-2/+31
| | | | | | | | | The address input scrolls/truncates on mobile, hiding the ends. Render the full address as wrapping text beneath it with the first/last 6 chars emphasized, so paste/type/scan can be verified without scrolling. Updated imperatively on input so the field keeps focus. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add QR scanner to the send page (lazy-loaded decoder)Adam Soltys2026-06-236-0/+302
| | | | | | | | | | | | | | | | | | | 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]>
* Allow signed-tx export when online; clarify it isn't sentAdam Soltys2026-06-232-3/+9
| | | | | | | | | | | - Add a "Sign & export (broadcast elsewhere)" action on the online review screen so you can sign here and broadcast from another device, not just in offline mode. - The signed-result screen now reads as an amber caution ("Signed, but not sent yet… it will not send on its own") instead of a green success banner, so it can't be mistaken for a completed send. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Match Hide button font to Copy phrase in settingsAdam Soltys2026-06-231-1/+1
| | | | | | | The reveal-phrase Hide button used the default size (16px) next to the btn-sm Copy phrase button (13px). Add btn-sm so they match. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add PWA install affordance; serve manifest as manifest+jsonAdam Soltys2026-06-232-1/+35
| | | | | | | | | | | Chrome no longer shows an automatic install prompt, so catch beforeinstallprompt and surface an "Install app" link in the footer that replays the prompt on a user tap (hides once installed). (nginx on the host now serves manifest.webmanifest as application/manifest+json instead of octet-stream.) 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/+9
| | | | | | | | | | | | | | | - 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]>
* Trim How it works technical sectionAdam Soltys2026-06-212-8/+23
| | | | | | | | | | - Drop the @scure/@noble crypto paragraph and the passphrase paragraph. - Link mempool.space in the no-backend paragraph; trim its trailing "only your addresses leave the device" clause. - "This page is one static HTML file" (was "The whole thing"). - Remove the cold-storage sentence from the offline paragraph. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make How it works copy more human; tidy passphrase fieldAdam Soltys2026-06-213-19/+12
| | | | | | | | | | | | | - Rewrite the How it works page in a plainer, less generated voice; drop the intro lead, the receive/send paragraph, and trim the rest. - Fix passphrase wording: "13th word" (12-word phrase), not 25th. - genIntro: "It's" instead of "It is". - Unlock screen: label the field "Passphrase" (was "Optional passphrase") and drop the "leave blank if unused" placeholder. - Remove now-unused hiwLead / optionalPassphrase / leaveBlank keys and the .lead style. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add How it works page, plainer copy, faster import scanAdam Soltys2026-06-194-156/+1373
| | | | | | | | | | | | | | | | | | | | | | | | | | | | UI / copy: - Footer: "made by Adam Soltys" (drop the heart) + a "How it works" link on its own line, matching the footer's size. - New "How it works" page: plain-language basics plus an expandable "for the technically curious" section. Routed via ui.screen, with Back/logo returning to wherever you opened it from. - Scrub jargon from the main UI for newcomers: drop BIP39, soften "raw hex" -> "code", "txid" -> "transaction ID", "air-gapped" -> "offline"; replace em dashes in the intro/keys notes. The technical terms now live only in the How it works technical section. Import / scan performance (fewer requests -> fewer 429s, faster): - Skip the /utxo fetch for fully-settled, zero-balance addresses (they can't hold UTXOs) -- the bulk of a history-heavy wallet. - Show balance as soon as the address scan finishes, then load UTXOs and history in the background. - Probe the receive and change chains concurrently so their per-hit delays overlap. - History fills in progressively as each address resolves, with a loading indicator on the History tab. i18n.js is now tracked; new English keys fall back to English for the other languages until translated. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add Skip verification on the verify screen tooAdam Soltys2026-06-161-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Tidy tx details layout on mobileAdam Soltys2026-06-162-8/+7
| | | | | | | | | Fix the key/value rows wrapping awkwardly on narrow screens (right-align + wrap-safe values, fixed-width key). Drop the redundant "When: Unconfirmed…" line; pending now shows a single "Status: Pending (in mempool)". Confirmed txs show Status / Block / Date. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add local transaction details pageAdam Soltys2026-06-161-2/+45
| | | | | | | | | Clicking a history item now opens an in-wallet details view (status, block, time, amount, fee, full copyable txid) instead of jumping straight to mempool.space — works offline from cached data, with mempool as an optional outbound link. Back returns to the list; switching tabs/logout resets it. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop pending-row tint; keep the pending badgeAdam Soltys2026-06-162-2/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show pending txs in history with a clear unconfirmed indicatorAdam Soltys2026-06-163-5/+23
| | | | | | | | | WS-detected deposits now get added to history (previously the ingest advanced past the address so the tx never entered history — balance showed but History was empty). Pending txs render with a "⏳ pending" badge, an orange-tinted row, and "awaiting confirmation" so they're clearly distinct from confirmed ones. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Slightly larger heart in footerAdam Soltys2026-06-161-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Persist payment-received ack across reloads; footer + READMEAdam Soltys2026-06-162-5/+30
| | | | | | | | | | | - The "Payment received!" celebration now persists: the acknowledged fresh index is stored (per seed) in localStorage, so a received payment keeps showing the dialog until you tap past it, even after a refresh. - Footer: only "GitHub" is the link now. - README rewritten for the current state (mainnet-only, realtime, Nostr sync, multi-recipient, sats default, explorer resilience, etc.). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add "Payment received!" celebration on the Receive tabAdam Soltys2026-06-162-0/+35
| | | | | | | | | | When a deposit lands on the shown address, the Receive tab now shows a "Payment received!" dialog (with the amount and a green check) and waits for a tap before revealing the next address/QR — instead of silently swapping it. Tracks the acknowledged fresh index so only payments that arrive while watching trigger it. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Advance fresh address instantly on WS depositAdam Soltys2026-06-161-3/+18
| | | | | | | | | The pending balance showed immediately from the WS ingest, but the next receive address/QR only appeared after the debounced refreshLive. Now the WS ingest marks the receiving address used and advances the fresh index right away, so the new address shows in the same update as the balance. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add footer; default to public explorers (proxy via localStorage opt-in)Adam Soltys2026-06-153-4/+29
| | | | | | | | | | - Footer: "made with ♥ by Adam Soltys" (links adamsoltys.com) and "source available on GitHub" (links the repo). - Explorer endpoint now defaults to the public explorers; a rate-limited machine can opt into a proxy via localStorage('explorer-proxy'). Correct for a hosted multi-user deploy (each visitor uses their own IP). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Incremental frontier refresh (no full re-scan on deposit)Adam Soltys2026-06-151-46/+97
| | | | | | | | | | refreshLive escalated to a full scan() when the fresh address changed, which re-queried every coin's /utxo and /txs. Rewrite it to walk the frontier incrementally: advance through newly-used addresses, fetching /utxo + /txs for only those, and never revisit already-scanned coin addresses. WS events use the same path. A new deposit now costs ~5 requests instead of a full re-scan. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Watch only the fresh receive + change address over the WSAdam Soltys2026-06-151-7/+9
| | | | | | | | | | mempool's track-addresses caps at 10 per connection; we were sending all coin addresses + a 15-wide frontier, so the subscription was rejected and realtime updates silently stopped. With the frontier model (coins handled by post-send refresh + Nostr sync), only the next receive and change address need watching — so track exactly those two. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Sync wallet state across devices via encrypted Nostr eventsAdam Soltys2026-06-153-30/+176
| | | | | | | | | | | | | | Persist wallet state (addresses, utxos, history, indices, fees) as a NIP-44 encrypted, parameterized-replaceable event (kind 30078) addressed to self on relay.coinos.io. The Nostr identity is derived from the same seed (NIP-06), so no extra secret. On load we pull the latest state from Nostr — a device with no local cache gets full state without re-scanning, and another device's updates propagate — using savedAt to keep the newest copy. localStorage stays as the instant/offline cache; state is published (debounced) on every change. (Relay note: added kind 30078 to the strfry write-policy allowlist on prod.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Stop re-scanning coin addresses; frontier-only poll + manual rescanAdam Soltys2026-06-152-14/+25
| | | | | | | | | | | Per request: an address is scanned once, then never auto-scanned again. The routine poll now checks only the fresh frontier (next receive + change address) for new deposits; coin-holding addresses are no longer polled and the periodic full re-scan is removed. Spends on existing coins arrive via the WebSocket while connected; a new Settings → Rescan button does a full scan to catch anything missed (e.g. a payment to a reused old address). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use cached state on reload; light poll instead of full re-scanAdam Soltys2026-06-151-2/+5
| | | | | | | | | | | On load we restored the cache for instant display but then always ran a full scan (every address's /address + /utxo + /txs) — so a refresh re-fetched everything. Now when a cache exists, the load path runs the light frontier poll (only live addresses, no utxo/txs re-fetch) and escalates to a full scan only if something changed. A brand-new wallet still does a full scan, and the periodic deep scan still runs. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Route explorer traffic through VPS proxy (temporary)Adam Soltys2026-06-152-10/+36
| | | | | | | | | | This IP is rate-limited/banned by mempool.space, so REST + WS now go through an nginx CORS proxy on the OVH VPS (mempool via /mp, blockstream via /bs). Controlled by a single PROXY constant in api.js — set it to '' to revert to the public explorers once the ban is lifted. Hosts are tagged by kind so fee format detection still works. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add per-request timeout + failover for unresponsive hostsAdam Soltys2026-06-151-2/+24
| | | | | | | | | | With concurrency 1, a single hung request (no response) stalled the whole serialized queue indefinitely — so balances/notifications never updated and the console filled with ERR_TIMED_OUT. Each request now has a 10s abort timeout; on timeout/network error the host is parked (20s) and we fail over to the other explorer. Applies to broadcasts too. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Cache stable addresses; poll only the live frontierAdam Soltys2026-06-151-4/+50
| | | | | | | | | | | Idle polls were re-querying every used address (~N requests each cycle). Now the background poll checks only addresses that can change — those holding coins and the next fresh receive/change address — and escalates to a full scan only when one of them moves. Stable historical addresses stay cached. A full scan still runs every 5 min as a safety net. Cuts steady-state request volume dramatically for wallets with many used addresses. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Prefer mempool, cooldown 429'ing hosts (stop Blockstream storm)Adam Soltys2026-06-151-16/+33
| | | | | | | | | | Round-robin sent half of every scan to Blockstream, which hard-rate-limits this IP (429 on each), and we kept routing back to it — stalling scans so new balances never showed. Now hosts are tried in order (mempool first) and any host that 429s is parked for 30s, so traffic stays on the healthy host and Blockstream is fallback-only. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Keep the WebSocket alive: heartbeat + reconnect-on-wakeAdam Soltys2026-06-151-0/+47
| | | | | | | | | | | The mempool WS sends no periodic traffic on a track-addresses-only subscription, so a half-open connection (died without firing onclose) went undetected and updates fell back to the slow poll. Add: - ping every 15s ({action:'ping'} -> pong); if no message for 45s, force a reconnect (handles half-open sockets). - reconnect + refresh on tab refocus (visibilitychange) and network 'online'. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Broadcast sends to all explorers in parallelAdam Soltys2026-06-151-15/+15
| | | | | | | | Post the signed tx to both mempool.space and Blockstream at once (outside the throttle, since a send is urgent) so it's visible regardless of which explorer the recipient watches. Succeeds if any accept; only fails if all do. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Apply WS tx payload instantly; faster reconcileAdam Soltys2026-06-151-1/+41
| | | | | | | | | | | | | | The mempool.space WebSocket push already contains the transaction, so parse it on arrival to add the incoming UTXO and bump pending immediately, instead of waiting on a debounced re-scan. The scan still follows (400ms) to reconcile spends/confirmations. Removes the ~3s our handler added on top of mempool.space's own push latency. (Measured: mempool.space itself takes ~8s to see a coinos-broadcast tx due to p2p propagation — the real fix for incoming-payment latency is the sender pushing directly to the explorer.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Global rate-aware request proxy + lighter pollingAdam Soltys2026-06-152-84/+110
| | | | | | | | | | | | | | 429 handling: route every outgoing request (address, utxo, txs, fees, broadcast) through one scheduler. A 429 from ANY request now pauses ALL subsequent requests with exponential backoff (2s→30s), easing off after a streak of successes — instead of only cooling down the same host/kind. Fewer requests: derive balance from /address chain_stats (no /utxo needed just to total), and on background polls fetch only /address. The heavier /utxo + /txs are pulled only on first load or when the balance/addresses actually change. Idle polls drop from ~3N requests to ~N. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Default to sats unit for first-time usersAdam Soltys2026-06-151-2/+3
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show confirmed as "Balance"; pending only when presentAdam Soltys2026-06-151-9/+9
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Straighten unit-label underline (zero inherited border-radius)Adam Soltys2026-06-151-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use solid underline for clickable unit labelsAdam Soltys2026-06-151-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Multi-recipient send, simpler review, global persisted unitAdam Soltys2026-06-152-70/+119
| | | | | | | | | | | | | | - Send: support multiple outputs. Default one recipient with a "+ Add recipient" button (and a remove ✕ per row). Max applies only when there's a single recipient. - Review: drop the technical lines (total, inputs/vsize, change, total cost). Show just the amount going to each output plus the network fee. - Units: replace the per-form BTC/sats toggle with a single global unit that persists in localStorage across refreshes and logouts. Every unit label (balance, breakdown, amounts, fee, coins, review) is clickable to toggle and all displays use the chosen unit consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Normalize amount input out of scientific notationAdam Soltys2026-06-151-1/+13
| | | | | | | | Chrome's number input emits "1e-8"-style values when stepping the tiny BTC step; convert any scientific-notation value back to a plain decimal string on input. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Set amount step to 0.00000001 for BTC (1 for sats)Adam Soltys2026-06-151-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use integer amount placeholder when unit is satsAdam Soltys2026-06-151-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Default custom fee to the economy rateAdam Soltys2026-06-151-1/+7
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Hide "Selected rate" line when entering a custom feeAdam Soltys2026-06-151-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>