aboutsummaryrefslogtreecommitdiffstats
path: root/src/i18n.js
Commit message (Collapse)AuthorAgeFilesLines
* Send progressive disclosure, swap context in tx detail, LN amount placeholderAdam Soltys2026-06-291-0/+1
| | | | | | | | | | | | | | | | | | - Send: show only the destination input ('Address or Lightning invoice') until a valid on-chain/SP address is entered, then reveal amount + fee rate + coin selection + Review. A pasted bolt11 still auto-advances to its own confirmation, so those controls never appear for it. New wallet.isOnchainAddress() validator + a destReady() gate; recipient onInput/paste re-render only when validity flips. - History: stop listing swaps at the top. Instead, a send/receive that was part of a swap shows its Lightning context (invoice, swap fee, status, refund) on the tx detail screen via swaps.findByTxid(). Removed the dead swapListView. - Lightning receive: drop the '50000' amount placeholder — a bare number looked like a value was already entered; the min/max hint below covers it. Verified in-browser: controls hidden until a valid address, then revealed; LN receive placeholder empty; no errors. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Copy: 'The swap provider pays the invoice once the on-chain lockup confirms'Adam Soltys2026-06-291-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Receive: drop the dropdown label, rename 'Address' → 'Bitcoin address'Adam Soltys2026-06-291-2/+1
| | | | | | | | The 'Receive over' label was redundant above a self-explanatory dropdown; remove it (and the now-unused i18n key). Rename the first option to 'Bitcoin address' so it's clear alongside Lightning and Silent payment. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Swaps: say 'Swap fee' / provider-agnostic copy instead of naming BoltzAdam Soltys2026-06-291-2/+2
| | | | | | | | | The fee label and status copy shouldn't hardcode the provider, since the swap backend is configurable. 'Boltz fee' → 'Swap fee', and the pay/settle messages no longer name Boltz. (The settings card still says 'Boltz-compatible provider', which is an accurate description of the API standard.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Receive dropdown, drop the Swap tab, auto-detect pasted invoicesAdam Soltys2026-06-291-0/+1
| | | | | | | | | | | | | | | | | - Receive: replace the 3-button segmented control (too wide on mobile) with a compact 'Receive over' dropdown that scales to more address types. - Remove the standalone Swap tab. Paying an invoice now lives in Send; the in-flight/recent swap list folds into the top of History. Deleted the dead swapTab + doSubmarine. - Send: as soon as a valid bolt11 is pasted/typed/scanned into the recipient field, jump straight to the Lightning confirmation (submarine-swap quote with itemized cost) — the invoice's baked-in amount is used, so the amount field is skipped entirely. Wired into the recipient onInput, the paste button, and QR scan. Verified in-browser: Swap tab gone, dropdown renders, and a pasted 60k invoice auto-progresses to 'Pay over Lightning' with the Boltz fee shown, no Review tap. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Send: pay a pasted Lightning invoice via an auto submarine swap (itemized cost)Adam Soltys2026-06-291-0/+9
| | | | | | | | | | | | | | | | | Paste a bolt11 into the normal Send recipient field and Review now detects it and routes through a Boltz submarine swap — no 'swap' concept surfaced. Split startSubmarine into quoteSubmarine (POST + build-but-don't-broadcast the funding tx) and fundQuotedSubmarine, so the review shows an itemized cost before committing: Amount + Boltz fee + on-chain network fee = Total. Pay funds the lockup; Boltz pays the invoice once it confirms. Verified on regtest: a 60,000-sat invoice quotes to Amount 60,000 / Boltz 362 / network 442 / Total 60,804, with a Pay button. Lightning-address (LNURL) input + removing the standalone Swap tab are the remaining pieces. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Receive: add a Lightning option (reverse swap) next to Address + Silent paymentAdam Soltys2026-06-281-0/+8
| | | | | | | | | | | | Receive now has a segmented [Address | Lightning | Silent payment] toggle. 'Lightning' takes an amount and creates a Boltz reverse-swap bolt11 to share; when it's paid, Boltz locks up on-chain and the SwapManager claims it into this wallet automatically (shown live, then 'Received!'). Gated to non-watch-only wallets (a reverse swap needs our keys). Removed the duplicate 'Receive over Lightning' card from the Swap tab. Verified in-browser on regtest: real limits (Min 50,000) load via the proxy and an invoice is created. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gifts: let recipients claim into an existing wallet, not just a new oneAdam Soltys2026-06-281-0/+4
| | | | | | | | | | | | | | | Opening a gift link when you already have wallet(s) now shows a 'Choose where to receive it' screen — each known wallet (full accounts this session plus the durable directory, deduped by xpub) plus 'Receive into a new wallet'. A first-timer with no wallets goes straight to a fresh wallet as before. Claiming into an existing wallet activates it non-provisionally and sweeps the gift to its receive address (watch-only is fine — re-enter the seed to spend), skipping the new-seed backup step. Both bearer and locked (NIP-17) gifts route through the same claimGift() entry. Verified in-browser: selection lists the wallet + new option + amount; claim-into-address mechanic proven on regtest. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gifts: re-view link/QR for unclaimed gifts + mark claimed ones in historyAdam Soltys2026-06-281-0/+3
| | | | | | | | | | | | | | | Persist a per-account gift record (shareable link/code + amount, plus a locked gift's claim code) when a gift is created, so: - Unclaimed gifts in History get a 'Gift link' button that re-opens the QR + link (and the claim code for locked gifts), to re-share without recreating the gift. - Once a gift's coin is spent and we didn't revoke it, it shows in History as 'claimed' instead of vanishing. Revoking marks the record so a reclaim isn't mislabelled as claimed. Verified on regtest: create → outstanding=1, link stored; after the recipient claims → outstanding=0, claimed=1 with the right amount. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Consolidate coins: a Settings action + a gift-time recommendationAdam Soltys2026-06-281-0/+8
| | | | | | | | | | | | | | | | | - Settings gains a 'Consolidate coins' card (shown when you hold >1 spendable coin): wallet.consolidate() does a max self-send merging them into one, so future sends/gifts use a single input — cheaper and a smaller gift QR. - At gift creation, detect when the gift would bundle several coins (giftCoinSummary returns the input count) and trigger the split/consolidate offer when count ≥ 3 (not just when it frees change). The offer now explains it plainly ('would bundle N coins, costly to claim') and recommends splitting into one coin first, with 'Use N coins anyway' as the explicit opt-out. Verified on regtest: 6 small coins → a 1000-sat gift needs 3 inputs and triggers the offer; consolidate merges them to 1 coin, after which the same gift is a clean single input. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fix gift creation silently failing on large (many-input) gifts; one Back buttonAdam Soltys2026-06-281-0/+1
| | | | | | | | | | | - A many-input gift produces a URL too long for a QR, and qrSvg threw — uncaught, inside render() — so the click did nothing and showed no error. Wrap the gift QR in try/catch: render the QR when it fits, otherwise fall back to a 'too large for a QR — use the link' note. The link itself still works. - The split-offer card has its own Back; hide the gift sub-view's outer Back while the split offer is showing, so there aren't two stacked Back buttons. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Locked gifts: let NIP-07 extension users decrypt in-place, skip the codeAdam Soltys2026-06-281-0/+5
| | | | | | | | | | | | | | The gift payload is now encrypted two ways in the link: under the one-time code (the DM / manual path) AND to the recipient's nostr pubkey via an ephemeral key. When a NIP-07 browser extension (window.nostr) is present, the claim view offers 'Claim with nostr extension' — it checks the extension's pubkey matches the gift, decrypts ctKey via window.nostr.nip44.decrypt, and claims into a fresh wallet with no code. The pasted-code path remains as the fallback for everyone else. Verified: both decrypt paths recover the payload from the blob; a different extension account is rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Lock a gift to a nostr key: public link, claim code DM'd to the recipientAdam Soltys2026-06-281-0/+18
| | | | | | | | | | | | | | | | | | | | | | | | A gift can now be locked to a recipient's nostr npub. The shareable link (/lg/<blob>) carries the amount + recipient in the clear (anyone can see what it is and who it's for), but the claim payload is encrypted under a one-time code. That code is delivered as an encrypted nostr DM (NIP-04) to the recipient — so only they can read it. They paste the code to decrypt, then claim into a fresh wallet exactly like a normal bearer gift. - nostr.js: parseNostrPubkey/npubOf, encryptWithCode/decryptWithCode (random 32-byte code), fetchNostrProfile, NostrSync.sendDM (broad relays). - wallet.js: lockGift → { blob, claimCode }, previewLockedGift, nostr identity + sendNostrDM. - app.js: gift form gains an optional 'Lock to npub' field that shows the recipient's avatar + name (nostr kind-0 profile); create encrypts + DMs the code (with sent/failed status + a copyable fallback); /lg/ links open a claim view showing the gift + recipient where you paste the code; account settings shows your own npub to share. Verified: code lock/unlock round-trip, a real nostr DM published and read back from relay.damus.io, and the UI loads + renders with no errors. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Unify data sources into one list; coinos → electrum.coinos.ioAdam Soltys2026-06-281-0/+4
| | | | | | | | | | | | | | | | | | | - 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]>
* Make silent-payment receives first-class in the UIAdam Soltys2026-06-271-1/+3
| | | | | | | | | | | | | | | - One Receive card with an Address ⇄ Silent payment toggle (no separate card, no BIP-352 badge, no scan-status line or Rescan button — discovery is automatic). - SP receipts appear in History like normal payments: wallet.history merges BIP84 txs with per-tx SP receive rows (spTxRows), newest first; the detail view and pagination use it. SP outputs carry a firstSeen timestamp. - Receive description simplified to 'A reusable address that can't be tracked.' - Removed the '· Silent payment' tag from the Send recipient line. Verified on regtest: a received SP shows in history and balance with no manual scan. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Auto-scan for silent payments + fold found value into balanceAdam Soltys2026-06-271-0/+2
| | | | | | | | | | | 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]>
* Wire silent-payment receiving into the wallet (scan + balance + UI)Adam Soltys2026-06-271-0/+9
| | | | | | | | | | | | | | | | | | | 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]>
* 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-271-1/+0
| | | | | | | 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]>
* Make the wallet-settings link a card with a clear button, not a bare headingAdam Soltys2026-06-271-0/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* De-dup wallet settings; reorder per-wallet page; lowercase 'this'Adam Soltys2026-06-271-1/+1
| | | | | | | | | | 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-271-0/+2
| | | | | | | | | | 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]>
* 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]>
* Fix broadcast hanging forever on a stalled responseAdam Soltys2026-06-261-0/+1
| | | | | | | | | | | | 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]>
* Confirm before removing a single wallet (like Clear all)Adam Soltys2026-06-261-0/+2
| | | | | | | | 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-261-0/+1
| | | | | | | | | | | - 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-261-0/+4
| | | | | | | | | | | 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-261-1/+1
| | | | | | | | | | | | | | | | | | 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-261-1/+1
| | | | | | | | 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]>
* Add auto log-out (background timer) + make vault passwords optionalAdam Soltys2026-06-261-2/+10
| | | | | | | | | | | | | | | | 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-261-0/+13
| | | | | | | | | | | | | | | | | | | | | | | 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]>
* Fold the coinos opt-out into a single Data source pickerAdam Soltys2026-06-261-5/+7
| | | | | | | | | | | | | | | | | | | 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 Electrum-over-WebSocket as an alternative data backendAdam Soltys2026-06-261-0/+6
| | | | | | | | | | | | | | | | | | | | | | | | | | 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-251-0/+1
| | | | | | | | | | | | | | | | | 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-251-0/+2
| | | | | | | | | | | 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]>
* Already-claimed gift: offer 'Create a wallet anyway' or go homeAdam Soltys2026-06-251-0/+4
| | | | | | | | The claim flow already generated a fresh wallet, so the dead-end screen now lets the opener keep it (→ back up the recovery phrase → into the wallet) or head to the home screen. The backup step's copy adapts (Claimed! vs Your new wallet). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Trim gift minimum note to just 'Minimum {n}.'Adam Soltys2026-06-251-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Trim the tribute line to just 'a tribute to Hal Finney (1956–2014)'Adam Soltys2026-06-251-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gift claim: show a loading screen while checking, not the claimable amountAdam Soltys2026-06-251-0/+1
| | | | | | | | Avoids flashing 'You've been sent Bitcoin' before flipping to 'Already claimed' for a spent gift. Offline fallback clears the checking flag so the loader can't hang. 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/+2
| | | | | | | | | | | | 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]>
* Rename the app title from 'Hal Wallet' to just 'Hal'Adam Soltys2026-06-251-1/+1
| | | | | | | Header, browser tab title, PWA name, and apple web-app title. Tribute prose (explaining the name honors Hal Finney) left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Remove the Live/Polling badge; shorten explorer link to 'Block explorer'Adam Soltys2026-06-251-3/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Detect stuck low-fee txs and stop actively monitoring themAdam Soltys2026-06-241-0/+4
| | | | | | | | | | | | | | | A pending tx is judged 'stuck' once it has waited ~10 blocks (≈100 min) and its fee rate (fee/vsize) is below the mempool's current floor (minimumFee) — i.e. it's been purged and won't confirm without a bump. _txSummary now records vsize and a firstSeen timestamp (persisted in cache) to support this. Stuck txs are dropped from active monitoring: excluded from the watched-address set and from the fast-poll trigger, so they don't hold a socket slot or keep us in the 6s lane. The slow ~25s reconcile still catches one if the floor later drops and it confirms. History flags them (⚠ stuck) and the detail view explains it — pointing outgoing ones at the existing fee bump. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Drop background/full rescans for on-demand per-address rescanAdam Soltys2026-06-241-1/+7
| | | | | | | | | | | | | | | Remove the periodic full re-scan entirely — the WS and the frontier poll keep the balance current, and refreshLive's _reconcileHeld catches spends of held coins. The only gap (a deposit to a reused old address) is now handled explicitly: Settings → Rescan opens a paginated list of every known address, each with its own Rescan button (wallet.rescanAddress re-queries just that one). Replaces the full 'Rescan wallet' button. This sharply cuts recurring REST volume (the main rate-limit driver). Trade-off: when the socket is down, a pending tx's confirmed badge in History may lag until the WS reconnects or you rescan — the balance itself stays correct via reconcile. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Make pending receives fast: cut REST load, surface live/polling stateAdam Soltys2026-06-241-0/+2
| | | | | | | | | | | | | | | | | | | Incoming deposits show instantly when the mempool.space WebSocket is healthy (_ingestWs updates immediately). The lag came from the socket being starved: a 20s frontier poll plus a full wallet re-scan every 120s generated enough REST volume to trip mempool's per-IP rate limit (the 429/CORS errors), which in turn degraded the socket — a feedback loop. - While the socket is live, skip the periodic full scan entirely (the WS pushes confirmations and block events) and poll the frontier only every ~25s as a half-open-socket safety net — near-zero recurring REST. - While the socket is down, poll the frontier every ~6s (was 20s) so receives still show quickly, and keep the 120s reconcile scan. - Refresh the frontier on WS (re)connect to catch deposits that landed while disconnected. - Surface a Live/Polling badge on the balance card so a stuck socket is visible. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Allow gifting the whole balance (no-change sweep) via MaxAdam Soltys2026-06-241-0/+3
| | | | | | | | | | | | A gift PSBT commits a sender change output (>= dust), so the entered amount always needed gift + 294 — making it impossible to gift the entire balance. Add a Max toggle on the gift form that builds a no-change sweep: all inputs SIGHASH_NONE, no change output, so the recipient gets the whole balance minus their claim fee. previewGift/buildClaimTx now treat a zero-output PSBT as zero change. A specific-amount gift that can't keep a dust change now points the user to Max instead of failing with a confusing 'not enough funds'. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Paginate History: cap inline gifts at 3, transactions at 10Adam Soltys2026-06-241-0/+4
| | | | | | | | | Outstanding gifts no longer dominate the History page — show at most 3 inline with a 'View all gifts (N)' button that opens a paginated gift-only list. Transactions paginate 10 per page with prev/next controls. Both reuse a shared pager helper; the all-gifts sub-view resets when leaving the History tab. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Show outstanding sent gifts in History, cancellable thereAdam Soltys2026-06-241-0/+3
| | | | | | | | | Unclaimed gift links now appear at the top of the History list (they aren't on-chain transactions until claimed or revoked). Each is tappable to cancel via the same reclaim/revoke flow as the Send → gift card, reusing ui.revokeId and the doReclaim/doRevoke handlers. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>