aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-15 09:24:32 -0700
committerAdam Soltys <[email protected]>2026-06-15 09:24:32 -0700
commit92b27e7d6de51ae178f068b079d3eec017689d85 (patch)
tree8783d96fe096f0633013991355bf3ef44f081088 /src
parent8a2bb0790d581ff67325966b3e37d6b2500128b5 (diff)
downloadcoinosv3-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]>
Diffstat (limited to 'src')
-rw-r--r--src/api.js13
-rw-r--r--src/app.js58
-rw-r--r--src/wallet.js10
3 files changed, 42 insertions, 39 deletions
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;