aboutsummaryrefslogtreecommitdiffstats
path: root/src/api.js
Commit message (Collapse)AuthorAgeFilesLines
* The savings wallet gets a mirror it can always reach: ↵Adam Soltys2026-09-021-0/+5
| | | | | | | wss://halwallet.app/electrum joins the mainnet sources right behind the default — the coinos.io zone edge 403s WS clients with poor IP reputation and the public fallbacks are the usual regional-block suspects, so failover now lands on our own Fulcrum through a different apex first Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* Mutinynet runs on our own rails: a coinos-hosted electrs serves the chain ↵Adam Soltys2026-08-181-3/+6
| | | | | | | for both the wallet and the ark flows — no CDN clocks, no rate limits, mutinynet.com demoted to fallback Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* A snapshot only counts on its own network: the seed-keyed cache stops ↵Adam Soltys2026-08-171-0/+11
| | | | | | | showing mainnet money under a mutinynet flag, and staging forgives the mainnet default it stamped before it knew better Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Staging wears its name on the logo and deals in play money: ↵Adam Soltys2026-08-161-1/+5
| | | | | | | HAL_TARGET=staging stamps the brand, renames the PWA, and starts fresh wallets on Mutinynet — one master, no diverging branch Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Mutinynet gets the full experience by default: the staging ASP answers ↵Adam Soltys2026-08-151-1/+1
| | | | | | | unless someone says otherwise Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Settings: surface wallet settings, add Nostr section, tuck rarities into ↵Adam Soltys2026-08-021-0/+4
| | | | | | | | | | | | | | | | | | Advanced The main Settings tab now inlines the active wallet's own cards (recovery phrase, name, public key) instead of linking to a sub-page, followed by payment address, Ark status, network, and data source. All nostr cards — address, account link, wallet connect, device sync — sit under one Nostr heading via a new nostrSettingsCards seam. Offline transfer, address rescan, and auto-logout move to an Advanced sub-page. The Ark provider picker is gone: mainnet always rides coinos (a stale stored choice no longer wins); dev networks keep their per-network defaults. The per-account screen (Accounts → ⚙) reuses the same card builders unchanged. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Simplify: one Lightning path, one balance, fewer featuresAdam Soltys2026-08-011-102/+0
| | | | | | | | | | | | | | | | | | | | | | | | | Dropped: silent payments (feature, wallet chains, scan worker, indexer config), Boltz submarine/reverse swaps (feature + swap.js + tools), the third-party ark swap bridge (config, client flows, settings card — the ASP's own Lightning send is the one path now), and the Consolidate coins settings action. The NWC pouch is gone with them: background answering now spends the WHOLE ark balance. The IndexedDB record mirrors the real manager state plus the chain-3 keys for its live coins and a change-index window; the worker pays through the ASP exactly like the open app, and the app folds the worker's spends, change, and key allocations back in on connect (additive merge + reconcile against the server). A leftover chain-6 pouch from the short-lived pouch era is swept home automatically using the keys stored in its own record. The stated tradeoff: same-origin script access now reaches the ark balance, not a carve-out — the ark balance is walking-around money, the on-chain wallet's keys are never mirrored, and per-connection NWC budgets remain the enforced ceiling. Bundle: 726KB -> 602KB. Co-Authored-By: Claude Fable 5 <[email protected]>
* Default new mainnet wallets to the coinos ASP (ark.coinos.io)Adam Soltys2026-08-011-3/+6
| | | | | | | | Existing wallets with an explicitly chosen provider keep it; a wallet that was riding the old default keeps its second.tech coins under that server's namespace and can reselect it in Settings at any time. Co-Authored-By: Claude Fable 5 <[email protected]>
* Default the mainnet ark bridge to coinosAdam Soltys2026-08-011-1/+4
| | | | | | | | | | Small bolt11 sends were unroutable by default: the ASP path's CLN fee budget is a fraction of the ASP's own minimum fee, which rounds to zero sats of routing budget on a zap-sized payment. The coinos bridge now runs open (token gate removed server-side) so the default works for every wallet; users can still pick Off or Custom in settings. Co-Authored-By: Claude Fable 5 <[email protected]>
* Settings: coinos ASP presets + swap-bridge providerAdam Soltys2026-07-311-0/+66
| | | | | | | | | | | | | | | - ark presets gain coinos entries (ark.coinos.io on mainnet, ark-staging.coinos.io on mutinynet) alongside Second's - new per-network swap-bridge setting (preset/custom URL + access token) with its own Settings card When a bridge is configured, a bolt11 quote asks it first and shows its fee; paying locks an HTLC only the bridge can claim, and only by revealing the invoice preimage. Any failure — unreachable bridge, refused swap, unpayable invoice — refunds and falls back to the ASP's own lightning-send, so the payment never dead-ends. Hanging swaps are refunded on reconnect. Co-Authored-By: Claude Fable 5 <[email protected]>
* Filter swap providers by network; guard cross-network selectionAdam Soltys2026-07-291-14/+26
| | | | | | | | | | | | | | 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]>
* Fee rates from live explorer + reconnect half-open socket on wakeAdam Soltys2026-07-061-1/+1
| | | | | | | | | | | | | | | | - Fee rates on the electrum backend came from Fulcrum's estimatefee (Core's estimatesmartfee): historical, laggy, floored at 1 sat/vB, so it showed 1 across the board while mempool.space showed 11/10. Pull the configured explorer's live recommendation instead (/v1/fees/recommended, then /fee-estimates), falling back to estimatefee only when the explorer is unreachable — self-hosted/regtest behaviour unchanged. - On mobile a backgrounded page leaves the socket half-open: TCP is dead but readyState still reads OPEN, so wake() never reconnected and fired scan RPCs into the void, waiting ~20s for the heartbeat to recover. wake() now forces a reconnect when the socket has been silent past the heartbeat window, so a payment shows the instant you refocus. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Prune RBF-replaced pending txs from historyAdam Soltys2026-07-041-1/+12
| | | | | | | | | | | | A replaced/evicted unconfirmed tx lingered forever as a phantom pending receive (signetfaucet fee-bumps its batch payout every few minutes, so History grew a new copy per bump). The poll now status-checks pending txs — via /tx/:txid, since /tx/:txid/status answers 200 confirmed:false for txids the node no longer knows — drops the vanished ones, and re-fetches their addresses' coins and history, which _reconcileHeld skips when the replacement pays identical amounts. Co-Authored-By: Claude Fable 5 <[email protected]>
* Signet network — Second's public ASP is the default Ark providerAdam Soltys2026-07-041-4/+18
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Ark: on by default with a per-network default serverAdam Soltys2026-07-031-1/+14
| | | | | | | | | | Mainnet defaults to Second (ark.second.tech), regtest to the local stack. Testnet/mutinynet get their own preset lists (off/custom only — no public ASP exists there) instead of falling back to mainnet's, and stay off by default. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
* Ark: wire into the wallet UIAdam Soltys2026-07-031-0/+49
| | | | | | | | | | | | | | | | | | | | - Settings: Ark server card (off by default, per-network presets incl. Second mainnet + local regtest, custom URLs) with balance, coin count, board form (on-chain -> Ark; funding output pinned to vout 0 via the new buildTx noSort option), refresh/consolidate, and recent activity. - Receive: Ark option in the address-type dropdown (QR + copy). - Send: t?ark1 addresses are recognized in the normal send form and route to an Ark confirmation (no mining fee) with a success view. - Balance card: Ark balance line under the on-chain figure. - ArkManager lifecycle: per-wallet instance created on account activation, background sync every 5s (regtest) / 30s, state persisted under the wallet cache key. Verified end-to-end in a real browser (tools/ark/ui-test.js, puppeteer against the regtest stack): receive, send, board, and refresh all green. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
* Add coinos (swap.coinos.io) mainnet Boltz presetAdam Soltys2026-07-021-0/+3
| | | | | | | | | | Production Boltz service now live on cs at swap.coinos.io (dedicated mainnet CLN, chain = coinos production bc on desk over Tailscale). Add it as a selectable provider. Mainnet default stays on public Boltz Exchange until our node has channel liquidity. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01RtuRuzDQ6ffngL5wFw4f9v
* Swaps: add coinos staging (mutinynet) Boltz providerAdam Soltys2026-06-291-1/+3
| | | | | | | | | Point mutinynet wallets at the new self-hosted staging Boltz backend (swap-staging.coinos.io) by default, instead of mainnet api.boltz.exchange. Backend: dedicated boltz CLN + shared mut bitcoind on cs, behind a CORS shim + cloudflared. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Dev: proxy all regtest backends through the dev server for LAN/phone testingAdam Soltys2026-06-291-3/+3
| | | | | | | | | | | | | | | | The dev server already binds 0.0.0.0, but the app's regtest data source + SP indexer pointed at localhost, which on a phone means the phone's own localhost. Proxy them all through the dev server (same-origin), like /boltz already was: HTTP by prefix (/boltz, /esplora, /sp) and WebSocket by exact path (/electrum → Fulcrum, /sp/ws → SP indexer), with a Bun websocket relay. The regtest presets become relative paths, and a small absWsUrl() resolves '/path' WS URLs to ws(s)://<page-host>/path so they work from any device hitting the dev server. No per-device config, no CORS. Verified: /electrum relays Fulcrum 2.1.1, /esplora returns the tip height, and the desktop wallet still loads (balance 98,444) and connects through the proxy with no errors. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Swaps(regtest dev): proxy boltz through the dev server to fix CORS 'failed ↵Adam Soltys2026-06-281-1/+1
| | | | | | | | | | | | | | | | | | to fetch' 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) <[email protected]>
* Merge branch 'master' into boltz-swapsAdam Soltys2026-06-281-56/+120
|\ | | | | | | | | | | # Conflicts: # src/api.js # src/app.js
| * SP indexer → sp.coinos.io (clearer than tweaks); tunnel on http2Adam Soltys2026-06-281-1/+1
| | | | | | | | | | | | | | | | Renamed the default mainnet SP-indexer endpoint to sp.coinos.io. Also switched the cloudflared bitcoin tunnel to http2 (QUIC was intermittently dropping edge connections → transient 502s). tweaks.coinos.io still resolves to the same indexer. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| * Show full URLs in the source lists; SP indexer → tweaks.coinos.ioAdam Soltys2026-06-281-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | - Data-source and SP-indexer dropdowns now show the endpoint URL alongside the name (e.g. 'coinos — wss://electrum.coinos.io'), so it's clear where data comes from; 'Custom' stays bare. - The tweak indexer is now served at https://tweaks.coinos.io (was halwallet.app/ sp): a cloudflared route on the bitcoin tunnel → the indexer at :8888, with a DNS CNAME. Verified from a browser: /height, /mempool (1064 items), and /ws all work (the coinos.io Bot Fight Mode challenges non-browser clients only). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| * Unify data sources into one list; coinos → electrum.coinos.ioAdam Soltys2026-06-281-56/+86
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - Combined the separate Electrum and Explorer settings into a single data-source list. Each preset knows its type (electrum vs esplora); a custom source's type is detected from its URL scheme (ws/wss → Electrum, http/https → Esplora). The settings UI is now one dropdown + a URL field for Custom, with a one-line note showing the detected/selected type. getBackend/electrumCandidates/resolveHosts derive from the selected source. Old split settings migrate automatically. - coinos now points at wss://electrum.coinos.io (was wss://halwallet.app/electrum). Set up as a cloudflared route on the bitcoin tunnel → the Fulcrum WS (:50003), with a DNS CNAME. Verified the endpoint serves Electrum from a browser (Fulcrum 2.1.1 replies; cloudflare Bot Fight Mode only challenges non-browser clients). Verified: combined list + auto-detection (unit + browser), old-settings migration, regtest data flow unchanged, and the app loads over electrum.coinos.io with no errors. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| * Default mainnet to the hosted coinos silent-payment indexerAdam Soltys2026-06-271-2/+2
| | | | | | | | | | | | | | Mainnet wallets now use https://halwallet.app/sp (our indexer on cs, behind nginx /sp), enabling silent-payment receiving on mainnet. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| * Wire SP foundations into the wallet: configurable indexer + keys/addressAdam Soltys2026-06-271-0/+34
| | | | | | | | | | | | | | | | | | | | | | | | api.js: per-network SP indexer config (presets + custom, stored per network) with getSpIndexerConfig/spIndexerUrl/spReceiveEnabled — regtest defaults to the local indexer, others to custom; a hosted mainnet default slots in once we run one. wallet.js: silentPaymentKeys() derives BIP-352 scan/spend keys from the master seed (needs the mnemonic / depth-0 xprv), and silentPaymentAddress() returns the sp1…/tsp1… address. Verified it matches the vector-validated e2e address. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* | Merge branch 'master' into boltz-swapsAdam Soltys2026-06-271-134/+126
|\| | | | | | | | | # Conflicts: # src/app.js
| * Per-network data source; drop duplicate regtest fields; add mutinynetAdam Soltys2026-06-271-118/+118
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The network card is now just the network picker. Each network's endpoints (Electrum server + block explorer) live solely in the Data source card, whose options change per network and are stored per network (so switching networks remembers each one's choice). Removes the duplicate regtest Esplora/WS fields. Adds mutinynet (signet — testnet address format, explorer at mutinynet.com). Per-network defaults: mainnet → coinos Electrum, testnet/mutinynet → explorer, regtest → local Fulcrum. Fixes tx-explorer links to use each network's web base (no double /testnet). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| * Make coinos the default Electrum server instead of a separate modeAdam Soltys2026-06-271-29/+21
| | | | | | | | | | | | | | | | | | The data-source picker is now just Electrum vs Block explorer; coinos is the first/default entry in the Electrum server list (our own Fulcrum), with public servers as failover. Drops the standalone 'coinos' mode and the retired watcher shim. Old 'coinos' selections migrate to electrum. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| * Cut 'coinos' data source over to our own Fulcrum (Electrum)Adam Soltys2026-06-271-6/+6
| | | | | | | | | | | | | | | | | | | | | | Flip COINOS_ELECTRUM on: the default 'coinos' mode is now a full Electrum backend served same-origin at wss://halwallet.app/electrum (nginx -> Fulcrum on cs, synced + using bitcoind hashblock ZMQ), with the public Electrum servers as automatic failover. Replaces the explorer-REST-data + coinos-watcher-push setup — one connection to our own node now serves both data and instant notifications. Verified the endpoint end-to-end through Cloudflare (server.version + get_balance). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* | Merge branch 'master' into boltz-swapsAdam Soltys2026-06-271-0/+4
|\|
| * Use Fulcrum (Electrum/WS) for regtest data + push, dropping esploraAdam Soltys2026-06-271-0/+4
| | | | | | | | | | | | | | | | | | | | | | | | Regtest now talks to a local Fulcrum over its native Electrum WebSocket for both chain data and realtime push, instead of esplora REST + a separate watcher. This matches prod (Fulcrum) and gives ~2-3x lower latency than the previous electrs polling. getBackend()/electrumCandidates() route regtest to the Electrum backend at the configured Fulcrum WS (default ws://localhost:50003); the wallet's realtime path is ungated for regtest. ElectrumApi already covers every call (utxos, history, fees, broadcast), incl. the swap feature. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* | Merge branch 'master' into boltz-swapsAdam Soltys2026-06-271-1/+13
|\| | | | | | | | | # Conflicts: # src/app.js
| * Regtest realtime via a local Electrum-over-WebSocket bridgeAdam Soltys2026-06-271-1/+13
| | | | | | | | | | | | | | | | | | | | | | | | | | On regtest there's no realtime push (coinos watcher is mainnet-only), so the UI only updated on the 5–10s poll — a funded tx took a while to appear and a just-mined block stayed "pending" until the next reconcile. electrs speaks the Electrum protocol the watcher already understands, but only over TCP; route it through a small WS<->TCP bridge (see coinos-server/electrum-ws) so the wallet subscribes scripthashes and reconciles instantly on each notification. Data still comes from esplora; this only adds the push channel. Configurable in Settings (default ws://localhost:50003); falls back to polling if unreachable. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* | Let users choose a Boltz swap provider in SettingsAdam Soltys2026-06-271-12/+35
| | | | | | | | | | | | | | | | | | | | | | | | Replaces the single hardcoded Boltz endpoint with a provider selector. Presets are independent Boltz-compatible instances from SwapMarket's list (Boltz Exchange, Middle Way, ZEUS Swaps, Eldamar) plus Local (regtest) and a Custom option; default is Local on regtest, Boltz Exchange otherwise. The swap-status WebSocket is derived from the API URL (http->ws + /v2/ws) unless overridden. Swaps remain non-custodial regardless of provider — a provider can fail a swap but never take funds (hal claims/refunds on-chain). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* | Add Boltz submarine + reverse swaps (Lightning <-> on-chain)Adam Soltys2026-06-271-0/+15
|/ | | | | | | | | | | | | | | | | | | | Non-custodial swaps via Boltz, fully client-side: hal holds the swap key + preimage and claims (reverse) / refunds (submarine) on-chain via the taproot script path, so Boltz can never strand funds. - src/swap.js: tapleaf/tree construction (byte-exact vs boltz-core vectors), swap P2TR address derivation (MuSig2 internal key + taptweak), script-path claim/refund tx builder, bolt11 decode, Boltz api-v2 client, and a polling SwapManager (reverse auto-claim; submarine fund + status + refund). - wallet.js: deterministic swap-key derivation (dedicated chain) + persistence. - api.js: configurable Boltz REST/WS endpoints. - app.js: ⚡ Swap tab — Receive over LN (reverse) + Pay an invoice (submarine). - tools/swap-*.test.js: vectors, live-address, and end-to-end regtest tests. Verified end-to-end on regtest against a local Boltz wired to coinos's bc + a dedicated CLN/hold node. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Add regtest network supportAdam Soltys2026-06-271-1/+32
| | | | | | | | | | New `regtest` network (bech32 `bcrt`, testnet version bytes) selectable in Settings, pointed at a configurable local Esplora (default http://localhost:3000). Adds getNetwork/setNetwork + getRegtestEsplora persistence, a regtest branch in resolveHosts, mainnet-only gating for the realtime watcher + Electrum backend, and a Network selector card. Account loading reads the active network. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Fix broadcast hanging forever on a stalled responseAdam Soltys2026-06-261-9/+10
| | | | | | | | | | | | The Esplora broadcast cleared its abort timer right after fetch() resolved, so the subsequent res.text() body read had no timeout — a stalled response body hung the whole broadcast (and the send spinner) indefinitely, with the tx never reaching the network. Keep the abort timer active through the body read, and add a hard 30s outer timeout in the send handler so a wedged broadcast always surfaces as 'timed out — try again' instead of spinning forever. (The silent-payment tx itself is valid — verified structure + BIP143 signature over the real outputs.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Stage Electrum failover + coinos-primary cutover switchAdam Soltys2026-06-261-10/+33
| | | | | | | | | | | | | | | | | Failover (live now): the Electrum backend now tries an ordered list of servers, advancing to the next when one fails to connect or dies within ~5s. Named public presets fall over to the other public servers; a custom your-node URL does NOT fall over (won't silently leak your addresses to a server you didn't pick). Verified: dead primary → auto-advances to a working server. Cutover switch (dormant): COINOS_ELECTRUM (false) flips 'coinos' mode from explorer-REST + watcher push to a full Electrum backend pointed at wss://coinos.io/electrum, with the public servers as failover, and disables the watcher for coinos mode. Flip it true once Fulcrum serves data behind that endpoint — no new logic needed at cutover. Flag off = current behavior exactly (default coinos → esplora + watcher), so this ships safely while Fulcrum syncs. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fold the coinos opt-out into a single Data source pickerAdam Soltys2026-06-261-18/+23
| | | | | | | | | | | | | | | | | | | 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]>
* Add all working Electrum server presets from supertestnet's listAdam Soltys2026-06-261-0/+3
| | | | | | | | Tested all six suggestions: 5 connect and return correct data (blockstream electrs, btc.electroncash.dk / blackie / jochen Fulcrum, mempool.guide). Only bitcoin.grey.pw:50004 was unreachable, so it's left out. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add Electrum-over-WebSocket as an alternative data backendAdam Soltys2026-06-261-1/+46
| | | | | | | | | | | | | | | | | | | | | | | | | | Settings → Data source now offers 'Electrum' alongside the Esplora REST explorer. One Electrum WebSocket serves BOTH the wallet's data queries and scripthash push notifications, so pointing Hal at your own Fulcrum/electrs gives a fully self-hosted setup — no coinos, no public REST, nothing shared off your node. - src/electrum.js: ElectrumApi maps the Esplora shapes the wallet consumes onto Electrum methods (scripthash get_balance/listunspent/get_history, estimatefee, transaction.broadcast). electrs has no verbose tx lookup, so it parses raw txs and resolves input values from parent txs to compute fees (cached, in-flight deduped, bounded-parallel). Validated field-for-field against mempool.space: addressTxs (fee/vin/vout/status), getTx, outspend, balance, utxos. - wallet.js: the realtime socket already spoke Electrum's notify protocol; added request/response (_rpcCall) so the same connection answers data calls. Real Electrum notifications carry no tx data, so they reconcile via the backend. Replaced the fragile newline parser with a string-aware brace-depth scanner that handles every framing (newline, one-per-frame, concatenated, split) — fixes a buffer-poisoning hang under concurrent calls; also hardens the coinos watcher path. Esplora stays the default; nothing changes for existing users. Verified end-to-end against blockstream's electrs: concurrent transport, a full fresh-wallet scan (~2s), and the parser unit-tested on all framings. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Settings: let users opt out of coinos realtime notifications (privacy)Adam Soltys2026-06-251-6/+17
| | | | | | | | | | | 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]>
* Gift claim: detect already-claimed (even unconfirmed) and block the raceAdam Soltys2026-06-251-0/+6
| | | | | | | | | | | | Opening a gift link now checks whether its funding coin is already spent — via the Esplora outspend endpoint, which reports a spend even by an unconfirmed claim. The Claim button is gated until that check passes; if the coin is spent, show an 'Already claimed' screen with a link to the claim tx instead of a claimable amount. doClaim also re-checks on broadcast failure (the race window), and refuses to run while unchecked/taken — so a second opener can't attempt a doomed double-spend. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Realtime via self-hosted ZMQ watcher (Electrum-over-WS)Adam Soltys2026-06-251-4/+17
| | | | | | | | | | | | | | | | | | | Replace the unreliable public WebSocket push (mempool/blockchain.info address subscriptions never reliably fire) with a private notification service: - tools/zmq-watcher.js: a standalone Bun service (reusing coinos lib/zmq.ts's ZMTP subscriber) that reads Bitcoin Core's ZMQ rawtx/rawblock, computes the scripthash of each output, and pushes a blockchain.scripthash.subscribe notification to subscribed clients. No historical index (~24MB RAM) — just the live tx stream. Runs on the coinos box reading its Core's ZMQ. - wallet.js: realtime client now speaks the Electrum protocol (server.version, scripthash.subscribe, server.ping) over the WS; any scripthash notification triggers the existing refreshLive() (REST stays the source of truth). - api.js: wsUrl -> wss://coinos.io/electrum. Verified end-to-end: live mempool txs to subscribed addresses notify instantly. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add RBF fee bumping for unconfirmed sendsAdam Soltys2026-06-241-0/+5
| | | | | | | | | | | | | | | | | | - Signal opt-in RBF (BIP125, sequence 0xfffffffd) on all new sends so they're replaceable. - "Bump fee" on an unconfirmed sent tx (its detail view): rebuilds the same inputs + recipient outputs at the Priority rate, taking the extra fee from the change output (pulling in a confirmed coin if change can't cover it), re-signs, and routes to a "Replace with a higher fee?" confirmation showing old → new fee before broadcasting. - New keys translated into all 19 locales. Honest scope: P2A/TRUC/package-relay are L2/pre-signed-tx + node-relay features that don't apply to a single-sig wallet broadcasting single txs via public explorers, so they're intentionally out. CPFP (for receives / unbumpable sends) is a possible follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Choose your own block explorer; flatten How it worksAdam Soltys2026-06-241-41/+45
| | | | | | | | | | | | | | | - Settings → Block explorer: pick mempool.space (default, with blockstream.info as a silent failover), blockstream.info only, or a custom Esplora/electrs REST URL (e.g. your own node). The realtime WebSocket only applies to mempool.space; other explorers fall back to polling. Switching rebuilds the API and rescans. Replaces the old undocumented explorer-proxy hack. - How it works: merge the "For the technically curious" disclosure into the main body so it's all visible under "The basics", and dedupe the overlapping no-server/offline sentences. Mentions the new explorer setting. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add footer; default to public explorers (proxy via localStorage opt-in)Adam Soltys2026-06-151-3/+12
| | | | | | | | | | - Footer: "made with ♥ by Adam Soltys" (links adamsoltys.com) and "source available on GitHub" (links the repo). - Explorer endpoint now defaults to the public explorers; a rate-limited machine can opt into a proxy via localStorage('explorer-proxy'). Correct for a hosted multi-user deploy (each visitor uses their own IP). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Route explorer traffic through VPS proxy (temporary)Adam Soltys2026-06-151-6/+34
| | | | | | | | | | This IP is rate-limited/banned by mempool.space, so REST + WS now go through an nginx CORS proxy on the OVH VPS (mempool via /mp, blockstream via /bs). Controlled by a single PROXY constant in api.js — set it to '' to revert to the public explorers once the ban is lifted. Hosts are tagged by kind so fee format detection still works. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add per-request timeout + failover for unresponsive hostsAdam Soltys2026-06-151-2/+24
| | | | | | | | | | With concurrency 1, a single hung request (no response) stalled the whole serialized queue indefinitely — so balances/notifications never updated and the console filled with ERR_TIMED_OUT. Each request now has a 10s abort timeout; on timeout/network error the host is parked (20s) and we fail over to the other explorer. Applies to broadcasts too. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>