Skip to content

Transfer/Add flow swallows mint quota-exceeded detail (33001), showing generic toast instead of mint's friendly message #1113

Description

@orveth

Background

In the agicash-team #feedback-team "auth limits testing" thread (1505825679269298267), bobthree reported 2026-05-18 that topping up >1k sats from a Spark wallet to an auth-limited gift-card mint (1k sats / 1m window) showed a generic toast instead of the mint's quota-exceeded detail. jbojcic confirmed the network tab returned a proper 33001 Mint quota exceeded payload from the mint, with structured data: {limit, used, unit, window_secs, retry_after} — the UI was masking it.

CDK PR #31 (merged) rephrased the mint-side 33001 detail to "Amount exceeds remaining daily limit of ₿X." — so on master today, the mint returns an already-friendly error.message. The wallet just isn't surfacing it.

What's wrong

33001 is not in app/lib/cashu/error-codes.ts (the CashuErrorCodes enum stops at 31004 on master), and the mint-quote call sites have no error handling:

  • app/features/receive/cashu-receive-quote-core.ts:268await wallet.createLockedMintQuote(...) (the receive side of /Add / gift-card top-up)
  • app/features/send/cashu-send-quote-service.ts:150await wallet.createMeltQuoteBolt11(...) (the send side)

A MintOperationError from cashu-ts propagates raw. It is not a DomainError, so at app/features/transfer/transfer-input.tsx:83-93 the instanceof DomainError check fails and the user gets the destructive fallback toast "Failed to create transfer. Please try again." The mint's friendly error.message goes to console.error only.

Proposed fix

Map MintOperationErrorDomainError at the cashu-quote boundary, so the mint-supplied message reaches the existing toast convention. Starting with 33001:

  1. Add MINT_QUOTA_EXCEEDED = 33001 to app/lib/cashu/error-codes.ts.
  2. At the quote-creation call sites (cashu-receive-quote-core.ts:268, cashu-send-quote-service.ts:150), catch MintOperationError, switch on error.code, and throw new DomainError(error.message) for recognized codes. Unknown codes can either pass through as DomainError(error.message) (mint message is usually decent) or fall to generic — design choice for the PR.

Nuance: the pinned cashu-ts@3.6.1 strips the structured data: {limit, used, unit, window_secs, retry_after} field before throwing — only error.message is reachable today. The cashu-ts v4 upgrade in PR #1108 restores data, after which a richer formatter (e.g. retry-after countdown) becomes possible. Not blocking — surfacing error.message is the immediate win.

Already drafted

.claude/worktrees/pr1-quota-error/ has a draft pass of this change (adds MINT_QUOTA_EXCEEDED, a quote-errors.ts formatter, wiring at both quote services). May be the starting point for the PR.

Minor follow-up (out of scope)

canSendToLightning at app/features/accounts/account.ts:102-110 returns a bare boolean for four distinct reject reasons (!isTransactional, isTestMint, !isOnline, NUT-05 disabled). The throw site at transfer-service.ts:88-92 formats them all as " cannot send Lightning payments". For gift-card / offer-purpose mints (the path now assumed for all auth-limited mints), the reject is correct but the copy reads as the named mint being broken. Reason-returning predicate + per-reason copy is a small cleanup; not blocking, not in this issue's scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions