| Commit message (Collapse) | Author | Age | Files | Lines |
| ... | |
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
A footnote at the bottom: "The name Hal Wallet is a tribute to
legendary cypherpunk and Bitcoin pioneer, Hal Finney (1956–2014)",
with his name linking to his Wikipedia article. The contextual nod,
rather than leaning on his name up front.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Device sync:
- Add a "Device sync" settings card: toggle (on by default) + editable
relay list (defaults to wss://relay.coinos.io). When off, the wallet
never contacts a relay. nostr.js is now multi-relay and reads an
enabled/relays preference from localStorage.
Reconciliation (fixes a stray "payment received" + stale history on
import, especially via the Nostr/cache load path):
- Always full-scan on load instead of a frontier-only refreshLive, so
balance, history confirmations and spends are reconciled and the
celebration baselines against everything that already exists (cache/
Nostr still shows instantly first).
- Actually start the periodic full-scan safety net (_deepTimer, 2 min);
it was declared but never wired up.
- WS pushes now refresh an existing tx's pending→confirmed status, not
only add new transactions.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- Headline balance now shows wallet.total (confirmed + mempool) instead
of confirmed, so a pending outgoing spend is debited immediately rather
than waiting for confirmation. The chain_stats-based confirmed balance
counts a coin as yours until the spend is mined, which is why it didn't
drop after broadcasting (incl. scanned-tx broadcasts). Incoming pending
is still surfaced by the "+X pending" line.
- summary { cursor: pointer } so the "Show QR" disclosure (and any other
<summary>) shows the hand cursor.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
The address input scrolls/truncates on mobile, hiding the ends. Render
the full address as wrapping text beneath it with the first/last 6
chars emphasized, so paste/type/scan can be verified without scrolling.
Updated imperatively on input so the field keeps focus.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Scan a Bitcoin address, a BIP21 URI (fills address + amount), or a
signed-transaction QR (opens a broadcast confirmation, then relays it)
from a viewfinder button on the Send form.
Decoder strategy keeps the main bundle small:
- Prefer the native BarcodeDetector (Chrome desktop/Android) — no lib.
- Otherwise lazy-load jsQR from a separate dist/jsqr.js, only when the
scanner is opened. jsQR stays out of index.html (~424 KB vs ~551 KB)
and off the initial load.
- The service worker precaches jsqr.js so offline scanning works for
installed PWAs on browsers without BarcodeDetector.
The scanner needs a camera + secure context, so it never shows on a
standalone file:// page; offline air-gapped signing is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
- Add a "Sign & export (broadcast elsewhere)" action on the online
review screen so you can sign here and broadcast from another device,
not just in offline mode.
- The signed-result screen now reads as an amber caution ("Signed, but
not sent yet… it will not send on its own") instead of a green
success banner, so it can't be mistaken for a completed send.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
| |
The reveal-phrase Hide button used the default size (16px) next to the
btn-sm Copy phrase button (13px). Add btn-sm so they match.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Chrome no longer shows an automatic install prompt, so catch
beforeinstallprompt and surface an "Install app" link in the footer
that replays the prompt on a user tap (hides once installed).
(nginx on the host now serves manifest.webmanifest as
application/manifest+json instead of octet-stream.)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Don't celebrate historical payments on import. receiveSeenIndex was
baselined to 0 before the scan ran, so when the scan advanced the
index every past payment looked new and flashed "payment received".
Leave it null until the post-scan ack logic baselines it.
- PWA: add a web manifest, ₿ icons (192/512 + maskable), theme/apple
meta tags, and a service worker that precaches the shell so the
installed app works offline. index.html stays self-contained; the
sidecar files just 404 harmlessly from file://. build.js emits them.
- Send page: drop btn-sm from the amount unit toggle so "sats" matches
the "Max" button size (both 16px).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
- Drop the @scure/@noble crypto paragraph and the passphrase paragraph.
- Link mempool.space in the no-backend paragraph; trim its trailing
"only your addresses leave the device" clause.
- "This page is one static HTML file" (was "The whole thing").
- Remove the cold-storage sentence from the offline paragraph.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- Rewrite the How it works page in a plainer, less generated voice;
drop the intro lead, the receive/send paragraph, and trim the rest.
- Fix passphrase wording: "13th word" (12-word phrase), not 25th.
- genIntro: "It's" instead of "It is".
- Unlock screen: label the field "Passphrase" (was "Optional
passphrase") and drop the "leave blank if unused" placeholder.
- Remove now-unused hiwLead / optionalPassphrase / leaveBlank keys
and the .lead style.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
UI / copy:
- Footer: "made by Adam Soltys" (drop the heart) + a "How it works"
link on its own line, matching the footer's size.
- New "How it works" page: plain-language basics plus an expandable
"for the technically curious" section. Routed via ui.screen, with
Back/logo returning to wherever you opened it from.
- Scrub jargon from the main UI for newcomers: drop BIP39, soften
"raw hex" -> "code", "txid" -> "transaction ID", "air-gapped" ->
"offline"; replace em dashes in the intro/keys notes. The technical
terms now live only in the How it works technical section.
Import / scan performance (fewer requests -> fewer 429s, faster):
- Skip the /utxo fetch for fully-settled, zero-balance addresses
(they can't hold UTXOs) -- the bulk of a history-heavy wallet.
- Show balance as soon as the address scan finishes, then load UTXOs
and history in the background.
- Probe the receive and change chains concurrently so their per-hit
delays overlap.
- History fills in progressively as each address resolves, with a
loading indicator on the History tab.
i18n.js is now tracked; new English keys fall back to English for the
other languages until translated.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
Fix the key/value rows wrapping awkwardly on narrow screens (right-align +
wrap-safe values, fixed-width key). Drop the redundant "When: Unconfirmed…"
line; pending now shows a single "Status: Pending (in mempool)". Confirmed txs
show Status / Block / Date.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
Clicking a history item now opens an in-wallet details view (status, block,
time, amount, fee, full copyable txid) instead of jumping straight to
mempool.space — works offline from cached data, with mempool as an optional
outbound link. Back returns to the list; switching tabs/logout resets it.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
WS-detected deposits now get added to history (previously the ingest advanced
past the address so the tx never entered history — balance showed but History
was empty). Pending txs render with a "⏳ pending" badge, an orange-tinted row,
and "awaiting confirmation" so they're clearly distinct from confirmed ones.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
| |
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]>
|
| |
|
|
|
|
|
| |
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 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]>
|