Aggressive cleanup of 359 exports across 153 files identified as
having zero importers outside their declaring module:
- 105 symbols deleted entirely (no internal uses either)
- 254 symbols un-exported (still referenced file-locally; dropped the
`export` keyword to shrink the public surface)
- ~70 cascade cleanups of locals that became dead once their sole
consumer was removed
Notable shrinkage:
- src/hooks/useShakespeare.ts: 626 \u2192 22 lines (unwired AI chat surface;
only the ChatMessage type is consumed)
- src/hooks/useTrending.ts: only useEventStats survives; trending feed
hooks were never wired up
- src/hooks/useTrustedCountryStats.ts: dead type re-exports removed
- src/lib/bitcoin.ts: PSBT helpers \u2014 unused wallet feature scaffolding
- src/lib/communityUtils.ts: unused NIP-72 moderation helpers
- src/lib/extraKinds.ts, src/lib/colorUtils.ts: unused helpers
- src/lib/logger.ts: bare debug/info/warn/error exports dropped;
consumers use the `logger` object
- src/lib/aiChatSystemPrompt.ts: trimmed to the
DEFAULT_SYSTEM_PROMPT_TEMPLATE constant
- src/components/music/MusicTrackRow.tsx: dead row component removed;
only the skeleton is consumed
src/hooks/useNostr.ts (intentional decoy) and src/i18n.ts
(side-effect import) were preserved per their respective contracts.
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.