93c22dec2e816ef87e18ee258bea01c19128fee8
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
58fd4c41c2 |
Explain silent-payment-only balance in HD Send dialog
When the user's only spendable balance is in silent-payment outputs,
the Send button stays disabled with no feedback because:
- The dialog's `ownedUtxos` is sourced from `scan.utxos` (BIP-86
only, populated by Blockbook).
- SP UTXOs live in a separate persisted store and aren't included.
- The PSBT signer can't spend them anyway: SP outputs use a
BIP-352 tweaked private key that isn't derivable from the
BIP-86 (chain, index) pair `signHdPsbt` reconstructs, so even
plumbing them into `ownedUtxos` would just move the failure
from "button disabled" to "signing throws".
`src/lib/hdwallet/sp/crypto.ts` is explicit: the wallet
"scans-and-displays SP receives but cannot spend or send them".
Surface that explicitly: when `totalBalance === 0` (no BIP-86
funds) but `silentPaymentBalance > 0`, render a one-line alert
above the Send button telling the user spending SP outputs isn't
supported yet and they need to receive on-chain to spend. Doesn't
remove the disabled state — there's nothing to spend regardless —
but at least the user now knows why.
|
||
|
|
ea825505cc |
Fix four bugs in /hdwallet Send dialog
1. Double close buttons. shadcn's DialogContent always renders an
absolute-positioned X in the top-right corner; the dialog also drew
its own X in a header row. Hide the default with `[&>button]:hidden`,
matching SendBitcoinDialog.
2. Absurdly high fees / "can't send". The UI fee preview multiplied the
fee rate by `ownedUtxos.length`, but an HD wallet typically holds
many UTXOs across many addresses while a real send only consumes the
minimal set the coin selector picks. On an active wallet this
over-estimated by an order of magnitude, drove `totalSats` past
`totalBalance`, and disabled the Send button via `insufficient`.
Add `previewHdFee` in lib/hdwallet/transaction.ts that runs the same
`selectUtxos` logic as the real PSBT builder and returns the resulting
fee. Use it in both the live fee display and the auto-tune effect, so
the preview matches what the transaction will actually pay.
Also flag `selectionFailed` (positive amount but `previewHdFee`
returns 0) as `insufficient` so the UI doesn't claim a 0-sat fee is
spendable when coin selection failed.
3. Em dash on every fee tier. The popover trigger only had a value path
for `estimatedFeeSats > 0 && btcPrice`. With no amount entered yet,
or while fee rates are still loading, every tier displayed `—`. Fall
back to `<rate> sat/vB` when we have a rate but no amount-derived
USD fee.
4. Privacy checkbox unchecking on fee tier change. The reset effect
listed `currentFeeRate` in its deps, so picking a different fee tier
silently flipped `acknowledgedPublic` back to false. Split the resets:
`confirmArmed` still re-arms on amount/fee/price/recipient changes,
but `acknowledgedPublic` only resets when the recipient changes.
Regression-of:
|
||
|
|
f1000f1838 |
Rewrite /hdwallet on Trezor Blockbook xpub API
The HD wallet at /hdwallet now talks exclusively to a single Blockbook endpoint (default: https://btc1.trezor.io, configurable via AppConfig's new blockbookBaseUrl). One scan refresh is exactly two HTTP calls regardless of wallet size: - GET /api/v2/xpub/<tr(xpub)>?details=txs&tokens=used Returns account-level balance, the list of used derived addresses with their BIP32 paths, and the full tx history -- everything we need to populate the UI -- in one response. - GET /api/v2/utxo/<tr(xpub)> Returns the UTXO set with paths attached, so the coin selector and signer can recover (chain, index) without redoing the derivation walk. This replaces the previous Esplora architecture which made dozens of per-address requests per refresh and routinely tripped mempool.space's public rate limits. What changed: - New src/lib/hdwallet/blockbook.ts: HTTP client for Blockbook's xpub, utxo, estimatefee, and sendtx endpoints. No failover list, no fallback to other indexers; errors surface to the user. Per-request timeout still applies (20s) so a hung connection doesn't lock the UI. - src/lib/hdwallet/derivation.ts gains accountToBip86Descriptor() which wraps account.accountNode.publicExtendedKey as `tr(<xpub>)`. The bare xpub prefix would default Blockbook to BIP44; the `tr(...)` wrapper selects BIP86 Taproot. - src/lib/hdwallet/scan.ts is now a thin translator from the Blockbook response shape into the existing AccountScanResult shape consumed by the page and the send dialog. The previous gap-walk, snapshot derivation, cache hydration, and inter-batch pacing are all gone -- Blockbook indexes the xpub server-side. Every server-returned address is re-derived locally and discarded if it doesn't match, so a compromised backend can't redirect funds to its own addresses. - src/lib/hdwallet/snapshot.ts and cache.ts deleted (Esplora-era only). - useHdWallet drops esploraApis dependency, reads blockbookBaseUrl, bumps refresh to 60s (was 120s; with only 2 calls per refresh we can afford it). - HDSendBitcoinDialog reads fee rates via fetchFeeRates (Blockbook /estimatefee for blocks 1/3/6/144 in parallel) and broadcasts via broadcastBlockbookTx. UTXOs come from the shared scan result, no separate fetch. - AppConfig: new blockbookBaseUrl: string field, defaulted to https://btc1.trezor.io in App.tsx, TestApp, and the Zod schema. /wallet and the rest of the app continue to use Esplora for the single-address wallet, on-chain zaps, NIP-73 tx/address pages, and campaign donations. No shared backend abstraction yet; this is deliberate -- Blockbook's xpub endpoint is unique to HD wallets. Privacy note: the full account xpub now goes to the configured Blockbook server on every request. Users who don't want that exposure can self-host Blockbook and point blockbookBaseUrl at it. Default remains Trezor's public mirror. |
||
|
|
b0561a5503 |
Esplora REST failover with abort signals and timeouts
Replace the single `esploraBaseUrl: string` with `esploraApis: string[]` and route every Esplora REST call through a new `esploraFetch` helper that handles ordered failover across multiple API endpoints. The failover client: - Tries URLs in order with a per-attempt 15s timeout. mempool.space has a shadowban-style rate-limit behaviour where requests are silently absorbed and never reply; the timeout converts that hang into a regular failover signal so the next URL is tried. - On `429` / `5xx` / network error / timeout, parks the URL in a module-level cool-down with exponential backoff (30s, 60s, 120s, 240s, 300s cap) and advances to the next. - Resets a URL's failure count on the first 2xx response, so the primary comes back into rotation as soon as it recovers. - Treats configurable `skipStatuses` (e.g. `404` on `/v1/prices`) as endpoint-capability mismatches: skip without penalising the endpoint. This lets non-mempool backends like Blockstream coexist in the list even though they don't expose the price extension. - Composes a caller-supplied AbortSignal with the per-attempt timeout via AbortSignal.any. Caller aborts (e.g. TanStack Query queryFn unmounts) propagate immediately; timeouts mark the endpoint failed and try the next URL. - Falls back to cooled-down endpoints when *every* URL is in cool-down, rather than failing outright. Default list is mempool.space \u2192 mempool.emzy.de \u2192 blockstream.info. Every helper in `src/lib/bitcoin.ts`, `src/lib/hdwallet/scan.ts`, and `verifyOnchainZap` now takes `(input, esploraApis: string[], signal?: AbortSignal)`. Every TanStack Query caller threads its `queryFn` signal through. Mutations (broadcasts, send/donate/onchain-zap flows) still call without an explicit signal but get the 15s per-attempt timeout. |
||
|
|
522c265041 |
Add HD Bitcoin wallet at /hdwallet
A production-grade BIP86 Taproot HD wallet, separate from the single-address
wallet at /wallet. The seed is derived deterministically from the user's nsec
via HKDF-SHA-256 with an app-specific info string, so there is no new secret
for the user to back up \u2014 if they have their nsec they have the wallet.
Architecture:
- src/lib/hdwallet/derivation.ts \u2014 HKDF seed, BIP86 (m/86'/0'/0'),
receive (0) and change (1) chains, per-leaf P2TR addresses, TapTweaked
signing keys.
- src/lib/hdwallet/scan.ts \u2014 Standard gap-limit (20) scan across both
chains via Esplora. Aggregated UTXO set, balance, and tx history
(merged by txid so send-with-change shows as one row).
- src/lib/hdwallet/transaction.ts \u2014 Largest-first coin selection
(confirmed first), multi-input P2TR PSBT build with per-input
tapInternalKey from re-derived child keys, fresh change addresses on
the internal chain (no address reuse).
- useHdWalletAccess \u2014 Gates on login type === 'nsec'. Extension and
bunker logins keep the key isolated, so the page shows an explanatory
card with a link back to /wallet.
- useHdWallet \u2014 Scan + tx history queries (60 s refresh), persisted
receive-cursor in secure storage (Keychain on native, localStorage on
web), auto-advance when chain catches up so old addresses are never
re-shown.
- HDWalletPage \u2014 Mirrors /wallet's clean UX: big USD balance, send
button, QR + truncated address, 'New address' rotator, collapsible tx
history.
- HDSendBitcoinDialog \u2014 Mirrors SendBitcoinDialog (USD presets, fee
speed picker, two-tap arm for large amounts, raw-address privacy
disclaimer, success screen) but uses the HD UTXO set across many
addresses and signs with per-input HD-derived keys.
|