Auto-heal wallets stuck on a CORS-blocked mint

- useCashu now probes existing mints on every init (not just when empty); if none are browser-reachable it adds a reachable candidate mint. This fixes existing users whose only saved mint is the legacy CORS-blocked mint.minibits.cash default.
- CashuWallet active-mint selection now prefers the first browser-reachable mint instead of blindly mints[0], so users aren't stuck with a mint that fails every invoice.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-24 17:11:31 +07:00
parent a0c59375ed
commit b830bf965d
2 changed files with 53 additions and 13 deletions

View file

@ -98,11 +98,31 @@ export function CashuWallet() {
mints,
});
// Set first mint as active if none selected
// Pick an active mint when none is selected — prefer a browser-REACHABLE mint
// so a user whose first saved mint is CORS-blocked (e.g. legacy minibits)
// doesn't get stuck with a mint that fails every invoice.
useEffect(() => {
if (mints.length > 0 && !activeMintUrl) {
setActiveMintUrl(mints[0].url);
}
if (mints.length === 0 || activeMintUrl) return;
let cancelled = false;
(async () => {
// Probe mints in order; select the first reachable one.
for (const m of mints) {
const { probeMintReachability } = await import('@/lib/mintReachability');
const probe = await probeMintReachability(m.url);
if (cancelled) return;
if (probe.reachability === 'ok') {
setActiveMintUrl(m.url);
return;
}
}
// Fallback: nothing reachable — select the first so the UI still renders.
if (!cancelled) setActiveMintUrl(mints[0].url);
})();
return () => {
cancelled = true;
};
}, [mints, activeMintUrl, setActiveMintUrl]);
// Show wallet loading state

View file

@ -167,35 +167,55 @@ export function useCashu(): UseCashuReturn {
console.warn('[Cashu] Failed to enable some coco services:', err);
}
// Add a default mint if the user has none yet.
// Ensure the user has at least one BROWSER-REACHABLE mint.
//
// A mint MUST send permissive CORS headers or the browser blocks every
// request with a NetworkError ("Failed to fetch mint"). We can't know from
// build time which mint is reachable in THIS browser, so we probe an
// ordered list of web-friendly candidates and add the first that works.
// (mint.minibits.cash is intentionally excluded — it's mobile-app only.)
// build time which mint is reachable in THIS browser, so we probe.
//
// This runs on every init so it also AUTO-HEALS an existing user whose
// only saved mint is CORS-blocked (e.g. legacy mint.minibits.cash default):
// if none of their mints are reachable, we add a working one and — since
// the UI picks the first mint — keep the broken one but ensure a good one
// exists. (mint.minibits.cash is excluded from candidates — mobile only.)
try {
const existingMints = await coco.mint.getAllTrustedMints();
if (existingMints.length === 0) {
// Does the user already have at least one reachable mint?
let hasReachable = false;
for (const m of existingMints) {
const probe = await probeMintReachability(m.mintUrl);
if (probe.reachability === 'ok') {
hasReachable = true;
break;
}
console.warn('[Cashu] Existing mint not usable in browser:', m.mintUrl, probe.reachability);
}
if (!hasReachable) {
const CANDIDATE_MINTS = [
'https://mint.coinos.io',
'https://21mint.me',
'https://stablenut.cashu.network',
'https://mint.lnvoltz.com',
];
const alreadyHave = new Set(
existingMints.map((m: Mint) => m.mintUrl.toLowerCase()),
);
for (const url of CANDIDATE_MINTS) {
if (alreadyHave.has(url.toLowerCase())) continue;
const probe = await probeMintReachability(url);
if (probe.reachability === 'ok') {
console.log('[Cashu] Adding default mint:', url);
console.log('[Cashu] Adding reachable mint:', url);
await coco.mint.addMint(url, { trusted: true });
console.log('[Cashu] Default mint added successfully');
console.log('[Cashu] Reachable mint added successfully');
break;
}
console.warn('[Cashu] Skipping unreachable default candidate:', url, probe.reachability);
console.warn('[Cashu] Skipping unreachable candidate:', url, probe.reachability);
}
}
} catch (err) {
console.warn('[Cashu] Failed to add default mint:', err);
console.warn('[Cashu] Failed to ensure a reachable mint:', err);
}
setIsInitialized(true);