Skip to content

fix(receive): probe mint reachability in route loader#1037

Open
gudnuf wants to merge 1 commit into
masterfrom
fix/offline-mint-loader-probe
Open

fix(receive): probe mint reachability in route loader#1037
gudnuf wants to merge 1 commit into
masterfrom
fix/offline-mint-loader-probe

Conversation

@gudnuf
Copy link
Copy Markdown
Contributor

@gudnuf gudnuf commented Apr 28, 2026

This is an alternative to #1029 which makes it so the receive cashu token routes don't even load the components that require the source account to be online

This mint is offline if you want to test:

https://agicash-git-fix-offline-mint-loader-probe-makeprisms.vercel.app/receive-cashu-token#cashuBo2Ftd2h0dHBzOi8vdGVzdDI4LmFnaS5jYXNoYXVjc2F0YXSBomFpSAG8Od03AFerYXCGo2FhGQIAYXN4QDNiNTg0M2QxMTdmYTBmZjYzMjc3Y2Q2YTg2MTQ0YmZmYTlkNTZlYmE2ZTA4ZGYwODAyYWZiN2Y1Y2M4NzQ0MmZhY1ghA6mbXJI10_30NBVXVBp71m1iOsvcEepOJes00gPDVuHvo2FhGQEAYXN4QGNiMmRkNTlmZWI1ODg5OWZmYmMyYmMxMDRkZmUxNGVjNTExMGIyYmE2NDUzYmM2NWVlYjZhYzhhMDEwY2EyZGVhY1ghA_Dwd-VFZHneQdAR2DgAAVnNC4eK-VPcl4vbto688NPko2FhGIBhc3hAZGE3NWYwM2EyYWZhMzhhNWYwOTZmNWQ2ZDQzNDIxN2QyODhkODE4YTEzODJmYTFlZWEwNjgxYmE0ZDA0MGE4ZWFjWCEDm9DuVgQuN5JIi0LLNbHSYQAjTViX0NE1sqnYxGhXxq6jYWEYQGFzeEBkNGFkZmNhMDMzNjJmNTUwOTE1OTdjNDk4MjJiOWExOWRiNmU3MDEzOWZjNzdjZWE5MGNhODM1ZTJlZDQ5OTAzYWNYIQNTC9ikVPmK8ii-sFdQkQp-RqVbkWni2iHGxttMe2jWHKNhYRggYXN4QGYyZThmYjdlN2JiYmU5OTE2Y2U5Y2U0YTExMTY2NTJhNjNhNTA2OTAxZmZmNDM5OWY1NDQyYTdhNDljMWZmZmFhY1ghA0Yvz51lWc5Phbkze53YLI8Z7oXfmlVMZbgNFh5fA06Do2FhCGFzeEA4ZTBiYjQ0MjEyMDg1OTlhZmRmN2M1NDhmOTI1YmNkNDliNTE0OWIxMTc4Y2E4NGZjOTNlMzQ3ZTA4MDdiOWY4YWNYIQJ_zYVpvTWM8gNNueTfh-crHSXvNKoS9J3i1h-GqcWYng

Summary

When a Cashu token's source mint is unreachable, detect it in the clientLoader before mounting the receive component, so the receive flow itself never has to know about offline mints.

The loader pre-fetches mintInfo, allMintKeysets, and mintKeys with a 10s timeout race. On NetworkError it returns a discriminated { kind: 'mint-offline', mintUrl } and the component renders an inline . On success the queryClient cache is warm, so getInitializedCashuWallet resolves from cache when the component mounts — no race window where the wallet ends up with unloaded mint info.

Same pattern in both the protected and public token-receive routes. TokenErrorDisplay is lifted into its own file so the routes can import it without pulling in receive-cashu-token internals.

Fixes Sentry: AGICASH-9S, 9Y, 93, 7G, 7F.

@gudnuf gudnuf requested a review from jbojcic1 April 28, 2026 18:13
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agicash Ready Ready Preview, Comment May 12, 2026 10:01pm

Request Review

@supabase
Copy link
Copy Markdown

supabase Bot commented Apr 28, 2026

This pull request has been ignored for the connected project hrebgkfhjpkbxpztqqke because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

Comment thread app/routes/_public.receive-cashu-token.tsx Outdated
}

// Keysets are warm in cache from the probe, so this resolves fast.
const token = await decodeCashuToken(hash);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async part of the decode process is only present for v2 keysets, right? if so, should we avoid these network requests when token is using v1?

also can you remind me, why do we need to decode the token here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea only v2, but most tokens are v2 so not sure its worth handling v1. Also cashu-ts v4 makes keysetIds required https://github.com/cashubtc/cashu-ts/blob/main/src/utils/core.ts#L332-L336

Comment thread app/routes/_protected.receive.cashu_.token.tsx
When a Cashu token's source mint is unreachable, detect it in the
clientLoader before mounting the receive component, so the receive
flow itself never has to know about offline mints.

The loader pre-fetches mintInfo, allMintKeysets, and mintKeys with
a 10s timeout race. On NetworkError it returns a discriminated
{ kind: 'mint-offline', mintUrl } and the component renders an
inline <TokenErrorDisplay>. On success the queryClient cache is
warm, so getInitializedCashuWallet resolves from cache when the
component mounts — no race window where the wallet ends up with
unloaded mint info.

Same pattern in both the protected and public token-receive routes.
TokenErrorDisplay is lifted into its own file so the routes can
import it without pulling in receive-cashu-token internals.

Fixes Sentry: AGICASH-9S, 9Y, 93, 7G, 7F.
@gudnuf gudnuf force-pushed the fix/offline-mint-loader-probe branch from 9140ee4 to 795af31 Compare May 12, 2026 23:13
});
queryClient.cancelQueries({ queryKey: mintKeysQueryKey(mintUrl) });
reject(new NetworkError('Mint request timed out'));
}, 10_000);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 seconds is too long. I would put it to 3 seconds or something like that.

* in-flight queries on timeout so they don't populate the cache after the fact.
* Throws `NetworkError` if the mint is unreachable or the timeout elapses.
*/
export async function fetchMintDataWithTimeout(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call this just fetchMintData

// Probe mint reachability before mounting the receive flow. Side effect: primes
// the query cache so the component's wallet init resolves without a round-trip.
try {
await fetchMintDataWithTimeout(extracted.metadata.mint, queryClient);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing I am not sure with this approach is that now the page will load slower. Could we make it so that we only check if the token is supported in the loader and then handle offline mint differently but without changing the ui too much so there is no weird change to different page?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal especially in the timeout scenario because the page will show loading state for a long time and look broken.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have to load keysets for decodiing v2 tokens. What if we just rely on the error that decodeCashuToken will throw. So if loading the keyset throws a network error then we can say the mint is not reachable

image

queryClient.fetchQuery(mintKeysQueryOptions(mintUrl)),
]),
new Promise<never>((_, reject) => {
setTimeout(() => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we store this timeout to variable and clear it when the the Promise.race resolves?

something like:

 let timer: ReturnType<typeof setTimeout>;
  try {
    return await Promise.race([
      Promise.all([...]),
      new Promise<never>((_, reject) => {
        timer = setTimeout(() => { /* cancel + reject */ }, 10_000);
      }),
    ]);
  } finally {
    timer && clearTimeout(timer);
  }

I don't think it matters a lot but no need to still have the timeout scheduled when the mint info wins the race

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants