aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
* Already-claimed gift: offer 'Create a wallet anyway' or go homeAdam Soltys2026-06-252-4/+13
| | | | | | | | The claim flow already generated a fresh wallet, so the dead-end screen now lets the opener keep it (→ back up the recovery phrase → into the wallet) or head to the home screen. The backup step's copy adapts (Claimed! vs Your new wallet). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Persist the active tab + open tx across refreshAdam Soltys2026-06-251-1/+13
| | | | | | | | | Save the wallet-screen nav (tab and open tx detail) to sessionStorage on each render and restore it on load, so refreshing keeps your place instead of jumping back to Receive. A restored tx detail re-fetches its fee/details if missing. Gift links still open the claim screen. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Lazily fetch a receive's fee when opening its detailAdam Soltys2026-06-251-1/+25
| | | | | | | | | A watcher-credited receive arrives with no fee (the push can't compute it from the raw tx, and the reduced reconcile doesn't revisit the now-used address). On opening the tx detail, fetch the full tx from the explorer to fill in the fee (and confirmations) — works for pending mempool txs too. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Celebrate a received payment once; don't re-show after refresh/reopenAdam Soltys2026-06-252-6/+10
| | | | | | | | Persist the receive-ack the moment the 'Payment received!' screen is shown (not only on tap), so refreshing or navigating away without tapping won't bring it back. It still stays visible in the current session until dismissed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show the network fee on the tx detail page for receives tooAdam Soltys2026-06-251-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Remove the gift-link description blurbAdam Soltys2026-06-251-1/+0
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Trim gift minimum note to just 'Minimum {n}.'Adam Soltys2026-06-251-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Remove the gift-link warning boxAdam Soltys2026-06-251-1/+0
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Compact gift links: base32 codec + uppercase /g/ path for a smaller QRAdam Soltys2026-06-253-18/+98
| | | | | | | | | | | | | | | | | | 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]>
* Trim the tribute line to just 'a tribute to Hal Finney (1956–2014)'Adam Soltys2026-06-251-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gift QR: use error-correction level L for a less dense, easier-to-scan gridAdam Soltys2026-06-252-3/+6
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gift claim: show a loading screen while checking, not the claimable amountAdam Soltys2026-06-252-0/+16
| | | | | | | | Avoids flashing 'You've been sent Bitcoin' before flipping to 'Already claimed' for a spent gift. Offline fallback clears the checking flag so the loader can't hang. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gift claim: detect already-claimed (even unconfirmed) and block the raceAdam Soltys2026-06-254-3/+79
| | | | | | | | | | | | Opening a gift link now checks whether its funding coin is already spent — via the Esplora outspend endpoint, which reports a spend even by an unconfirmed claim. The Claim button is gated until that check passes; if the coin is spent, show an 'Already claimed' screen with a link to the claim tx instead of a claimable amount. doClaim also re-checks on broadcast failure (the race window), and refuses to run while unchecked/taken — so a second opener can't attempt a doomed double-spend. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Claimed screen: big green check instead of green-on-orange badgeAdam Soltys2026-06-251-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Confirm transactions via the WebSocket push, not RESTAdam Soltys2026-06-251-37/+46
| | | | | | | | | | The watcher's block (rawblock) push now carries confirmed=true + the block time, and _applyTxNotification flips the matching history row and coins from pending to confirmed directly — for incoming and outgoing txs (flag-only, so no overcount). Confirmations no longer wait on a mempool.space reconcile; the 30s backstop poll just fills in block height / fee afterward. 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-1/+1
| | | | | | | 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]>
* Remove the Live/Polling badge; shorten explorer link to 'Block explorer'Adam Soltys2026-06-252-11/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Credit payments instantly from the watcher push (no REST on critical path)Adam Soltys2026-06-251-23/+89
| | | | | | | | | | | | | | | | | | The watcher push now carries the tx data (matched outputs + confirmed flag), so the app credits an incoming payment the moment the push arrives — no mempool.space round-trip. This removes the rate-limited REST reconcile from the path a user actually waits on, and lets us back the frontier poll way off when the socket is live (30s backstop vs the old 3s receive-tab hammer), cutting the 429s to a trickle. - watcher: notification includes { txid, confirmed, outputs:[{vout,value}] }. - wallet: _applyTxNotification credits the receive coin + a pending history row optimistically (receives only; change reconciles in the background), advances the frontier, updates the balance, and schedules a background reconcile for full accuracy off the critical path. - poll intervals relaxed now that the WS does the real work instantly. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Realtime via self-hosted ZMQ watcher (Electrum-over-WS)Adam Soltys2026-06-252-108/+93
| | | | | | | | | | | | | | | | | | | Replace the unreliable public WebSocket push (mempool/blockchain.info address subscriptions never reliably fire) with a private notification service: - tools/zmq-watcher.js: a standalone Bun service (reusing coinos lib/zmq.ts's ZMTP subscriber) that reads Bitcoin Core's ZMQ rawtx/rawblock, computes the scripthash of each output, and pushes a blockchain.scripthash.subscribe notification to subscribed clients. No historical index (~24MB RAM) — just the live tx stream. Runs on the coinos box reading its Core's ZMQ. - wallet.js: realtime client now speaks the Electrum protocol (server.version, scripthash.subscribe, server.ping) over the WS; any scripthash notification triggers the existing refreshLive() (REST stays the source of truth). - api.js: wsUrl -> wss://coinos.io/electrum. Verified end-to-end: live mempool txs to subscribed addresses notify instantly. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fast-poll the receive address while watching for a paymentAdam Soltys2026-06-252-1/+41
| | | | | | | | | | | The WS badge reads Live but mempool.space's free address tracking doesn't reliably push incoming mempool txs, so a deposit could take ~18s to appear (the 25s safety poll catching it). While the user is on the Receive tab (online), now poll just the fresh receive address every ~3s regardless of Live state — one cheap request that guarantees near-instant arrival. Toggled idempotently from render() based on the active tab; stops when they navigate away. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Faster WS reconnect; stop double-running the held-coin reconcileAdam Soltys2026-06-251-1/+9
| | | | | | | | | | | | - _scheduleReconnect now backs off exponentially from ~0.6s (was a flat 4s), so a first socket attempt that's transiently rejected retries fast — the wallet reaches Live in well under a second instead of ~4s. Backoff resets on open. - refreshLive now stamps _lastPoll at its start, so a socket-open refresh and the poll-timer tick don't both fire reconcileHeld back-to-back on load. A cached load does one held-coin reconcile (verify coins still unspent + frontier check) instead of ~3x. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Stop full-scanning the API on every load; go Live before NostrAdam Soltys2026-06-251-9/+17
| | | | | | | | | | | | | | | | The refresh request storm (and the 429s) was a full API re-scan running on every load even when the local cache or Nostr already held the complete state. Now the full scan runs ONLY when there's neither cache nor Nostr (a seed on a fresh device); otherwise the socket + frontier poll keep things current and reconcile held coins — a handful of requests instead of re-querying every historical address plus its utxos and txs. Also reordered: startRealtime() now runs BEFORE syncFromNostr (which blocks up to 6s waiting on the relay) and before any discovery scan, so the wallet goes Live immediately instead of sitting in Polling. retrack() after sync/scan re-subscribes to the final frontier; a fresh open re-baselines its celebration against it. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Detect stuck low-fee txs and stop actively monitoring themAdam Soltys2026-06-243-5/+41
| | | | | | | | | | | | | | | A pending tx is judged 'stuck' once it has waited ~10 blocks (≈100 min) and its fee rate (fee/vsize) is below the mempool's current floor (minimumFee) — i.e. it's been purged and won't confirm without a bump. _txSummary now records vsize and a firstSeen timestamp (persisted in cache) to support this. Stuck txs are dropped from active monitoring: excluded from the watched-address set and from the fast-poll trigger, so they don't hold a socket slot or keep us in the 6s lane. The slow ~25s reconcile still catches one if the floor later drops and it confirms. History flags them (⚠ stuck) and the detail view explains it — pointing outgoing ones at the existing fee bump. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Watch pending-coin addresses; let per-address rescans queueAdam Soltys2026-06-242-20/+30
| | | | | | | | | | | | | | - watchedAddresses() now includes any address holding an unconfirmed coin (not just the frontier), so a pending deposit's confirmation pushes over the socket instantly. Capped at the socket's 10-address limit. refreshLive/rescanAddress retrack when a new pending coin changes the set. - While any coin is pending, the frontier poll runs every ~6s even when live, so confirmations show fast as a belt-and-suspenders to the WS push. - Per-address rescans no longer disable each other: clicking many queues them, and the api.js global scheduler (1 concurrent, 500ms spacing, 429 backoff) serializes and staggers the underlying requests within the rate limit. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop background/full rescans for on-demand per-address rescanAdam Soltys2026-06-243-14/+124
| | | | | | | | | | | | | | | Remove the periodic full re-scan entirely — the WS and the frontier poll keep the balance current, and refreshLive's _reconcileHeld catches spends of held coins. The only gap (a deposit to a reused old address) is now handled explicitly: Settings → Rescan opens a paginated list of every known address, each with its own Rescan button (wallet.rescanAddress re-queries just that one). Replaces the full 'Rescan wallet' button. This sharply cuts recurring REST volume (the main rate-limit driver). Trade-off: when the socket is down, a pending tx's confirmed badge in History may lag until the WS reconnects or you rescan — the balance itself stays correct via reconcile. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop beforeinstallprompt preventDefault to silence its console warningAdam Soltys2026-06-241-1/+3
| | | | | | | | preventDefault() without a following prompt() makes Chrome log a 'Banner not shown' warning. Modern Chrome shows no auto-banner to suppress, so we just stash the event for our own install link without preventing the default. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make pending receives fast: cut REST load, surface live/polling stateAdam Soltys2026-06-243-8/+34
| | | | | | | | | | | | | | | | | | | Incoming deposits show instantly when the mempool.space WebSocket is healthy (_ingestWs updates immediately). The lag came from the socket being starved: a 20s frontier poll plus a full wallet re-scan every 120s generated enough REST volume to trip mempool's per-IP rate limit (the 429/CORS errors), which in turn degraded the socket — a feedback loop. - While the socket is live, skip the periodic full scan entirely (the WS pushes confirmations and block events) and poll the frontier only every ~25s as a half-open-socket safety net — near-zero recurring REST. - While the socket is down, poll the frontier every ~6s (was 20s) so receives still show quickly, and keep the 120s reconcile scan. - Refresh the frontier on WS (re)connect to catch deposits that landed while disconnected. - Surface a Live/Polling badge on the balance card so a stuck socket is visible. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Allow gifting the whole balance (no-change sweep) via MaxAdam Soltys2026-06-243-22/+75
| | | | | | | | | | | | A gift PSBT commits a sender change output (>= dust), so the entered amount always needed gift + 294 — making it impossible to gift the entire balance. Add a Max toggle on the gift form that builds a no-change sweep: all inputs SIGHASH_NONE, no change output, so the recipient gets the whole balance minus their claim fee. previewGift/buildClaimTx now treat a zero-output PSBT as zero change. A specific-amount gift that can't keep a dust change now points the user to Max instead of failing with a confusing 'not enough funds'. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Balance, claim button, and gift-recipient backup verificationAdam Soltys2026-06-242-10/+64
| | | | | | | | | | | | - Spendable headline no longer counts pending incoming receives: compute it from UTXOs (confirmed coins + our own unconfirmed change, minus gift locks) so a pending spend still debits immediately but a pending receive shows only on the PENDING line (new pendingIncoming getter). - Claim button shows the fee in parens in slightly smaller font, no dot. - Gift recipients now get the same "Verify backup" / "Skip verification" flow as new-wallet creation instead of a single "I've written it down" button. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Paginate History: cap inline gifts at 3, transactions at 10Adam Soltys2026-06-242-23/+73
| | | | | | | | | Outstanding gifts no longer dominate the History page — show at most 3 inline with a 'View all gifts (N)' button that opens a paginated gift-only list. Transactions paginate 10 per page with prev/next controls. Both reuse a shared pager helper; the all-gifts sub-view resets when leaving the History tab. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show outstanding sent gifts in History, cancellable thereAdam Soltys2026-06-242-2/+39
| | | | | | | | | Unclaimed gift links now appear at the top of the History list (they aren't on-chain transactions until claimed or revoked). Each is tappable to cancel via the same reclaim/revoke flow as the Send → gift card, reusing ui.revokeId and the doReclaim/doRevoke handlers. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Instant gift-via-split and spend-unconfirmed supportAdam Soltys2026-06-243-36/+98
| | | | | | | | | | | | | | | | Gift split no longer makes the sender wait for confirmation: createGiftFromSplit broadcasts the carve-out, waits only until it's visible in the mempool, then builds the gift spending that still-unconfirmed coin. Funding + claim form a chain that confirms together; the recipient sees a pending balance until then. Polls for mempool visibility before reserving so a background refresh can't drop a not-yet-indexed coin and prune the reservation (double-spend risk). Regular sends can already draw on unconfirmed coins; surface that honestly: a 'pending' tag on unconfirmed coins in coin control, and a plain-language warning at review when a payment spends unconfirmed bitcoin (won't confirm until the ancestor does). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Minimize gift lock-up: best-fit coins + optional pre-splitAdam Soltys2026-06-243-19/+112
| | | | | | | | | | | | | | Gift coin selection now picks the smallest single coin that covers the gift (was largest-first), locking the least value until claim. When no near-exact coin exists, offer to pre-split: broadcast a self-send carving out a gift+dust coin so the gift later locks only ~the gift amount. The split is only offered when the liquidity it frees (lock - gift - dust) exceeds the split's own fee, so it never costs more than it regains. Decision is surfaced as an interstitial on Create, since a full re-render drops input focus and a live preview isn't viable. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Deduct gift-locked coins from the spendable balanceAdam Soltys2026-06-243-5/+26
| | | | | | | | | Coins reserved behind unclaimed gift links were already excluded from spending, but the headline balance still counted them. Show the spendable balance (total minus gift locks) as the headline and surface the locked amount as its own line, so a gift debits immediately rather than at claim. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gift links: stop pre-reserving fee, show it at claim timeAdam Soltys2026-06-243-13/+21
| | | | | | | | | | | | | | Previously createGift reserved a claim-fee headroom on top of the entered amount based on the fee rate at creation. Because the fee was only actually charged at claim time, fee-rate drift between the two moments silently ate into (or padded) the recipient's amount. Now the entered amount is exactly the recipient's full received total (sum of inputs less our change); no fee is reserved. The claim screen shows that full total as the headline and surfaces the live network-fee estimate on the Claim button, where it's deducted from the recipient's balance at claim time. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Simplify import placeholder to 'Your seed words'Adam Soltys2026-06-241-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add a 🎁 to the Gift some Bitcoin linkAdam Soltys2026-06-241-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix dark-mode contrast and stop install-link jitterAdam Soltys2026-06-242-12/+43
| | | | | | | | | | | - warn-box, addr-box, summary, toast and green badge/notice borders used hardcoded light colors that washed out or vanished in dark mode; route them through theme variables (new --toast-bg/-fg and --green-line pairs). - Drive the footer "Install app" link from a persisted localStorage flag instead of the live beforeinstallprompt event so it's present on first paint after a refresh, eliminating the layout shift. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add dark mode; gift amount unit toggle; multi-input giftsAdam Soltys2026-06-2423-49/+152
| | | | | | | | | | | | | | - 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]>
* Move the language picker from Settings to the footerAdam Soltys2026-06-241-8/+3
| | | | | | It now lives in the global footer, available on every screen. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Relabel tx explorer link to "View on block explorer"Adam Soltys2026-06-2420-20/+20
| | | | | | | The link already targets the selected explorer (explorerTx); only the label said mempool.space. Now generic, across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Guard the receive celebration to recent payments onlyAdam Soltys2026-06-241-3/+11
| | | | | | | | | Defense-in-depth so the "payment received" screen can never fire for an old payment on opening a wallet: in addition to the ack baseline, require an incoming tx that is pending or confirmed within the last ~2 hours. Verified it still celebrates genuine new (pending/recent) payments. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Remove the caret from the account selector chipAdam Soltys2026-06-241-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop the "saved" tag from the account titleAdam Soltys2026-06-241-1/+1
| | | | | | It was redundant with the "Saved · forget" link shown beneath the name. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rename "Lock all" to "Clear all" with confirmation; editable wallet namesAdam Soltys2026-06-2421-21/+131
| | | | | | | | | | | | - The Accounts action is now "Clear all", which prompts a confirmation warning that any wallet whose recovery phrase/key isn't backed up will be lost. Confirming wipes everything from the device: session accounts, the encrypted vault, and saved watch-only accounts. - Each account has a rename (✎) control: edit the label inline and save. Persists to the session, the watch-only store, and the vault as needed. New strings translated across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fold watch-only into Import; support xprv spending importAdam Soltys2026-06-2422-116/+199
| | | | | | | | | | | | | | | | | | Home screen is back to two tabs (Create / Import). The Import box now takes a recovery phrase, an xpub/zpub, or an xprv/zprv: - mnemonic → full wallet (as before) - xpub/zpub → watch-only (view/receive, no Send) - xprv/zprv → full spending wallet with no mnemonic. parseExtendedKey classifies + normalizes version bytes; account() derives the BIP84 path from a master xprv (depth 0) or uses an account-level xprv as-is; signing works (only watch-only blocks it). Vault persists xprv accounts too; Settings shows an "Imported key" note instead of a recovery card and hides Nostr sync (no seed). Public-key export still works. Note: a seed phrase cannot be derived from an xprv (one-way KDF), so an xprv wallet has no recovery words by design. New strings in all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Don't celebrate pre-existing payments on opening a walletAdam Soltys2026-06-241-12/+20
| | | | | | | | | | | | | | | | The "payment received" screen is for payments that arrive while you're using the wallet, but the seen-index is persisted per-seed, so re-importing/unlocking/switching to a wallet used before would resurface the celebration for a payment that had already arrived (and confirmed) in between. Opening a wallet (import, unlock, account switch, watch-only add, fresh-session restore) now baselines the ack to the current receive frontier; only a same-session refresh keeps the persisted ack so an unacknowledged live celebration survives the refresh. Verified: with a stale persisted ack, a fresh import shows no celebration and re-baselines to the current frontier. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix confirmed txs stuck showing "pending"Adam Soltys2026-06-241-2/+11
| | | | | | | | | | | | | | | | Two paths left a tx's confirmed flag stale once the balance had already reconciled: - The frontier poll re-fetched an address's txs but only appended new ones (`if (!txs.some(...))`), so a tx that moved from mempool to a block kept its old pending status. Now it refreshes the record in place. - On reload, scan() skipped the history re-pull when the cached balance already matched on-chain, so a stale-pending cached tx never updated. scan() now also re-pulls history whenever any tx is still pending. Verified: a cached pending tx that is confirmed on-chain flips to confirmed (and pending balance clears) after a scan. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add optional password-encrypted persistence for full accountsAdam Soltys2026-06-2422-38/+537
| | | | | | | | | | | | | Per-account "Save on this device" encrypts a seed account into a localStorage vault (scrypt KDF + XChaCha20-Poly1305 via @noble), so it survives a browser close. On reopen, a vault prompt unlocks all saved wallets with the password (or "Use another wallet" to skip). Default stays session-only; the password is held only in memory for the session and re-encrypts on save/forget. Wrong password fails closed (AEAD). Adds @noble/ciphers dependency. New strings translated across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add account switching and watch-only walletsAdam Soltys2026-06-2422-94/+664
| | | | | | | | | | | | | | | | | | | | | | Accounts: a switcher (header chip → Accounts screen) lets you keep several wallets loaded and flip between them without re-importing. Per the chosen model, full (seed) accounts live only in sessionStorage — ephemeral, no seed on disk; watch-only accounts (no seed) are persisted in localStorage and reload across restarts. enterWallet is refactored around an accounts list (add/switch/remove/lock); Add wallet reaches the unlock screen with a back button. Watch-only: load any wallet from its account xpub/zpub (parseAccountKey normalizes zpub→xpub; the existing public-derivation path yields the same addresses). Signing is blocked, the Send tab and seed/sync/offline cards are hidden, and a watch-only note is shown. Settings gains a "Public key" card (export this wallet's zpub) so it can be watched read-only elsewhere. Password-encrypted persistence for full accounts is the planned next step. New strings translated across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>