aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
...
* Add footer; default to public explorers (proxy via localStorage opt-in)Adam Soltys2026-06-153-4/+29
| | | | | | | | | | - 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]>
* Incremental frontier refresh (no full re-scan on deposit)Adam Soltys2026-06-151-46/+97
| | | | | | | | | | refreshLive escalated to a full scan() when the fresh address changed, which re-queried every coin's /utxo and /txs. Rewrite it to walk the frontier incrementally: advance through newly-used addresses, fetching /utxo + /txs for only those, and never revisit already-scanned coin addresses. WS events use the same path. A new deposit now costs ~5 requests instead of a full re-scan. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Watch only the fresh receive + change address over the WSAdam Soltys2026-06-151-7/+9
| | | | | | | | | | mempool's track-addresses caps at 10 per connection; we were sending all coin addresses + a 15-wide frontier, so the subscription was rejected and realtime updates silently stopped. With the frontier model (coins handled by post-send refresh + Nostr sync), only the next receive and change address need watching — so track exactly those two. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Sync wallet state across devices via encrypted Nostr eventsAdam Soltys2026-06-153-30/+176
| | | | | | | | | | | | | | Persist wallet state (addresses, utxos, history, indices, fees) as a NIP-44 encrypted, parameterized-replaceable event (kind 30078) addressed to self on relay.coinos.io. The Nostr identity is derived from the same seed (NIP-06), so no extra secret. On load we pull the latest state from Nostr — a device with no local cache gets full state without re-scanning, and another device's updates propagate — using savedAt to keep the newest copy. localStorage stays as the instant/offline cache; state is published (debounced) on every change. (Relay note: added kind 30078 to the strfry write-policy allowlist on prod.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Stop re-scanning coin addresses; frontier-only poll + manual rescanAdam Soltys2026-06-152-14/+25
| | | | | | | | | | | Per request: an address is scanned once, then never auto-scanned again. The routine poll now checks only the fresh frontier (next receive + change address) for new deposits; coin-holding addresses are no longer polled and the periodic full re-scan is removed. Spends on existing coins arrive via the WebSocket while connected; a new Settings → Rescan button does a full scan to catch anything missed (e.g. a payment to a reused old address). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use cached state on reload; light poll instead of full re-scanAdam Soltys2026-06-151-2/+5
| | | | | | | | | | | On load we restored the cache for instant display but then always ran a full scan (every address's /address + /utxo + /txs) — so a refresh re-fetched everything. Now when a cache exists, the load path runs the light frontier poll (only live addresses, no utxo/txs re-fetch) and escalates to a full scan only if something changed. A brand-new wallet still does a full scan, and the periodic deep scan still runs. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Route explorer traffic through VPS proxy (temporary)Adam Soltys2026-06-152-10/+36
| | | | | | | | | | 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]>
* Cache stable addresses; poll only the live frontierAdam Soltys2026-06-151-4/+50
| | | | | | | | | | | Idle polls were re-querying every used address (~N requests each cycle). Now the background poll checks only addresses that can change — those holding coins and the next fresh receive/change address — and escalates to a full scan only when one of them moves. Stable historical addresses stay cached. A full scan still runs every 5 min as a safety net. Cuts steady-state request volume dramatically for wallets with many used addresses. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Prefer mempool, cooldown 429'ing hosts (stop Blockstream storm)Adam Soltys2026-06-151-16/+33
| | | | | | | | | | Round-robin sent half of every scan to Blockstream, which hard-rate-limits this IP (429 on each), and we kept routing back to it — stalling scans so new balances never showed. Now hosts are tried in order (mempool first) and any host that 429s is parked for 30s, so traffic stays on the healthy host and Blockstream is fallback-only. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Keep the WebSocket alive: heartbeat + reconnect-on-wakeAdam Soltys2026-06-151-0/+47
| | | | | | | | | | | The mempool WS sends no periodic traffic on a track-addresses-only subscription, so a half-open connection (died without firing onclose) went undetected and updates fell back to the slow poll. Add: - ping every 15s ({action:'ping'} -> pong); if no message for 45s, force a reconnect (handles half-open sockets). - reconnect + refresh on tab refocus (visibilitychange) and network 'online'. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Broadcast sends to all explorers in parallelAdam Soltys2026-06-151-15/+15
| | | | | | | | Post the signed tx to both mempool.space and Blockstream at once (outside the throttle, since a send is urgent) so it's visible regardless of which explorer the recipient watches. Succeeds if any accept; only fails if all do. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Apply WS tx payload instantly; faster reconcileAdam Soltys2026-06-151-1/+41
| | | | | | | | | | | | | | The mempool.space WebSocket push already contains the transaction, so parse it on arrival to add the incoming UTXO and bump pending immediately, instead of waiting on a debounced re-scan. The scan still follows (400ms) to reconcile spends/confirmations. Removes the ~3s our handler added on top of mempool.space's own push latency. (Measured: mempool.space itself takes ~8s to see a coinos-broadcast tx due to p2p propagation — the real fix for incoming-payment latency is the sender pushing directly to the explorer.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Global rate-aware request proxy + lighter pollingAdam Soltys2026-06-152-84/+110
| | | | | | | | | | | | | | 429 handling: route every outgoing request (address, utxo, txs, fees, broadcast) through one scheduler. A 429 from ANY request now pauses ALL subsequent requests with exponential backoff (2s→30s), easing off after a streak of successes — instead of only cooling down the same host/kind. Fewer requests: derive balance from /address chain_stats (no /utxo needed just to total), and on background polls fetch only /address. The heavier /utxo + /txs are pulled only on first load or when the balance/addresses actually change. Idle polls drop from ~3N requests to ~N. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Default to sats unit for first-time usersAdam Soltys2026-06-151-2/+3
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show confirmed as "Balance"; pending only when presentAdam Soltys2026-06-151-9/+9
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Straighten unit-label underline (zero inherited border-radius)Adam Soltys2026-06-151-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use solid underline for clickable unit labelsAdam Soltys2026-06-151-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Multi-recipient send, simpler review, global persisted unitAdam Soltys2026-06-152-70/+119
| | | | | | | | | | | | | | - Send: support multiple outputs. Default one recipient with a "+ Add recipient" button (and a remove ✕ per row). Max applies only when there's a single recipient. - Review: drop the technical lines (total, inputs/vsize, change, total cost). Show just the amount going to each output plus the network fee. - Units: replace the per-form BTC/sats toggle with a single global unit that persists in localStorage across refreshes and logouts. Every unit label (balance, breakdown, amounts, fee, coins, review) is clickable to toggle and all displays use the chosen unit consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Normalize amount input out of scientific notationAdam Soltys2026-06-151-1/+13
| | | | | | | | Chrome's number input emits "1e-8"-style values when stepping the tiny BTC step; convert any scientific-notation value back to a plain decimal string on input. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Set amount step to 0.00000001 for BTC (1 for sats)Adam Soltys2026-06-151-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use integer amount placeholder when unit is satsAdam Soltys2026-06-151-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Default custom fee to the economy rateAdam Soltys2026-06-151-1/+7
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Hide "Selected rate" line when entering a custom feeAdam Soltys2026-06-151-1/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix Max button, slash scan requests (429s), cache history locallyAdam Soltys2026-06-153-16/+98
| | | | | | | | | | | | | | | | | | | | Max button: fill the amount field with the computed maximum (n inputs, 1 output, current fee rate) so it gives visible feedback instead of just blanking/disabling the field. Rate limits: eliminate look-ahead (gap limit 1 — stop at the first unused address, safe because we only ever expose one unused address at a time), query one address at a time (API concurrency 1, 400ms spacing across two backends), scan chains sequentially, and pause 500ms after each used address. An empty scan is now ~2-3 requests instead of ~10+. History cache: persist scanned state (addresses, utxos, txs, indices) in localStorage keyed by sha256(seed) — the seed itself is never stored. Re- importing the same seed in this browser shows the last-known balance/history instantly while a fresh scan runs. Cleared by nothing automatically (public chain data); different seeds get separate keys. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop live badge + fiat price; persist wallet in sessionStorageAdam Soltys2026-06-153-39/+42
| | | | | | | | | | | | | - Remove the online/live status badge and the USD price line, which both appeared after load and caused layout shift. Stop fetching price entirely. - Persist the seed + passphrase in sessionStorage so a page refresh restores the open wallet instead of returning to the unlock screen. Cleared on logout and (by the browser) when the tab closes. Note: sessionStorage holds the seed in plaintext for the tab session — an acceptable tradeoff for this self-custody tool and scoped to the tab. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add polling safety-net so pending balances appear reliablyAdam Soltys2026-06-151-14/+47
| | | | | | | | | | | | | | | | | | Relying solely on the mempool WebSocket was unreliable: it only pushes new events (no replay), and pushes can be missed (drops, server quirks), so a deposit could go unseen with no manual refresh to fall back on. - Add a 12s background poll alongside the WebSocket. - Make background refreshes "silent": scan({silent:true}) re-renders only when the visible state (utxos/txs/fresh indices) actually changes, so polls never flash the UI or steal input focus while typing. - Skip fees/price fetches on routine polls to keep request volume low. - WS events now trigger the same silent refresh (debounce 800ms). Unit-tested: silent refresh emits once when a pending deposit appears and not at all when nothing changed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Simplify Receive tab to QR + address + copyAdam Soltys2026-06-151-38/+2
| | | | | | | Drop the heading, privacy note, derivation path, and the "View all addresses" list. Remove the now-unused addressBook and showAddrs state. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Replace Coins tab with a Settings tabAdam Soltys2026-06-151-72/+38
| | | | | | | | | Remove the standalone Coins (UTXO list) tab and fold snapshot import/export into a new Settings tab alongside the recovery-phrase reveal. Offline fallback, goHome, the offline banner, and post-import navigation now point at Settings. (Manual coin selection still lives in the Send form.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Move Backup from header to its own tab (away from Logout)Adam Soltys2026-06-152-42/+26
| | | | | | | | Prevents accidental Logout clicks when reaching for Backup. Backup is now a tab rendering the gated recovery-phrase reveal inline; Logout is the only header button. The phrase re-masks whenever tabs change. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gate recovery-phrase reveal behind a warning + Proceed stepAdam Soltys2026-06-152-13/+30
| | | | | | | | | Opening Backup now shows the warning with the 12 words masked (••••••) and a Proceed/Cancel choice. The real words and passphrase are kept out of the DOM until Proceed, so the user reads the "make sure nobody is watching" warning before anything sensitive renders. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add Backup button to view recovery phrase + passphrase againAdam Soltys2026-06-152-1/+41
| | | | | | | | Anyone who skipped backup verification (or just wants to re-check) can now reveal the seed words and passphrase from a header "Backup" button that opens a warning-gated modal with copy actions. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Verify passphrase on backup step only when one was enteredAdam Soltys2026-06-151-1/+21
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* passphrase labelAdam Soltys2026-06-151-1/+1
|
* Remove Tools tab; move snapshot import/export to CoinsAdam Soltys2026-06-141-54/+28
| | | | | | | | | The Tools page held a now-redundant manual offline toggle (we auto-detect), a small wallet-info blurb, and the offline snapshot import/export. Drop the page and relocate snapshot import/export to the Coins tab. Offline fallback and goHome now land on Coins; the offline banner points there. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop fee rate numbers from fee buttons (shown below already)Adam Soltys2026-06-141-2/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Simplify: auto-detect offline, mainnet-only, no manual refreshAdam Soltys2026-06-142-72/+73
| | | | | | | | | | | | | | - Remove the unlock-screen "Offline mode" checkbox and network selector. Always mainnet. Attempt a scan on open; if navigator is offline or the scan fails (network unreachable), fall back to offline automatically with a banner + Retry instead of an upfront toggle. - Drop the manual balance "Refresh" and receive "Check for payments" buttons; the WebSocket keeps things current. - Update balances silently: add a wallet.loaded flag so the balance only dims on the very first load and history/coins only show a spinner before the first load. Background WS-driven rescans no longer flash spinners. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Reduce gap limit to 5; clarify receive-address noteAdam Soltys2026-06-142-2/+8
| | | | | | | | | | | Since the wallet only ever exposes one unused receive address at a time, used addresses stay contiguous (gap never exceeds 1), so a gap limit of 5 is a safe margin and cuts a scan from ~40 to ~10 requests. Also reword the receive note: the address advances automatically (via the realtime WebSocket) when paid, so drop the confusing "scan again". Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Tweak seed phrase placeholder wordingAdam Soltys2026-06-141-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Use friendlier seed phrase placeholder textAdam Soltys2026-06-141-2/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Defeat scan 429s with multi-backend rotation + cooldownAdam Soltys2026-06-141-39/+105
| | | | | | | | | | | | | | | | | | The "CORS" errors were 429 Too Many Requests (a 429 has no CORS headers, so the browser reports it as a CORS failure). SSL/hosting is irrelevant. Per-request retries made it worse by multiplying load on an already rate-limited host. Replace with: - round-robin across two Esplora-compatible backends (mempool.space + Blockstream), halving per-host load - park a host on an 8s cooldown when it 429s instead of retrying it - keep the global throttle (2 concurrent, 200ms gap) - map Blockstream's /fee-estimates to the named fee tiers Unit-tested rotation/cooldown; live scan of 40 addresses now completes in ~8s with no failures. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix scan rate-limiting (CORS errors) + tidy seed inputAdam Soltys2026-06-142-11/+55
| | | | | | | | | | | | | | | | | The "CORS" errors during scan were actually mempool.space rate-limiting: bursting ~40 address lookups gets some throttled, and throttled responses lack the access-control-allow-origin header, which the browser reports as a CORS failure. - api.js: route all requests through a global throttle (max 2 concurrent, 250ms min gap between starts, ~4 req/s) with exponential backoff retry so occasional throttles recover instead of failing. Unit-tested spacing + retry recovery. - Import pane: drop the "(12 or 24 words)" parenthetical (we generate 12), and replace the scrunched ellipsis placeholder with an enumerated word1..word12. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rename Lock button to LogoutAdam Soltys2026-06-141-2/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make skip-verification a full-width button matching VerifyAdam Soltys2026-06-141-2/+2
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rebuild as single-file offline BIP84 walletAdam Soltys2026-06-1414-435/+1923
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Replace the SvelteKit/daisyUI prototype with a vanilla JS + hand-rolled CSS app that Bun bundles (code + scure/noble crypto + QR + CSS) into one self-contained dist/index.html runnable offline from file://. Wallet core (src/wallet.js): - BIP84 native-segwit HD wallet from BIP39 seed (+optional passphrase) - gap-limit scan of receive/change chains, aggregate balance, tx history - fresh receive/change addresses; signs with witnessUtxo only (enables offline signing with no prev-tx fetch) - coin selection + fee estimation via btc-signer selectUTXO; send-max; manual coin control - offline JSON snapshot export/import (keyless) + signed-tx hex/QR export - realtime mempool.space WebSocket: track-addresses -> debounced auto refresh of balances/history (no polling); live status badge UI (src/app.js): clean light paper-wallet; create (with optional, skippable backup verification) / import flow; receive (QR) / send / history / coins / tools tabs; clickable logo returns home. API (src/api.js): mempool.space wrapper with retry/backoff for scan-burst rate limiting; single network choke-point for offline enforcement. Verified: BIP84 spec vectors, build/sign/parse for auto/max/manual flows, snapshot roundtrip, live scan + WebSocket, headless-Chrome UI smoke tests. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* flexAdam Soltys2025-09-152-29/+54
|
* moar buttonsAdam Soltys2025-09-151-1/+11
|
* seed toggleAdam Soltys2025-09-152-13/+24
|
* new buttonsAdam Soltys2025-09-083-22/+65
|
* combine balancesAdam Soltys2025-07-151-5/+7
|