aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-29 08:49:41 -0700
committerAdam Soltys <[email protected]>2026-07-29 08:49:41 -0700
commitfa31fb2721910604abef73049c04b3f872d34a83 (patch)
treee8b68c5f0982dabb56ac5bb67f1843193aeaa525 /src
parent0fb4b03b48bda2ba187d96d1feb4459716b8e73c (diff)
downloadcoinosv3-fa31fb2721910604abef73049c04b3f872d34a83.tar.gz
coinosv3-fa31fb2721910604abef73049c04b3f872d34a83.zip
Filter swap providers by network; guard cross-network selection
Tag each BOLTZ_PRESET with the network(s) it serves and add boltzPresetsFor(net) so the provider dropdown only lists backends valid for the active network — a mainnet wallet no longer shows the local (regtest) or coinos-staging (mutinynet) backends. getBoltzProviderId() now only honors a stored choice if it's valid for the current network, so switching to mainnet can't silently keep using a regtest/mutinynet backend, and the getBoltzProvider() fallback can no longer resolve to staging. Also point the testnet default at custom (was the mainnet Boltz API). Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/api.js40
-rw-r--r--src/features/swaps.js4
2 files changed, 28 insertions, 16 deletions
diff --git a/src/api.js b/src/api.js
index 4a7dd75..e56992d 100644
--- a/src/api.js
+++ b/src/api.js
@@ -150,16 +150,25 @@ function nk(base, net) { return net === 'mainnet' ? base : `${base}:${net}`; }
// Presets are independent Boltz-compatible instances from SwapMarket's list
// (swapmarket.github.io); `local` is the regtest stack. Non-custodial either
// way: a provider can fail a swap but never steal (we claim/refund on-chain).
+// `nets` is which Bitcoin networks a preset actually serves — a provider runs on
+// exactly one chain, so a regtest/mutinynet backend must never be offered (or
+// used) on mainnet, and vice versa. `custom` carries no `nets` and is available
+// everywhere.
export const BOLTZ_PRESETS = [
- { id: 'local', label: 'Local (regtest)', api: '/boltz', ws: 'ws://localhost:9004/v2/ws' },
- { id: 'staging', label: 'coinos staging (mutinynet)', api: 'https://swap-staging.coinos.io', ws: 'wss://swap-staging.coinos.io/v2/ws' },
- { id: 'coinos', label: 'coinos (swap.coinos.io)', api: 'https://swap.coinos.io', ws: 'wss://swap.coinos.io/v2/ws' },
- { id: 'boltz', label: 'Boltz Exchange', api: 'https://api.boltz.exchange' },
- { id: 'middleway', label: 'Middle Way', api: 'https://api.middle-way.space' },
- { id: 'zeus', label: 'ZEUS Swaps', api: 'https://swaps.zeuslsp.com/api' },
- { id: 'eldamar', label: 'Eldamar', api: 'https://boltz-api.eldamar.icu' },
+ { id: 'local', label: 'Local (regtest)', api: '/boltz', ws: 'ws://localhost:9004/v2/ws', nets: ['regtest'] },
+ { id: 'staging', label: 'coinos staging (mutinynet)', api: 'https://swap-staging.coinos.io', ws: 'wss://swap-staging.coinos.io/v2/ws', nets: ['mutinynet'] },
+ { id: 'coinos', label: 'coinos (swap.coinos.io)', api: 'https://swap.coinos.io', ws: 'wss://swap.coinos.io/v2/ws', nets: ['mainnet'] },
+ { id: 'boltz', label: 'Boltz Exchange', api: 'https://api.boltz.exchange', nets: ['mainnet'] },
+ { id: 'middleway', label: 'Middle Way', api: 'https://api.middle-way.space', nets: ['mainnet'] },
+ { id: 'zeus', label: 'ZEUS Swaps', api: 'https://swaps.zeuslsp.com/api', nets: ['mainnet'] },
+ { id: 'eldamar', label: 'Eldamar', api: 'https://boltz-api.eldamar.icu', nets: ['mainnet'] },
{ id: 'custom', label: 'Custom…', api: '', ws: '' },
];
+
+// Presets valid for a network: those that serve it, plus Custom (any network).
+export function boltzPresetsFor(net = getNetwork()) {
+ return BOLTZ_PRESETS.filter((p) => !p.nets || p.nets.includes(net));
+}
const BOLTZ_PROVIDER_KEY = 'btc-wallet-boltz-provider'; // selected preset id
const BOLTZ_CUSTOM_KEY = 'btc-wallet-boltz-custom'; // { api, ws } for custom
@@ -171,17 +180,20 @@ export function setBoltzCustom({ api, ws }) {
try { localStorage.setItem(BOLTZ_CUSTOM_KEY, JSON.stringify({ api: (api || '').trim(), ws: (ws || '').trim() })); } catch {}
}
export function getBoltzProviderId() {
- try { const id = localStorage.getItem(BOLTZ_PROVIDER_KEY); if (id && BOLTZ_PRESETS.some((p) => p.id === id)) return id; } catch {}
- const net = getNetwork(); // default per network
- // Mainnet defaults to public Boltz Exchange until our own swap.coinos.io
- // node ('coinos') has channel liquidity; flip this to 'coinos' once funded.
- // No Boltz-compatible instance runs on signet, so it defaults to custom/empty.
- return net === 'regtest' ? 'local' : net === 'mutinynet' ? 'staging' : net === 'signet' ? 'custom' : 'boltz';
+ const net = getNetwork();
+ // Only honour a stored choice that's valid for the CURRENT network — otherwise
+ // switching to mainnet would silently keep a regtest/mutinynet backend.
+ try { const id = localStorage.getItem(BOLTZ_PROVIDER_KEY); if (id && boltzPresetsFor(net).some((p) => p.id === id)) return id; } catch {}
+ // Default per network. Mainnet uses public Boltz Exchange until our own
+ // swap.coinos.io node ('coinos') has channel liquidity; flip this to 'coinos'
+ // once funded. No Boltz-compatible instance runs on signet/testnet, so they
+ // default to custom/empty.
+ return net === 'regtest' ? 'local' : net === 'mutinynet' ? 'staging' : (net === 'signet' || net === 'testnet') ? 'custom' : 'boltz';
}
export function setBoltzProviderId(id) { try { localStorage.setItem(BOLTZ_PROVIDER_KEY, id); } catch {} }
export function getBoltzProvider() {
- const p = BOLTZ_PRESETS.find((x) => x.id === getBoltzProviderId()) || BOLTZ_PRESETS[1];
+ const p = BOLTZ_PRESETS.find((x) => x.id === getBoltzProviderId()) || BOLTZ_PRESETS.find((x) => x.id === 'custom');
if (p.id === 'custom') { const c = getBoltzCustom(); return { id: 'custom', api: c.api, ws: c.ws }; }
return { id: p.id, api: p.api, ws: p.ws || '' };
}
diff --git a/src/features/swaps.js b/src/features/swaps.js
index 2ff43ed..3e97412 100644
--- a/src/features/swaps.js
+++ b/src/features/swaps.js
@@ -3,7 +3,7 @@
// protocol layer lives in ../swap.js.
import { SwapManager, installSwapWallet } from '../swap.js';
-import { getNetwork, getBoltzApi, BOLTZ_PRESETS, getBoltzProviderId, setBoltzProviderId, getBoltzCustom, setBoltzCustom } from '../api.js';
+import { getNetwork, getBoltzApi, boltzPresetsFor, getBoltzProviderId, setBoltzProviderId, getBoltzCustom, setBoltzCustom } from '../api.js';
import { t } from '../i18n.js';
import { qrSvg } from '../qr.js';
import { fmtBtc } from '../format.js';
@@ -84,7 +84,7 @@ export function swapsFeature(ctx) {
h('h3', {}, '⚡ Swap provider'),
h('p', { class: 'small muted', style: 'margin:0' }, 'Boltz-compatible provider for Lightning swaps. Non-custodial: a provider can fail a swap but never take your funds.'),
h('select', { onChange: (e) => { setBoltzProviderId(e.target.value); ui.swapLimits = null; render(); } },
- BOLTZ_PRESETS.map((p) => h('option', { value: p.id, selected: p.id === id }, p.label))),
+ boltzPresetsFor(getNetwork()).map((p) => h('option', { value: p.id, selected: p.id === id }, p.label))),
id === 'custom'
? h('div', { class: 'col', style: 'gap:8px' },
h('label', { class: 'field' },