Skip to content

refactor(core): centralize mint quote claimability - #403

Open
Egge21M wants to merge 6 commits into
masterfrom
feat/issue-387-claimability-reconciliation
Open

refactor(core): centralize mint quote claimability#403
Egge21M wants to merge 6 commits into
masterfrom
feat/issue-387-claimability-reconciliation

Conversation

@Egge21M

@Egge21M Egge21M commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • stack the common Mint Quote Claimability work on the pinned implementation from feat(core): add protocol and secure BOLT11 foundations #400
  • centralize atomic BOLT11 and balance-based BOLT12/on-chain claimability decisions
  • preserve NUT-20 locked BOLT11 creation, signing, and exact-output recovery
  • make quote observation and compatibility-state updates transactional and monotonic
  • coordinate explicit execution with background processing without duplicate issuance
  • add regressions for stale observations, trust changes, overpayment, restart recovery, and in-flight quote updates

Why

Runtime claimability had multiple competing decision paths across handlers, orchestration, watchers, processors, and repositories. Stacking #398 directly on #400 exposed additional races and ordering problems where raw or stale observations could override canonical accounting.

This replacement keeps one common claimability seam while retaining #400's protocol-specific security behavior.

Impact

Mint quote scheduling, execution, recovery, and pending selection now use the same canonical accounting policy. BOLT11 remains atomic, while BOLT12 and on-chain quotes retain reusable balance semantics. Compatibility state remains limited to import and projection behavior.

Validation

  • 1,214 core unit tests
  • 188 focused claimability and handler regression tests
  • SQLite3 adapter contracts: 59
  • sqlite-bun adapter contracts: 59
  • Expo SQLite adapter contracts: 61
  • IndexedDB Chromium adapter contracts: 53
  • SQL storage tests: 26
  • full workspace build and typecheck
  • formatting and React lint (zero errors; two pre-existing warnings)

Docker-backed live-mint integration could not run locally because Docker and MINT_URL are unavailable; those workflows remain for CI.

Stack

Closes #387.
Supports #365.

@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9dea02a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@cashu/coco-core Major
@cashu/coco-adapter-tests Major
@cashu/coco-sqlite Major
@cashu/coco-sqlite-bun Major
@cashu/coco-expo-sqlite Major
@cashu/coco-indexeddb Major
@cashu/coco-sql-storage Major
@cashu/coco-react Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.30556% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.37%. Comparing base (1601cf7) to head (9dea02a).

Files with missing lines Patch % Lines
...ages/core/infra/handlers/mint/MintBolt11Handler.ts 96.42% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #403      +/-   ##
==========================================
+ Coverage   92.63%   93.37%   +0.73%     
==========================================
  Files         111      112       +1     
  Lines       17633    17816     +183     
==========================================
+ Hits        16334    16635     +301     
+ Misses       1299     1181     -118     
Flag Coverage Δ
core-unit 93.81% <99.30%> (+0.74%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Egge21M
Egge21M marked this pull request as ready for review July 30, 2026 12:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9dea02a661

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

);
}
if (!quote.reusable) {
const siblings = await this.mintOperationRepository.getByQuoteId(mintUrl, method, quoteId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Normalize the quote URL before loading sibling operations

When a plugin calls claimMintQuote with an equivalent but non-canonical URL such as https://mint.test/, the quote lookup succeeds because mint-quote repositories normalize URLs, but every MintOperationRepository.getByQuoteId implementation compares the supplied URL exactly. The resulting empty siblings list omits finalized issuance and executing reservations, so reusable-quote claimability can be overstated and an additional issuance operation may be created. Normalize mintUrl at entry or query with the canonical quote.mintUrl.

AGENTS.md reference: AGENTS.md:L160-L165

Useful? React with 👍 / 👎.

);
return {
observedRemoteStateAt,
quoteSnapshot: quote,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate locked quote ownership before persisting the snapshot

For a locked BOLT11 operation, if polling returns a missing or different pubkey together with monotonic paid accounting, this snapshot is persisted and can classify the operation as ready; execution then signs against the quote using the operation's old key. BOLT12/on-chain pending checks and BOLT11 recovery already reject this ownership contradiction, so checkPending should perform the same comparison before returning quoteSnapshot.

AGENTS.md reference: AGENTS.md:L162-L163

Useful? React with 👍 / 👎.

Comment on lines +91 to +93
: amountPaid.greaterThan(amountIssued)
? 'PAID'
: 'ISSUED';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Project overpaid issued BOLT11 quotes as issued

When a fixed quote for 10 units reports amountPaid = 11 and amountIssued = 10, claimability correctly treats it as complete, but this comparison projects its compatibility state as PAID because paid remains greater than issued. Both getMintQuoteRemoteState and mintQuoteToMethodSnapshot expose that incorrect projection, so compatibility consumers can treat an already-issued atomic quote as redeemable; valid nonzero BOLT11 issuance should project as ISSUED even when the payment was excessive.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Centralize Mint Quote Claimability behind canonical accounting

2 participants