aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
* Push silent payments over WebSocket instead of pollingAdam Soltys2026-06-271-1/+65
| | | | | | | | | | | | The indexer now serves a /ws endpoint and publishes each newly-indexed block's SP items to subscribers. The wallet subscribes (_spConnect) and scans pushed items on arrival (_scanSpItems) — folding receipts into the balance instantly — catching up via /scan on connect/reconnect. Polling for SP is now only a fallback for when the socket isn't live. Verified on regtest: connect, mine a block, wallet receives the push. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Auto-scan for silent payments + fold found value into balanceAdam Soltys2026-06-273-10/+19
| | | | | | | | | | | scanSilentPayments() now recomputes the balance so received SP value shows in the wallet total (not just the Receive card), and gains a silent mode. It's triggered automatically after each full scan and on the poll loop (incremental, cheap), so payments appear without tapping anything. The Receive card now shows a status line ('Watching for payments' / found amount) with a small manual Rescan instead of a primary Scan button. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Spend received silent-payment outputs (taproot key-path)Adam Soltys2026-06-271-23/+70
| | | | | | | | | | | | | | | buildTx now includes received SP outputs in the coin pool, and sign() spends them as taproot key-path inputs signed manually with d = spend_priv + t_k (the output key is used raw — no BIP-341 tweak — so btc-signer's auto key-path signing doesn't apply; we set tapKeySig from preimageWitnessV1 + schnorr.sign). The fee estimator is fed a tapInternalKey (sizing only) with disableScriptCheck. SP value folds into the displayed balance. Paying an sp1… recipient still uses BIP84 coins (SP-input-funded SP sends need even-Y handling, deferred). Validated on regtest via testmempoolaccept: a wallet-built, wallet-signed spend of a received SP output is accepted by Core. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Wire silent-payment receiving into the wallet (scan + balance + UI)Adam Soltys2026-06-273-6/+171
| | | | | | | | | | | | | | | | | | | wallet.js: scanSilentPayments() pulls the indexer's /scan range, matches outputs on-device, and verifies each is unspent via the chain backend; spUtxos + balance are tracked in their own bucket (one-time taproot keys), persisted in the cache. silentPaymentsAvailable()/spBalance() expose state. app.js: the Receive tab shows the reusable sp address (QR + copy), a 'Scan for silent payments' button, and the found amount; Settings gains a per-network SP indexer picker (under Data source) with custom/self-host support. sp-indexer.js: added /scan/{from} so a client fetches a whole range in one request instead of one fetch per block. Receiving verified end-to-end on regtest (scan finds the output, 49,999,800 sat confirmed). Spending SP outputs is the remaining piece. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Wire SP foundations into the wallet: configurable indexer + keys/addressAdam Soltys2026-06-272-1/+57
| | | | | | | | | | | | 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]>
* Add regtest SP tweak indexer; accept Core's array-witness in inputPubKeyAdam Soltys2026-06-271-11/+17
| | | | | | | | | | | | | | tools/sp-indexer.js: a minimal BIP-352 oracle (Bun) that walks Core and serves, per block, one entry per eligible tx — the tweak (input_hash*A) plus that tx's taproot outputs — for the wallet to scan on-device. Shares src/silentpay.js so the tweak math is the vector-validated code. Added to the regtest compose as the 'sp' service (oven/bun, pointed at bc:18443). inputPubKey now accepts a witness as either serialized hex (vectors) or an array of stack items (Core getblock RPC). Verified end-to-end on regtest: a real SP payment is indexed and the scan recovers the output, value, and spend tweak. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add BIP-352 receiving crypto (scan, tweak, keys, address)Adam Soltys2026-06-271-0/+177
| | | | | | | | | | | | | | | | | | silentpay.js gains the receiving half of silent payments, validated against all official BIP-352 vectors (29/29 receiving sub-cases): - inputPubKey(): recover the eligible input pubkey from P2PKH/P2WPKH/P2SH-P2WPKH/ P2TR inputs (skips uncompressed keys, excludes NUMS script-path, hash160-matches malleated P2PKH scriptSigs) - silentPaymentTweak(): the oracle tweak input_hash*A (sum over eligible inputs, input hash over all outpoints, point-at-infinity guarded) — used by the indexer - silentPaymentScan(): ecdh = scan_priv*tweak, derive P_k = B_spend + t_k*G, match the tx's taproot outputs, return per-output spend tweaks - silentPaymentOutputPrivKey(), deriveSilentPaymentKeys() (m/352' paths), encodeSilentPaymentAddress() Labels not yet implemented (wallet uses a single no-label address). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Don't flash 'Payment received' on import for a pre-existing depositAdam Soltys2026-06-271-1/+6
| | | | | | | | | | | On a fresh open, receiveSeenIndex was baselined to the pre-scan frontier (line 691), so the moment the socket's reconcile credited a deposit that predated the open, nextReceiveIndex advanced past it and the celebration flashed for ~2s until the post-scan baseline caught up. Leave receiveSeenIndex null until that final baseline for fresh opens (matching the documented intent); the != null guard then suppresses the flash. Same-session refreshes still use the persisted ack. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Offline transfer copy: drop the '(with this wallet + seed)' asideAdam Soltys2026-06-271-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Data source copy: drop the '— no REST' aside from the Electrum descriptionAdam Soltys2026-06-271-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Data source copy: drop coinos branding, "your own server" not Fulcrum/electrsAdam Soltys2026-06-271-2/+2
| | | | | | | | Hal stands on its own, so the data-source descriptions shouldn't name coinos: the Electrum description now says "Point it at your own server … no REST" and the block-explorer one drops "nothing is sent to coinos". Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Payment details: say "Pending", not "Pending (in mempool)"Adam Soltys2026-06-272-2/+1
| | | | | | | The mempool parenthetical is jargon for non-technical users; the status line now reuses the plain `pending` string. Drop the now-unused pendingInMempool key. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Sort pending txs newest-first in History (by firstSeen, not blockTime)Adam Soltys2026-06-271-0/+4
| | | | | | | | | | | | All pending txs share blockTime 0, so _sortTxs's blockTime comparator returned 0 for every pending pair and they kept array (scan/arrival-append) order — so the most recently received tx sat at the bottom of the pending group instead of the top. Order pending txs by firstSeen descending (the timestamp we already record when a tx is first seen unconfirmed); confirmed txs still sort by block time. Also sort on cache restore so the order is right immediately, not only after the first refresh. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Harden Electrum WS recovery: fail in-flight calls on drop, faster half-open ↵Adam Soltys2026-06-271-4/+12
| | | | | | | | | | | | | | | | | | | detect After a socket drop the in-flight _rpcCall requests were never rejected (only stopRealtime did that), so dead calls held every _rpcPump slot until they timed out — stalling the post-reconnect refresh by ~10s even though the new socket and its pushes were already live. Now reject pending calls in ws.onclose and in _reconnectNow (which nulls onclose), so the pump frees instantly and the on-open refreshLive runs right away. Also tighten the half-open watchdog: ping every 10s and recycle the socket after 21s of silence (two unanswered pings) instead of 45s, so a silently-dead connection — no onclose, no online/visibility event — resumes pushing in ~20s instead of ~45s. Verified via CDP: force-closing the live socket reconnects in ~0.6s and the next deposit pushes in ~0.4s (UI ~1.2s). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Allow importing a seed from a watch-only wallet's settings pageAdam Soltys2026-06-271-4/+15
| | | | | | | | | | The recovery-phrase section of the per-wallet settings page now shows an Import seed action for watch-only wallets, with the load-seed card inline. The load-seed flow takes an account id (defaulting to the active wallet), so it can upgrade a specific watch-only wallet — validating the entered seed against that wallet's xpub before making it spendable. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make the wallet-settings link a card with a clear button, not a bare headingAdam Soltys2026-06-272-1/+6
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Link to the current wallet's settings from the top of global SettingsAdam Soltys2026-06-271-0/+4
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* De-dup wallet settings; reorder per-wallet page; lowercase 'this'Adam Soltys2026-06-272-94/+22
| | | | | | | | | | The global Settings tab no longer shows the recovery phrase / public key — those live only on each wallet's settings page now (the watch-only 'load seed' upgrade stays in the global tab since it targets the active wallet). On the per-wallet page, recovery phrase moves to the top and auto-logout to the bottom. Removes the now-dead pubkeyCard/autoLockCard. Lowercases 'THIS' in the auto-logout copy. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add a per-wallet settings page (cog from the accounts list)Adam Soltys2026-06-272-2/+88
| | | | | | | | | | The accounts list now shows a settings cog (instead of a rename pencil) that opens a per-wallet settings page: edit name, set auto-logout (moved here from the global settings tab), reveal the recovery phrase, and show the public key (zpub) — all read from the account itself, so it works for any wallet, not just the active one. Room for more per-wallet settings later. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Per-network data source; drop duplicate regtest fields; add mutinynetAdam Soltys2026-06-274-154/+137
| | | | | | | | | | | | | | 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-272-42/+26
| | | | | | | | | 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]>
* Update coinos data-source copy now that it's our own Fulcrum nodeAdam Soltys2026-06-271-1/+1
| | | | 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]>
* Fix electrum reconcile stall: keep _rpcId numeric (id:null broke matching)Adam Soltys2026-06-271-2/+5
| | | | | | | | | | | _rpcId could go non-numeric, so ++ produced NaN and requests serialized with "id":null. _handleRpc matches responses with `msg.id != null`, so every data-call response was dropped → the call timed out → _reconcileHeld skipped the address → confirmations never reflected (until an unrelated full refresh). Coerce to a number on every send. Verified via CDP: a mined deposit now flips pending→confirmed in ~1s instead of stalling. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Use Fulcrum (Electrum/WS) for regtest data + push, dropping esploraAdam Soltys2026-06-272-2/+6
| | | | | | | | | | | | 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]>
* Regtest realtime via a local Electrum-over-WebSocket bridgeAdam Soltys2026-06-272-11/+31
| | | | | | | | | | | | | 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]>
* Add regtest network supportAdam Soltys2026-06-273-8/+82
| | | | | | | | | | 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]>
* Refresh tx confirmations on poll so non-WS backends don't show stale pendingAdam Soltys2026-06-271-0/+11
| | | | | | | | | | | The light frontier poll walks past already-used addresses, and _reconcileHeld refreshed held-coin balances but not the history tx summaries — so a pending->confirmed transition only showed via the realtime WebSocket push. Backends without that push (custom electrs/esplora, regtest) left the History row stuck on "pending" even after the tx confirmed. Re-fetch and update tx summaries for changed held addresses during reconcile. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Throttle Electrum requests so scans don't overwhelm public serversAdam Soltys2026-06-261-7/+32
| | | | | | | | | | | The Electrum data path fired every request at once, so a scan burst dozens of calls at a rate-limited public server, which then dropped some (the timeouts). Add a concurrency cap (4 in flight; Electrum pipelines, so it stays fast) with excess queued — mirrors the Esplora scheduler's intent. Verified a full scan via the public jochen server completes in ~4.5s with peak concurrency 4, no timeouts. The throttle queue is drained on disconnect/stop alongside in-flight calls. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix stale cross-device balance + Electrum call timeoutsAdam Soltys2026-06-261-16/+17
| | | | | | | | | | | | | | | | | | Two Electrum-backend issues: 1. Stale balance after a send made on another device: _reconcileHeld set each address's balance from get_balance/address-stats, where a confirmed coin being spent by an unconfirmed tx STILL counts as confirmed (with a compensating negative unconfirmed). So a device that didn't make the send kept showing the spent coin's value. Now the entry balance is derived from the actual unspent set (listunspent, which excludes spent coins) — verified a spent coin clears. 2. Per-address rescan failing with 'electrum timeout: blockchain.scripthash. get_balance': public Electrum servers can drop a response or the socket can flap mid-call. _rpcCall now retries a couple of times on a fresh id (12s each) before giving up. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Update balance/history optimistically right after a sendAdam Soltys2026-06-262-2/+54
| | | | | | | | | | | | | On the Electrum backend the post-send rescan is many round-trips, so the balance lagged a few seconds. Now a successful broadcast immediately applies the tx locally (applySentTx): drop the spent coins, credit change as pending, add a pending history row, recompute the balance, and emit — so the UI reflects the send instantly. The realtime watcher / backstop poll reconciles and fills in the confirmation once the explorer has indexed it (no immediate rescan, so a not-yet-indexed tx can't briefly revert the optimistic state). Verified the balance/utxo/history math. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix broadcast hanging forever on a stalled responseAdam Soltys2026-06-263-10/+15
| | | | | | | | | | | | 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]>
* Don't hold the send spinner up during the post-broadcast rescanAdam Soltys2026-06-261-1/+4
| | | | | | | | | | broadcast() set the success result but kept ui.busy=true and didn't render until after awaiting wallet.scan(), so the 'Sign & broadcast' button spun for the whole post-send rescan (slow under rate-limiting / on the Electrum backend) — looking stuck even though the tx had already broadcast. Now it shows the success screen the instant the broadcast resolves and refreshes balances in the background. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Confirm before removing a single wallet (like Clear all)Adam Soltys2026-06-262-1/+18
| | | | | | | | The ✕ on a wallet now opens a confirmation screen warning it's removed from this device (keep its recovery phrase; other wallets unaffected) instead of deleting immediately. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Logo returns to the wallet's Receive page; add paste-from-clipboard buttonsAdam Soltys2026-06-262-24/+48
| | | | | | | | | | | - goHome: when a wallet is open, the logo goes to its Receive page (its tabs) instead of the create/import screen — so it's always a way back to your wallet. Only with no wallet open does it show create/import. - Paste buttons: a clipboard 'Paste' icon on the send address field and the import (seed/key/xpub) and 'load seed to spend' fields, filling them from the clipboard. Hidden where the Clipboard read API isn't available. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add 'Change password' for the saved-to-device vaultAdam Soltys2026-06-262-5/+30
| | | | | | | | | | | A vault exists → Accounts screen shows Change password: enter the current password, then a new one (with confirm; leave blank to remove it). It decrypts the actual vault and re-encrypts it with the new password, so wallets not loaded in the current session are never dropped. Keeps the in-memory password in sync when the vault is already unlocked. Verified the re-encrypt round-trip (old rejected, new works, blank works). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make auto log-out per-wallet — only signs out the wallet it's set forAdam Soltys2026-06-263-56/+117
| | | | | | | | | | | | | | | | | | The logout timer is now an account setting (stored per account in the session and the durable directory), not a global one, and it removes only that wallet from the device — its session entry, directory entry, cached state (xpub + seed keys), and its vault entry — leaving other wallets untouched. - wipeAccount(id) surgically removes one wallet; the active one falls back to the next account (or the unlock screen). - Background-away timestamp drives per-account evaluation on resume/boot; an armed timer fires for the soonest-due wallet so a blurred-but-visible window still signs out on time. Overdue saved wallets are removed from the vault when it's unlocked (the only point we can re-encrypt it). - New cacheKeyFor() export lets app code target one wallet's cache keys exactly (verified against the wallet's seed/xpub/watch keys). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Auto log-out wipes the saved-to-device vault too (full device wipe)Adam Soltys2026-06-262-14/+7
| | | | | | | | With a logout timer set, signing out now removes everything from the device including the password-protected vault — nothing survives, so the recovery phrase is required to return. Updated the setting copy to say so. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Auto log-out also wipes a blank-password (unprotected) vaultAdam Soltys2026-06-261-0/+11
| | | | | | | | A vault saved with no password auto-unlocks on open, which would undo the sign-out. When a logout timer fires (or a boot is overdue), remove it too — only a password-protected vault survives. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add auto log-out (background timer) + make vault passwords optionalAdam Soltys2026-06-262-10/+119
| | | | | | | | | | | | | | | | Auto log-out: a Settings option (Never default, 1m/5m/1h/1d) that, after the app has been backgrounded/unfocused that long, signs out completely — clears the session, the watch-only directory, and cached balance/history from the device (only password-saved vault wallets remain). The countdown only runs while the app is hidden/unfocused, never while it's open and focused; a background timestamp covers throttled/discarded-tab cases on resume/boot. Optional vault passwords: drop the 8-character minimum and allow a blank password. A blank-password vault auto-unlocks on open with no prompt (Save to device with no password = just persist). Verified blank round-trip + wrong- password rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Persist wallets as watch-only across sessions; re-enter seed to spendAdam Soltys2026-06-263-25/+178
| | | | | | | | | | | | | | | | | | | | | | | Closing the browser without 'Save to device' used to wipe the wallet entirely. Now every wallet's account xpub is mirrored to a durable localStorage directory (never the seed), so it survives a session wipe as a watch-only entry — you keep seeing balance and history, and re-enter the seed to spend again. - Durable account directory: saveDirectory() mirrors {id,label,xpub} for every account; restore falls back to it as watch-only when there's no session/vault. - Instant view: a full wallet also caches under its xpub key, so the downgraded watch-only shows balance/history immediately (no rescan). Verified round-trip. - Re-enter seed to spend: the Send tab (now shown for watch-only) and Settings prompt for the recovery phrase / xprv; it must derive to this wallet's xpub (verified via accountXpubFor — wrong seed/passphrase rejected), with an option to Save to device at the same time. Upgrade is in place (id/label/cache kept). - Vault merge now dedupes by xpub so saved + non-saved wallets coexist without duplicates. Privacy note: the xpub now persists by default (someone with device access can see balance/history without the seed); 'Remove' clears it. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Stage Electrum failover + coinos-primary cutover switchAdam Soltys2026-06-262-12/+50
| | | | | | | | | | | | | | | | | 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-263-58/+57
| | | | | | | | | | | | | | | | | | | 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-265-59/+438
| | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Add Silent Payments (BIP-352) sendingAdam Soltys2026-06-254-9/+188
| | | | | | | | | | | | | | | | | Pay sp1… addresses: the wallet derives the one-time taproot output from the recipient's address + the transaction's own input keys (ECDH), so payments are unlinkable on-chain with no interaction. New src/silentpay.js implements the sender derivation + bech32m address decode, validated against the official BIP-352 test vectors (all P2WPKH-input / single-recipient cases pass; the rest exercise input types Hal never spends). buildTx detects sp1… recipients, selects coins against a same-size placeholder taproot output, then swaps in the script derived from the final input set (updateOutput). Works for normal sends, multi-recipient, and send-max. The review screen shows the sp1… address with a 'Silent payment' label (the on-chain output is a derived one-time taproot address). End-to-end signing verified. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Settings: let users opt out of coinos realtime notifications (privacy)Adam Soltys2026-06-253-8/+38
| | | | | | | | | | | 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]>
* Open block-explorer links via window.open (reliable in standalone PWA)Adam Soltys2026-06-251-3/+10
| | | | | | | | Reorder the already-claimed actions (Create a wallet anyway above the explorer link) and force explorer links to open in a new window/tab via window.open — inside an installed PWA, <a target=_blank> can navigate the app away instead. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Already-claimed: move 'Create a wallet anyway' above the explorer linkAdam Soltys2026-06-251-5/+5
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make the gift-claim wallet provisional until committedAdam Soltys2026-06-251-2/+22
| | | | | | | | | | | Opening a gift link generates a wallet only to claim into. Mark it provisional so bailing from an already-claimed gift (Go to home / logo) discards it instead of leaving an empty account in the session list. It's committed — kept for good — the moment the user claims, chooses 'Create a wallet anyway', or otherwise enters the wallet (incl. a refresh). Critically, commit on claim *success* so the backup step can't discard a wallet that just received funds. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>