aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-25 18:08:07 -0700
committerAdam Soltys <[email protected]>2026-06-25 18:08:07 -0700
commit1edb9ac5723f2d5fda5093437b2e375f98af3777 (patch)
tree1af7769f023da7d1a57f835285259bf749a0d86b /src
parent6245a4583e786beaa6d5c02f7abfb646eddf00c6 (diff)
downloadcoinosv3-1edb9ac5723f2d5fda5093437b2e375f98af3777.tar.gz
coinosv3-1edb9ac5723f2d5fda5093437b2e375f98af3777.zip
Settings: let users opt out of coinos realtime notifications (privacy)
The WS watcher is the one place the app shares addresses off-device (it subscribes your scripthashes to coinos to push instant payment updates). Add an 'Instant notifications' toggle in the explorer settings: off means the app never connects to coinos and polls your chosen explorer instead — a bit slower, more requests, but nothing is told to coinos. wsUrl returns null when disabled, so startRealtime cleanly skips the socket and relies on the polling fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/api.js23
-rw-r--r--src/app.js21
-rw-r--r--src/i18n.js2
3 files changed, 38 insertions, 8 deletions
diff --git a/src/api.js b/src/api.js
index 1f57353..b981bc6 100644
--- a/src/api.js
+++ b/src/api.js
@@ -45,11 +45,10 @@ function resolveHosts(net) {
return [host('https://mempool.space', 'mempool'), host('https://blockstream.info', 'esplora')];
}
-// Realtime is a private Fulcrum (Electrum protocol over WebSocket-Secure). It
-// reliably pushes a scripthash-status notification the moment a tx touches a
-// watched address — unlike the public Esplora/blockchain.info sockets, whose
-// address push doesn't fire. Independent of the REST explorer choice. Mainnet
-// only; testnet falls back to polling (null). Overridable for staging/testing.
+// Realtime notifications come from coinos's WebSocket watcher: it pushes the
+// instant a tx touches a watched address (the app subscribes its frontier +
+// pending scripthashes). Independent of the REST explorer choice. Mainnet only;
+// testnet falls back to polling (null). Overridable for staging/testing.
const ELECTRUM_KEY = 'btc-wallet-electrum';
export function getElectrumWsUrl() {
try {
@@ -60,8 +59,20 @@ export function getElectrumWsUrl() {
// WebSocket quirk; the main hostname proxies WS cleanly).
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 {}
+}
+
export function wsUrl(net) {
- if (net === 'testnet') return null;
+ if (net === 'testnet' || !getRealtimeEnabled()) return null;
return getElectrumWsUrl();
}
diff --git a/src/app.js b/src/app.js
index 5709934..21aea3d 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 } from './api.js';
+import { getExplorerConfig, setExplorerConfig, EXPLORER_PRESETS, getRealtimeEnabled, setRealtimeEnabled } from './api.js';
import { t, LANGS, getLang, setLang, isRTL, loadLocale } from './i18n.js';
import {
fmtBtc,
@@ -1298,6 +1298,13 @@ async function doRevoke(id) {
// or a custom Esplora/electrs REST URL (e.g. your own node).
function explorerCard() {
const cfg = getExplorerConfig();
+ const rt = getRealtimeEnabled();
+ const setRealtime = (on) => {
+ if (on === getRealtimeEnabled()) return;
+ setRealtimeEnabled(on);
+ render();
+ if (!wallet.offline) wallet.startRealtime(); // reconnect or drop the watcher
+ };
return h(
'div',
{ class: 'card col' },
@@ -1330,7 +1337,17 @@ function explorerCard() {
}),
h('div', { class: 'small faint' }, t('explorerUrlHint'))
)
- : null
+ : null,
+ // Realtime via coinos — the one place addresses are shared off-device. Opt
+ // out to poll the explorer above instead (a bit slower, nothing told to coinos).
+ 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'))
);
}
diff --git a/src/i18n.js b/src/i18n.js
index bf6e807..0004b83 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -259,6 +259,8 @@ const STR = {
explorerDesc: 'The server this wallet queries for balances and history. Point it at your own node for maximum privacy.',
explorerUrl: 'Server URL',
explorerUrlHint: 'Esplora / electrs REST base, e.g. https://mempool.space/api',
+ 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',