| Commit message (Collapse) | Author | Age | Files | Lines |
| ... | |
| |
|
|
|
|
|
|
|
|
|
| |
- The "Payment received!" celebration now persists: the acknowledged fresh
index is stored (per seed) in localStorage, so a received payment keeps
showing the dialog until you tap past it, even after a refresh.
- Footer: only "GitHub" is the link now.
- README rewritten for the current state (mainnet-only, realtime, Nostr sync,
multi-recipient, sats default, explorer resilience, etc.).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
When a deposit lands on the shown address, the Receive tab now shows a
"Payment received!" dialog (with the amount and a green check) and waits for a
tap before revealing the next address/QR — instead of silently swapping it.
Tracks the acknowledged fresh index so only payments that arrive while
watching trigger it.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
The pending balance showed immediately from the WS ingest, but the next
receive address/QR only appeared after the debounced refreshLive. Now the WS
ingest marks the receiving address used and advances the fresh index right
away, so the new address shows in the same update as the balance.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| | |
|
| |
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| | |
|
| | |
|