From fcd971da0c110607ccdc7637667beb0fa79d42a7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 28 Jun 2026 21:59:07 -0700 Subject: Swaps(regtest dev): proxy boltz through the dev server to fix CORS 'failed to fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regtest boltz API (localhost:9001) sends no CORS headers, so the browser blocked cross-origin fetches — creating a Lightning invoice failed with 'failed to fetch', and the limits GET silently fell back to defaults (min showed 25k instead of regtest's real 50k). bun E2E tests didn't hit this (no CORS enforced). dev.js now proxies /boltz/* to localhost:9001 (adding CORS + handling the OPTIONS preflight), and the 'local' boltz preset points at the same-origin /boltz path. Public providers (mainnet/testnet) already send CORS and are unaffected. Verified: GET limits via proxy returns the real 50k/40.29M with CORS; POST create-reverse succeeds; the browser swap form now shows Min 50,000. Co-Authored-By: Claude Opus 4.8 (1M context) --- dev.js | 14 ++++++++++++++ src/api.js | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/dev.js b/dev.js index 6dffad9..02aa772 100644 --- a/dev.js +++ b/dev.js @@ -13,6 +13,20 @@ Bun.serve({ const NO_STORE = 'no-store, must-revalidate'; try { const path = new URL(req.url).pathname; + // The regtest boltz API (localhost:9001) sends no CORS headers, so the + // browser can't fetch it cross-origin. Proxy /boltz/* to it (adding CORS) + // so Lightning swaps work same-origin in dev. + if (path === '/boltz' || path.startsWith('/boltz/')) { + const CORS = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET,POST,OPTIONS', 'access-control-allow-headers': 'content-type' }; + if (req.method === 'OPTIONS') return new Response(null, { status: 204, headers: CORS }); + const target = 'http://localhost:9001' + path.replace(/^\/boltz/, '') + new URL(req.url).search; + const r = await fetch(target, { + method: req.method, + headers: req.headers.get('content-type') ? { 'content-type': req.headers.get('content-type') } : {}, + body: ['GET', 'HEAD'].includes(req.method) ? undefined : await req.text(), + }); + return new Response(await r.text(), { status: r.status, headers: { 'content-type': r.headers.get('content-type') || 'application/json', ...CORS } }); + } if (path === '/jsqr.js') { return new Response(await buildJsQr({ minify: false }), { headers: { 'content-type': 'text/javascript; charset=utf-8', 'cache-control': NO_STORE }, diff --git a/src/api.js b/src/api.js index 54175f1..a12ddc6 100644 --- a/src/api.js +++ b/src/api.js @@ -144,7 +144,7 @@ function nk(base, net) { return net === 'mainnet' ? base : `${base}:${net}`; } // (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). export const BOLTZ_PRESETS = [ - { id: 'local', label: 'Local (regtest)', api: 'http://localhost:9001', ws: 'ws://localhost:9004/v2/ws' }, + { id: 'local', label: 'Local (regtest)', api: '/boltz', ws: 'ws://localhost:9004/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' }, -- cgit v1.2.3