From 92b27e7d6de51ae178f068b079d3eec017689d85 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 15 Jun 2026 09:24:32 -0700 Subject: Drop live badge + fiat price; persist wallet in sessionStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove the online/live status badge and the USD price line, which both appeared after load and caused layout shift. Stop fetching price entirely. - Persist the seed + passphrase in sessionStorage so a page refresh restores the open wallet instead of returning to the unlock screen. Cleared on logout and (by the browser) when the tab closes. Note: sessionStorage holds the seed in plaintext for the tab session — an acceptable tradeoff for this self-custody tool and scoped to the tab. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/api.js | 13 ------------- src/app.js | 58 ++++++++++++++++++++++++++++++++++++++++------------------ src/wallet.js | 10 ++-------- 3 files changed, 42 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/api.js b/src/api.js index ea8461e..43d2bae 100644 --- a/src/api.js +++ b/src/api.js @@ -142,19 +142,6 @@ export class Api { return { fastestFee: 20, halfHourFee: 10, hourFee: 5, economyFee: 2, minimumFee: 1 }; } - // Only mempool.space exposes a price endpoint; price is non-critical. - async price() { - const h = this._hosts.find((x) => x.base.includes('mempool')); - if (!h || h.cooldownUntil > Date.now()) return null; - try { - const res = await fetch(h.base + '/v1/prices'); - if (!res.ok) return null; - const p = await res.json(); - return p && p.USD ? p.USD : null; - } catch { - return null; - } - } async broadcast(hexTx) { if (this.offline) throw new Error('offline'); diff --git a/src/app.js b/src/app.js index e409656..e3a023c 100644 --- a/src/app.js +++ b/src/app.js @@ -9,7 +9,6 @@ import { qrSvg } from './qr.js'; import { fmtBtc, fmtSats, - fmtUsd, parseAmount, shortAddr, shortTxid, @@ -340,7 +339,14 @@ async function openWallet(mnemonic) { render(); return; } - wallet.load({ mnemonic: m, passphrase: ui.passphrase, netName: 'mainnet', offline: false }); + await enterWallet(m, ui.passphrase); +} + +// Load a wallet and start scanning. Persists to sessionStorage so a refresh +// restores the open wallet (cleared on logout or when the tab closes). +async function enterWallet(mnemonic, passphrase) { + wallet.load({ mnemonic, passphrase, netName: 'mainnet', offline: false }); + persistSession(mnemonic, passphrase); ui.screen = 'wallet'; ui.tab = 'receive'; ui.send = blankSend(); @@ -363,6 +369,33 @@ async function openWallet(mnemonic) { } } +const SESSION_KEY = 'btc-wallet-session'; + +function persistSession(mnemonic, passphrase) { + try { + sessionStorage.setItem(SESSION_KEY, JSON.stringify({ mnemonic, passphrase })); + } catch {} +} + +function clearSession() { + try { + sessionStorage.removeItem(SESSION_KEY); + } catch {} +} + +// Restore an open wallet after a page refresh (same tab session only). +function restoreSession() { + let saved; + try { + saved = JSON.parse(sessionStorage.getItem(SESSION_KEY) || 'null'); + } catch {} + if (saved && saved.mnemonic && isValidMnemonic(saved.mnemonic)) { + enterWallet(saved.mnemonic, saved.passphrase || ''); + return true; + } + return false; +} + function enterOfflineFallback() { wallet.setOffline(true); wallet.deriveWindow(40); @@ -386,6 +419,7 @@ async function retryOnline() { function lock() { wallet.stopRealtime(); + clearSession(); wallet.load({ mnemonic: '', passphrase: '', netName: 'mainnet', offline: false }); wallet.mnemonic = ''; ui.screen = 'unlock'; @@ -400,7 +434,6 @@ function lock() { // ================================================================ WALLET function brandHeader(withLock) { - const status = wallet.offline ? 'offline' : wallet.live ? 'live' : 'online'; return h( 'div', { class: 'row between' }, @@ -410,17 +443,7 @@ function brandHeader(withLock) { h('div', { class: 'logo' }, '₿'), h('h1', {}, 'Bitcoin Wallet') ), - h( - 'div', - { class: 'row gap6' }, - withLock && - h( - 'span', - { class: `badge dot ${wallet.offline ? 'off' : ''} ${wallet.live ? 'live' : ''}` }, - status - ), - withLock && h('button', { class: 'btn-sm', onClick: lock }, 'Logout') - ) + withLock && h('button', { class: 'btn-sm', onClick: lock }, 'Logout') ); } @@ -508,15 +531,13 @@ function offlineBanner() { } function balanceCard() { - const price = wallet.price; - // Only dim on the very first load; WebSocket-driven updates happen silently. + // Only dim on the very first load; background updates happen silently. const firstLoad = wallet.scanning && !wallet.loaded; return h( 'div', { class: 'card balance' }, h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.05em' }, 'Total balance'), h('div', { class: 'amt', style: firstLoad ? 'opacity:.3' : '' }, fmtBtc(wallet.total), h('span', { class: 'unit' }, 'BTC')), - price ? h('div', { class: 'usd', style: firstLoad ? 'opacity:.3' : '' }, '≈ ' + fmtUsd(wallet.total, price)) : null, h( 'div', { class: 'split' }, @@ -911,4 +932,5 @@ async function importSnapshotFile(e) { } // ================================================================ start -render(); +// Restore a wallet left open in this tab; otherwise show the unlock screen. +if (!restoreSession()) render(); diff --git a/src/wallet.js b/src/wallet.js index 77ec97e..3627275 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -59,7 +59,6 @@ export class Wallet { this.utxos = []; // { txid, vout, value, address, chain, index, confirmed } this.txs = []; // aggregated history this.feeRates = null; - this.price = null; this.scanning = false; this.loaded = false; // true once a scan/snapshot has populated balances once @@ -219,12 +218,9 @@ export class Wallet { this.nextReceiveIndex = firstUnused(this.receive); this.nextChangeIndex = firstUnused(this.change); - // Fees/price are only needed at load — skip on routine background polls. + // Fees are only needed at load — skip on routine background polls. if (!silent || !this.feeRates) { - [this.feeRates, this.price] = await Promise.all([ - this.api.feeRates(), - this.api.price(), - ]); + this.feeRates = await this.api.feeRates(); } await this.refreshUtxos(); @@ -524,7 +520,6 @@ export class Wallet { netName: this.netName, exportedAt: new Date().toISOString(), feeRates: this.feeRates, - price: this.price, nextReceiveIndex: this.nextReceiveIndex, nextChangeIndex: this.nextChangeIndex, utxos: this.utxos.map((u) => ({ @@ -547,7 +542,6 @@ export class Wallet { ); this.feeRates = snap.feeRates || this.feeRates; - this.price = snap.price || this.price; this.nextReceiveIndex = snap.nextReceiveIndex || 0; this.nextChangeIndex = snap.nextChangeIndex || 0; -- cgit v1.2.3