diff options
| author | Adam Soltys <[email protected]> | 2026-06-15 09:24:32 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-15 09:24:32 -0700 |
| commit | 92b27e7d6de51ae178f068b079d3eec017689d85 (patch) | |
| tree | 8783d96fe096f0633013991355bf3ef44f081088 | |
| parent | 8a2bb0790d581ff67325966b3e37d6b2500128b5 (diff) | |
| download | coinosv3-92b27e7d6de51ae178f068b079d3eec017689d85.tar.gz coinosv3-92b27e7d6de51ae178f068b079d3eec017689d85.zip | |
Drop live badge + fiat price; persist wallet in sessionStorage
- 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) <[email protected]>
| -rw-r--r-- | src/api.js | 13 | ||||
| -rw-r--r-- | src/app.js | 58 | ||||
| -rw-r--r-- | src/wallet.js | 10 |
3 files changed, 42 insertions, 39 deletions
@@ -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'); @@ -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; |
