move the wallet's chain source out of env and into the ui
WALLET_ESPLORA_URL was the one wallet setting an owner actually has to change — off a public explorer that rate-limits and sees every address, onto their own indexer — and it was the one they could only change with a shell and a restart. It now lives in service_connections under 'esplora' and is edited at Wallet -> Settings -> Chain source, probed against /blocks/tip/height before it is stored. The URL joins the backend fingerprint, so re-pointing rebuilds every on-chain backend and drops the gap-limit scan taken through the old endpoint. /_health probes what the wallets actually use rather than the built-in default, and /_officer/config no longer reports a URL it cannot know. Also two receive-screen defects the Blockstream 429 exposed: a query error rendered as "No address available", and the "new address" button called refetch() on the ?peek=true query, so it re-fetched the same address instead of advancing the index. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ import { LndHubBackend } from './backends/lndhub';
|
||||
import { NwcBackend } from './backends/nwc';
|
||||
import { OnchainBackend } from './backends/onchain';
|
||||
import { sessionFor } from './keys';
|
||||
import { getConfig } from './upstream';
|
||||
import { getChainSource } from './upstream';
|
||||
import { BackendError, BIP_ADDRESS_TYPE, type AddressType, type BitcoinNetwork, type WalletBackend } from './types';
|
||||
|
||||
// Turns a stored wallet row into a live backend instance. This is the one place that knows the mapping
|
||||
@@ -38,8 +38,8 @@ export function invalidateAll(): void {
|
||||
}
|
||||
|
||||
/** A stable fingerprint of the inputs a backend was built from, so a stale instance is detected. */
|
||||
function versionOf(wallet: WalletSummary, config: Record<string, unknown> | null): string {
|
||||
return JSON.stringify([wallet.kind, wallet.network, wallet.defaultBip, wallet.xpubs, config]);
|
||||
function versionOf(wallet: WalletSummary, config: Record<string, unknown> | null, esploraUrl: string): string {
|
||||
return JSON.stringify([wallet.kind, wallet.network, wallet.defaultBip, wallet.xpubs, config, esploraUrl]);
|
||||
}
|
||||
|
||||
export type Resolved = { wallet: WalletSummary; backend: WalletBackend };
|
||||
@@ -50,12 +50,15 @@ export async function resolveBackend(userId: number, walletId: number): Promise<
|
||||
|
||||
const secrets = await getWalletSecrets(userId, walletId);
|
||||
const config = secrets?.config ?? null;
|
||||
const version = versionOf(wallet, config);
|
||||
// Read per resolve rather than per build: the URL is in the fingerprint, so pointing the owner at a
|
||||
// different indexer rebuilds every backend and drops the scan taken through the old one.
|
||||
const { baseUrl: esploraUrl } = await getChainSource(userId);
|
||||
const version = versionOf(wallet, config, esploraUrl);
|
||||
|
||||
const hit = cache.get(walletId);
|
||||
if (hit && hit.configVersion === version) return { wallet, backend: hit.backend };
|
||||
|
||||
const backend = build(wallet, config);
|
||||
const backend = build(wallet, config, esploraUrl);
|
||||
cache.set(walletId, { backend, configVersion: version });
|
||||
return { wallet, backend };
|
||||
}
|
||||
@@ -81,7 +84,7 @@ function required(config: Record<string, unknown> | null, key: string, kind: str
|
||||
return v;
|
||||
}
|
||||
|
||||
function build(wallet: WalletSummary, config: Record<string, unknown> | null): WalletBackend {
|
||||
function build(wallet: WalletSummary, config: Record<string, unknown> | null, esploraUrl: string): WalletBackend {
|
||||
const network = wallet.network as BitcoinNetwork;
|
||||
|
||||
switch (wallet.kind) {
|
||||
@@ -123,7 +126,6 @@ function build(wallet: WalletSummary, config: Record<string, unknown> | null): W
|
||||
if (Object.keys(accountXpub).length === 0) {
|
||||
throw new BackendError('wallet has no account xpubs', 500, 'BAD_CONFIG');
|
||||
}
|
||||
const { esploraUrl } = getConfig();
|
||||
return new OnchainBackend({
|
||||
chain: new EsploraChain({ baseUrl: esploraUrl, network }),
|
||||
network,
|
||||
|
||||
Reference in New Issue
Block a user