Skip to content

fix: don't retry melt quote creation on MintOperationError#1090

Open
gudnuf wants to merge 1 commit into
masterfrom
claude/issue-1089-20260519-1932
Open

fix: don't retry melt quote creation on MintOperationError#1090
gudnuf wants to merge 1 commit into
masterfrom
claude/issue-1089-20260519-1932

Conversation

@gudnuf

@gudnuf gudnuf commented May 19, 2026

Copy link
Copy Markdown
Contributor

Closes #1089

Mint operation errors like 20739 (returned when paying a non-allowed invoice with a closed-loop gift card mint) are definitive mint responses and retrying won't help.

Updated useCreateCashuLightningSendQuote (app/features/send/cashu-send-quote-hooks.ts) and useCreateCrossAccountReceiveQuotes (app/features/receive/receive-cashu-token-hooks.ts) — the two mutations that call createMeltQuoteBolt11 — to treat MintOperationError as non-retryable, matching the existing pattern in useInitiateCashuSendQuote and useInitiateMelt.

@gudnuf gudnuf requested a review from jbojcic1 May 19, 2026 21:03
@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agicash Ready Ready Preview, Comment Jun 5, 2026 10:19pm

Request Review

@supabase

supabase Bot commented May 19, 2026

Copy link
Copy Markdown

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 ↗︎.

@gudnuf gudnuf self-assigned this May 19, 2026
},
retry: (failureCount, error) => {
if (error instanceof DomainError) {
if (error instanceof DomainError || error instanceof MintOperationError) {

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 seems wrong since who knows what else could be the reason to get MintOperationError and maybe for some of those it might make sense to retry. shouldn't we map that exact error case to domain error instead?

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 I was wondering that too. We could, but this is only for creating a quote and I couldn't come up with why that should be retryable.

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 guess we can say that but I'd still map limit exceeded to DomainError since it is exactly what we built DomainError for

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.

@claude lets make the services throw DomainError

@claude claude Bot May 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude finished @gudnuf's task in 4m 18s —— View job


Mapped MintOperationError → DomainError in services

  • Read current implementation in send/receive hooks
  • Identify services that call createMeltQuoteBolt11
  • Map MintOperationError to DomainError in the services
  • Revert the non-retryable checks in the hooks
  • Run bun run fix:all (typecheck passes, no new warnings)
  • Commit changes

What changed

app/features/send/cashu-send-quote-service.tsgetLightningQuote() now wraps the createMeltQuoteBolt11 call. If the mint throws a MintOperationError, it's re-thrown as a DomainError carrying the mint's detail message.

app/features/receive/receive-cashu-token-quote-service.ts — same wrapping around the createMeltQuoteBolt11 call inside createCrossAccountReceiveQuotes / the quote-finding loop.

app/features/send/cashu-send-quote-hooks.ts and app/features/receive/receive-cashu-token-hooks.ts — reverted the useCreateCashuLightningSendQuote and useCreateCrossAccountReceiveQuotes retry checks back to only error instanceof DomainError, and dropped the now-unused MintOperationError import from receive-cashu-token-hooks.ts (kept in send hooks because it's still used by useInitiateCashuSendQuote).

Rationale

Mint operation errors returned from createMeltQuoteBolt11 are definitive responses from the mint (limit exceeded, invoice not allowed, closed-loop 20739, etc.) — there's no scenario where retrying the same request helps. Wrapping them as DomainError at the service boundary is exactly what DomainError is for (user-facing, never retry) and keeps the cashu-ts error type from leaking into the hook retry logic. The mint's detail message is preserved as the DomainError message, so users still see the specific reason.

Commit: e78cd7c
Branch

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.

An alternative idea to this PR would be to change the scope and focus on addressing all possible mint operation erorrs across flows. For example, we infinitely retry swap that are created with invalid inputs but we should know that error is not retryable.

Not saying this is the exact thing we should do, but orveth proposed classifying the error codes. I can map that out in more detail

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.

hm not sure about this. are we sure some mint operation errors can't be recoved with retry?

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.

are we sure some mint operation errors can't be recovered with retry?

I'm sure for these two call sites which are only fetching the melt quote. The PostMeltQuoteBolt11Request looks like

{
  "request": <str>,
  "unit": <str_enum[UNIT]>,
  "options": { // Optional
    "amountless": {
      "amount_msat": <int>
    }
  }
}

and the only error codes that apply to this request are

  • 12001 invalid invoice
  • 11005 unit not support
  • 11006 amount outside of limit range (min/max melt amount)
  • 20006 invoice already paid
  • 20739 closed-loop invalid destination
  • 3000X auth required
  • 3100X rate limitting on minting BATs

So, I would say that specifically for quote creation its safe to not retry. I guess one risk is that some new error code gets added that is retryable, but I can't see what could change between retries (other than rate limit cooldown which isn't a MintOperationError)

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.

I can change to specifically check for these specific codes above, but it doesn't seem necessary for this case and I think there are very few cases outside of creating a melt quote that would be retryable. In those cases we can be more specific

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.

An alternative idea to this PR would be to change the scope and focus on addressing all possible mint operation erorrs across flows. For example, we infinitely retry swap that are created with invalid inputs but we should know that error is not retryable.

I think we should do this eventually so might as well do it now. I approved but not sure I would just map it like this and pass the message forward. It might make more sense to use different messages to abstract away some cashu details that don't mean a lot to average user.

Squashed onto post-monorepo master (paths relocated app/ -> apps/web-wallet/app/). Original commits:
 - fix: don't retry melt quote creation on MintOperationError
 - refactor: map MintOperationError from melt quote creation to DomainError
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.

closed loop error 20739 should not retry for melt quotes

3 participants