aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-26 09:03:49 -0700
committerAdam Soltys <[email protected]>2026-06-26 09:03:49 -0700
commiteef3193729ccea2c3d712cda9381c24325fac260 (patch)
treedd22d714a3667773a73fc42e3b0a88b3171bdbae /src
parent190c849cf9ce1c09501fedea33020098c8026c1d (diff)
downloadcoinosv3-eef3193729ccea2c3d712cda9381c24325fac260.tar.gz
coinosv3-eef3193729ccea2c3d712cda9381c24325fac260.zip
Fold the coinos opt-out into a single Data source picker
Replace the Esplora|Electrum backend toggle plus the separate 'Instant notifications' on/off with one 'Data source' selector: • coinos (instant) — default; explorer data + coinos push • Block explorer — REST + polling, nothing sent to coinos (the opt-out) • Electrum server — one WS server for data + push (your node / public) Picking a non-coinos source IS the privacy opt-out, so the standalone toggle is gone. A single 'mode' setting drives getBackend()/getRealtimeEnabled(), so the wallet/wsUrl logic is unchanged; existing per-feature settings migrate in (realtime=off → explorer, backend=electrum → electrum). coinos⇄explorer just flips the watcher (no rescan); to/from electrum rebuilds the backend. Sets up cleanly for a future coinos Fulcrum: 'coinos' mode upgrades from REST+watcher to a full Electrum backend transparently. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/api.js41
-rw-r--r--src/app.js62
-rw-r--r--src/i18n.js12
3 files changed, 57 insertions, 58 deletions
diff --git a/src/api.js b/src/api.js
index 4d4212f..03db6b9 100644
--- a/src/api.js
+++ b/src/api.js
@@ -60,26 +60,31 @@ export function getElectrumWsUrl() {
return 'wss://coinos.io/electrum';
}
-// Privacy opt-out: turning this off means the app never connects to coinos's
-// watcher (so it can't see your addresses) and relies on polling your explorer
-// instead. On by default.
-const REALTIME_KEY = 'btc-wallet-realtime';
-export function getRealtimeEnabled() {
- try { return localStorage.getItem(REALTIME_KEY) !== 'off'; } catch { return true; }
-}
-export function setRealtimeEnabled(on) {
- try { localStorage.setItem(REALTIME_KEY, on ? 'on' : 'off'); } catch {}
-}
-
-// Data backend: the default Esplora REST (`Api`) or a full Electrum-over-WS
-// server (`ElectrumApi`) that serves data AND notifications over one connection.
-const BACKEND_KEY = 'btc-wallet-backend';
-export function getBackend() {
- try { return localStorage.getItem(BACKEND_KEY) === 'electrum' ? 'electrum' : 'esplora'; } catch { return 'esplora'; }
+// One "data source" setting drives both where chain data comes from and whether
+// coinos sees your addresses for instant push:
+// coinos — block-explorer data + coinos instant-payment notifications (default)
+// explorer — block-explorer data only; polls for payments, nothing sent to coinos
+// electrum — a single Electrum-over-WS server for both data and push (your node)
+// getBackend()/getRealtimeEnabled() derive from it, so the rest of the code (and
+// the wsUrl logic) is unchanged. Existing per-feature settings migrate in.
+const DATA_MODE_KEY = 'btc-wallet-mode';
+const BACKEND_KEY = 'btc-wallet-backend'; // legacy (migration only)
+const REALTIME_KEY = 'btc-wallet-realtime'; // legacy (migration only)
+export function getDataMode() {
+ try {
+ const m = localStorage.getItem(DATA_MODE_KEY);
+ if (m === 'coinos' || m === 'explorer' || m === 'electrum') return m;
+ if (localStorage.getItem(BACKEND_KEY) === 'electrum') return 'electrum';
+ if (localStorage.getItem(REALTIME_KEY) === 'off') return 'explorer';
+ } catch {}
+ return 'coinos';
}
-export function setBackend(b) {
- try { localStorage.setItem(BACKEND_KEY, b === 'electrum' ? 'electrum' : 'esplora'); } catch {}
+export function setDataMode(m) {
+ try { localStorage.setItem(DATA_MODE_KEY, m === 'electrum' || m === 'explorer' ? m : 'coinos'); } catch {}
}
+export function getBackend() { return getDataMode() === 'electrum' ? 'electrum' : 'esplora'; }
+// The coinos watcher is used only in 'coinos' mode; 'explorer' polls instead.
+export function getRealtimeEnabled() { return getDataMode() === 'coinos'; }
// Electrum servers that accept WebSocket connections. blockstream.info is
// electrs (validated); the point, though, is pointing at your own Fulcrum/electrs.
diff --git a/src/app.js b/src/app.js
index 6a994d8..8524528 100644
--- a/src/app.js
+++ b/src/app.js
@@ -8,7 +8,7 @@ import { Wallet, newMnemonic, isValidMnemonic, utxoId, previewGift, giftOutpoint
import { qrSvg } from './qr.js';
import { scanQr } from './scan.js';
import { getSyncConfig, setSyncConfig } from './nostr.js';
-import { getExplorerConfig, setExplorerConfig, EXPLORER_PRESETS, getRealtimeEnabled, setRealtimeEnabled, getBackend, setBackend, ELECTRUM_PRESETS, getElectrumServerConfig, setElectrumServerConfig } from './api.js';
+import { getExplorerConfig, setExplorerConfig, EXPLORER_PRESETS, getDataMode, setDataMode, ELECTRUM_PRESETS, getElectrumServerConfig, setElectrumServerConfig } from './api.js';
import { isSilentPaymentAddress } from './silentpay.js';
import { t, LANGS, getLang, setLang, isRTL, loadLocale } from './i18n.js';
import {
@@ -1298,36 +1298,34 @@ async function doRevoke(id) {
// Block explorer / server selection: a preset (mempool.space, blockstream.info)
// or a custom Esplora/electrs REST URL (e.g. your own node).
function explorerCard() {
- const backend = getBackend();
+ const mode = getDataMode();
const cfg = getExplorerConfig();
const ecfg = getElectrumServerConfig();
- const rt = getRealtimeEnabled();
- const setRealtime = (on) => {
- if (on === getRealtimeEnabled()) return;
- setRealtimeEnabled(on);
+ const setMode = (m) => {
+ const prev = getDataMode();
+ if (m === prev) return;
+ setDataMode(m);
render();
- if (!wallet.offline) wallet.startRealtime(); // reconnect or drop the watcher
- };
- const setBackendTo = (b) => {
- if (b === getBackend()) return;
- setBackend(b);
- render();
- if (!wallet.offline) wallet.reloadExplorer(); // rebuild api + reconnect
+ if (wallet.offline) return;
+ // coinos⇄explorer only flips the watcher (same REST data); to/from electrum
+ // swaps the whole backend, so rebuild + rescan there.
+ if ((m === 'electrum') !== (prev === 'electrum')) wallet.reloadExplorer();
+ else wallet.startRealtime();
};
return h(
'div',
{ class: 'card col' },
- h('h3', {}, t('explorer')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('explorerDesc')),
- // Data source: Esplora REST, or a full Electrum-over-WS server (data + push).
- h('div', { class: 'row between', style: 'margin-top:2px' },
- h('span', { class: 'lab', style: 'margin:0' }, t('dataSource')),
- h('div', { class: 'seg' },
- h('button', { type: 'button', class: backend === 'esplora' ? 'active' : '', onClick: () => setBackendTo('esplora') }, t('backendEsplora')),
- h('button', { type: 'button', class: backend === 'electrum' ? 'active' : '', onClick: () => setBackendTo('electrum') }, t('backendElectrum'))
- )
+ h('h3', {}, t('dataSource')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('dataSourceDesc')),
+ h('select', { onChange: (e) => setMode(e.target.value) },
+ h('option', { value: 'coinos', selected: mode === 'coinos' }, t('modeCoinos')),
+ h('option', { value: 'explorer', selected: mode === 'explorer' }, t('modeExplorer')),
+ h('option', { value: 'electrum', selected: mode === 'electrum' }, t('modeElectrum'))
),
- backend === 'electrum'
+ mode === 'coinos'
+ ? h('div', { class: 'small faint' }, t('modeCoinosDesc'))
+ : null,
+ mode === 'electrum'
? h('div', { class: 'col', style: 'gap:8px' },
h('div', { class: 'small faint' }, t('backendElectrumDesc')),
h('select', {
@@ -1345,7 +1343,10 @@ function explorerCard() {
)
: null
)
- : h('div', { class: 'col', style: 'gap:8px' },
+ : null,
+ mode === 'explorer'
+ ? h('div', { class: 'col', style: 'gap:8px' },
+ h('div', { class: 'small faint' }, t('modeExplorerDesc')),
h('select', {
onChange: (e) => { setExplorerConfig({ server: e.target.value, url: cfg.url }); render(); if (e.target.value !== 'custom' || cfg.url) wallet.reloadExplorer(); },
}, EXPLORER_PRESETS.map((o) => h('option', { value: o.id, selected: o.id === cfg.server }, o.label))),
@@ -1359,18 +1360,9 @@ function explorerCard() {
}),
h('div', { class: 'small faint' }, t('explorerUrlHint'))
)
- : null,
- // Realtime via coinos — the one place addresses are shared off-device.
- // (Electrum already serves its own push, so this only applies to REST.)
- h('div', { class: 'row between', style: 'margin-top:8px' },
- h('span', { class: 'lab', style: 'margin:0' }, t('instantNotif')),
- h('div', { class: 'seg' },
- h('button', { type: 'button', class: rt ? 'active' : '', onClick: () => setRealtime(true) }, t('syncOn')),
- h('button', { type: 'button', class: !rt ? 'active' : '', onClick: () => setRealtime(false) }, t('syncOff'))
- )
- ),
- h('div', { class: 'small faint' }, t('instantNotifDesc'))
+ : null
)
+ : null
);
}
diff --git a/src/i18n.js b/src/i18n.js
index aa8b499..e3341a7 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -260,14 +260,16 @@ const STR = {
explorerUrl: 'Server URL',
explorerUrlHint: 'Esplora / electrs REST base, e.g. https://mempool.space/api',
dataSource: 'Data source',
- backendEsplora: 'Explorer',
- backendElectrum: 'Electrum',
- backendElectrumDesc: 'Fetch data and get instant payments over one Electrum WebSocket. Point it at your own Fulcrum/electrs for a fully self-hosted wallet — no coinos, no REST.',
+ dataSourceDesc: 'Where Hal reads the chain and how it learns about incoming payments.',
+ modeCoinos: 'coinos (instant)',
+ modeCoinosDesc: 'The default. Instant payment alerts via coinos, with chain data from a public block explorer.',
+ modeExplorer: 'Block explorer',
+ modeExplorerDesc: 'Reads from your chosen block explorer and polls for payments — nothing is sent to coinos. A bit slower.',
+ modeElectrum: 'Electrum server',
+ backendElectrumDesc: 'Data and instant payments over one Electrum WebSocket. Point it at your own Fulcrum/electrs for a fully self-hosted wallet — no coinos, no REST.',
electrumUrl: 'Electrum server (wss://)',
electrumUrlHint: 'A WebSocket Electrum server, e.g. wss://your-node:50004',
silentPaymentNote: 'Silent payment',
- instantNotif: 'Instant notifications',
- instantNotifDesc: 'Uses coinos to push payments the instant they arrive. Turn off to poll the explorer above instead — a bit slower, but your addresses are never sent to coinos.',
deviceSync: 'Device sync',
deviceSyncDesc: 'Keep this wallet in sync across your devices by storing an encrypted copy on a Nostr relay — only a device with your seed can read it. Turn it off to keep everything on this device.',
syncAcross: 'Sync across devices',