aboutsummaryrefslogtreecommitdiffstats
path: root/src/i18n.js
Commit message (Collapse)AuthorAgeFilesLines
* 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]>
* Instant gift-via-split and spend-unconfirmed supportAdam Soltys2026-06-241-1/+1
| | | | | | | | | | | | | | | | Gift split no longer makes the sender wait for confirmation: createGiftFromSplit broadcasts the carve-out, waits only until it's visible in the mempool, then builds the gift spending that still-unconfirmed coin. Funding + claim form a chain that confirms together; the recipient sees a pending balance until then. Polls for mempool visibility before reserving so a background refresh can't drop a not-yet-indexed coin and prune the reservation (double-spend risk). Regular sends can already draw on unconfirmed coins; surface that honestly: a 'pending' tag on unconfirmed coins in coin control, and a plain-language warning at review when a payment spends unconfirmed bitcoin (won't confirm until the ancestor does). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Minimize gift lock-up: best-fit coins + optional pre-splitAdam Soltys2026-06-241-0/+5
| | | | | | | | | | | | | | Gift coin selection now picks the smallest single coin that covers the gift (was largest-first), locking the least value until claim. When no near-exact coin exists, offer to pre-split: broadcast a self-send carving out a gift+dust coin so the gift later locks only ~the gift amount. The split is only offered when the liquidity it frees (lock - gift - dust) exceeds the split's own fee, so it never costs more than it regains. Decision is surfaced as an interstitial on Create, since a full re-render drops input focus and a live preview isn't viable. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Deduct gift-locked coins from the spendable balanceAdam Soltys2026-06-241-0/+1
| | | | | | | | | Coins reserved behind unclaimed gift links were already excluded from spending, but the headline balance still counted them. Show the spendable balance (total minus gift locks) as the headline and surface the locked amount as its own line, so a gift debits immediately rather than at claim. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Gift links: stop pre-reserving fee, show it at claim timeAdam Soltys2026-06-241-0/+1
| | | | | | | | | | | | | | Previously createGift reserved a claim-fee headroom on top of the entered amount based on the fee rate at creation. Because the fee was only actually charged at claim time, fee-rate drift between the two moments silently ate into (or padded) the recipient's amount. Now the entered amount is exactly the recipient's full received total (sum of inputs less our change); no fee is reserved. The claim screen shows that full total as the headline and surfaces the live network-fee estimate on the Claim button, where it's deducted from the recipient's balance at claim time. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Simplify import placeholder to 'Your seed words'Adam Soltys2026-06-241-1/+1
| | | | Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Add dark mode; gift amount unit toggle; multi-input giftsAdam Soltys2026-06-241-0/+2
| | | | | | | | | | | | | | - Dark mode: theme variables + a footer "Dark mode/Light mode" toggle, persisted and applied pre-paint via a small head script (defaults to the OS preference). Hardcoded surfaces moved to CSS variables. - Gift amount: the unit label is now the clickable sats/BTC toggle, and the amount is parsed in the selected unit. - Gifts can now span multiple coins: createGift selects several confirmed coins (first input SIGHASH_SINGLE to lock the change, the rest SIGHASH_NONE), so a gift larger than any single coin works. Claim/preview sum all inputs and size the fee by input count. Verified end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Relabel tx explorer link to "View on block explorer"Adam Soltys2026-06-241-1/+1
| | | | | | | The link already targets the selected explorer (explorerTx); only the label said mempool.space. Now generic, across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Rename "Lock all" to "Clear all" with confirmation; editable wallet namesAdam Soltys2026-06-241-1/+3
| | | | | | | | | | | | - The Accounts action is now "Clear all", which prompts a confirmation warning that any wallet whose recovery phrase/key isn't backed up will be lost. Confirming wipes everything from the device: session accounts, the encrypted vault, and saved watch-only accounts. - Each account has a rename (✎) control: edit the label inline and save. Persists to the session, the watch-only store, and the vault as needed. New strings translated across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Fold watch-only into Import; support xprv spending importAdam Soltys2026-06-241-0/+5
| | | | | | | | | | | | | | | | | | Home screen is back to two tabs (Create / Import). The Import box now takes a recovery phrase, an xpub/zpub, or an xprv/zprv: - mnemonic → full wallet (as before) - xpub/zpub → watch-only (view/receive, no Send) - xprv/zprv → full spending wallet with no mnemonic. parseExtendedKey classifies + normalizes version bytes; account() derives the BIP84 path from a master xprv (depth 0) or uses an account-level xprv as-is; signing works (only watch-only blocks it). Vault persists xprv accounts too; Settings shows an "Imported key" note instead of a recovery card and hides Nostr sync (no seed). Public-key export still works. Note: a seed phrase cannot be derived from an xprv (one-way KDF), so an xprv wallet has no recovery words by design. New strings in all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>