Skip to content

feat(core): adopt NUT-04 mint quote accounting#303

Draft
Egge21M wants to merge 13 commits into
masterfrom
codex/nut04-mint-quote-accounting
Draft

feat(core): adopt NUT-04 mint quote accounting#303
Egge21M wants to merge 13 commits into
masterfrom
codex/nut04-mint-quote-accounting

Conversation

@Egge21M

@Egge21M Egge21M commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Description

This pull request implements the NUT-04 mint quote accounting PRD on top of the PRD docs branch codex/nut04-quote-accounting-docs.

Implements #294.
Closes #297.
Closes #298.
Closes #299.
Closes #300.
Closes #301.
Closes #302.

Problem

Coco still treated remote NUT-04 mint quote state as the primary readiness signal. With cashu-ts v5 and reusable or partially issued quotes, readiness needs to come from persisted accounting fields (amount_paid, amount_issued, and observation time) while preserving atomic mint operation behavior.

Summary

  • Refactors canonical quote observation persistence into shared quote lifecycle helpers.
  • Upgrades the quote wire boundary to cashu-ts v5 and lets cashu-ts own wire normalization.
  • Persists first-class mint quote accounting columns across SQL, IndexedDB, Expo SQLite, memory, and adapter contracts.
  • Applies stale observation rules for remote accounting updates and preserves accepted null remote timestamps.
  • Drives BOLT11 mint readiness and reusable quote claiming from accounting rather than compatibility state.
  • Preserves reusable onchain and BOLT12 quote reservations and their method-specific data.

Verification

  • git diff --check
  • bun run format:check
  • bun run security:release-age
  • bun run build
  • bun run typecheck
  • bun run --filter='@cashu/coco-core' test:unit
  • bun test packages/sql-storage/src/test/schema.test.ts
  • bun --cwd packages/expo-sqlite test src/test/contract.test.ts

Changeset

  • Added .changeset/cashu-ts-v5-boundary.md.
  • Added .changeset/mint-quote-accounting-columns.md.

@github-project-automation github-project-automation Bot moved this to Backlog in coco Jul 4, 2026
@changeset-bot

changeset-bot Bot commented Jul 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 12a606e

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-adapter-tests Major
@cashu/coco-core Major
@cashu/coco-expo-sqlite Major
@cashu/coco-indexeddb Major
@cashu/coco-sql-storage Major
@cashu/coco-sqlite Major
@cashu/coco-sqlite-bun 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 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.03922% with 8 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (codex/nut04-quote-accounting-docs@740e6ea). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...ages/core/infra/handlers/mint/MintBolt11Handler.ts 85.18% 4 Missing ⚠️
...ges/core/infra/handlers/send/DefaultSendHandler.ts 85.71% 1 Missing ⚠️
...ckages/core/infra/handlers/send/P2pkSendHandler.ts 88.88% 1 Missing ⚠️
packages/core/quotes/QuoteLifecycle.ts 99.52% 1 Missing ⚠️
...e/repositories/memory/MemoryMintQuoteRepository.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                          @@
##             codex/nut04-quote-accounting-docs     #303   +/-   ##
====================================================================
  Coverage                                     ?   87.68%           
====================================================================
  Files                                        ?      131           
  Lines                                        ?    18496           
  Branches                                     ?        0           
====================================================================
  Hits                                         ?    16218           
  Misses                                       ?     2278           
  Partials                                     ?        0           
Flag Coverage Δ
core-integration 49.84% <ø> (?)
core-unit 92.13% <98.03%> (?)

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.

@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: c187989371

ℹ️ 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".

Comment thread packages/core/quotes/QuoteLifecycle.ts Outdated
await beforePersist?.(canonicalQuote);
const persisted = await this.persistCanonicalMintQuote(canonicalQuote);
return { quote: persisted, remoteStateChanged };
await this.mintQuoteRepository.setMintQuoteState(

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 Clear stale remoteUpdatedAt on local state observations

When a BOLT11 quote already has a remoteUpdatedAt and we later record a local state observation (for example finalization writes ISSUED), the resolution above intentionally sets remoteUpdatedAt: null, but this call persists through setMintQuoteState; the memory, SQL, and IndexedDB implementations only update state/accounting fields and leave the old remote timestamp in place. A later remote snapshot with a higher updated_at but stale amount_issued can then be accepted as newer and regress a completed quote back to ready/pending. Persist the resolved quote here or clear remoteUpdatedAt in setMintQuoteState.

Useful? React with 👍 / 👎.

beforePersist?: (quote: MintQuote) => Promise<void>;
} = {},
): Promise<{ quote: MintQuote; remoteQuoteChanged: boolean }> {
await options.beforePersist?.(canonicalQuote);

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 Resolve quote freshness before validating imports

When importMintQuote is called for a quote that already exists and the incoming snapshot is stale (for example a lower updated_at), this line still runs the import-only accounting/capability validation before resolveMintQuoteObservation can discard the stale observation. In that case a stale snapshot with an unsupported unit or impossible accounting throws instead of returning the canonical stored quote, even though the same observation would otherwise be ignored. Move this validation after freshness resolution, or skip it when the observation will not be persisted.

Useful? React with 👍 / 👎.

'amount' | 'state'
> & {
amount: Amount;
state?: MintQuoteBolt11Response['state'];

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 Record BOLT11 accounting-only subscription updates

Once BOLT11 quote snapshots are allowed to omit state, the subscription path can silently drop valid v5 accounting updates: MintOperationWatcherService only records BOLT11 payloads when payload.state is PAID or ISSUED. If a mint sends an update with amount_paid/amount_issued but no compatibility state, the watcher ignores it, so the quote is never marked ready and pending mint operations are not processed until another polling/recovery path runs. The BOLT11 watch policy needs to treat accounting fields as recordable too.

Useful? React with 👍 / 👎.

@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: f2f9654270

ℹ️ 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".

const keyset = wallet.getKeyset();

const sendOT = OutputData.createP2PKData({ pubkey }, amount, keyset);
const sendOT = OutputData.createP2PKData({ kind: 'P2PK', data: pubkey }, amount, keyset);

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 Pass pubkey to the P2PK output builder

When preparing any P2PK send, this now passes { kind, data } to OutputData.createP2PKData, but the upgraded cashu-ts helper still expects P2PKOptions with a pubkey field. With pubkey undefined, cashu-ts throws while building the P2PK secret, so P2PK send preparation fails before outputs are reserved; I verified this with bun run --filter='@cashu/coco-core' test -- test/unit/P2pkSendHandler.test.ts, where all prepare-path P2PK tests fail at this call.

Useful? React with 👍 / 👎.

@Egge21M Egge21M force-pushed the codex/nut04-quote-accounting-docs branch from 71560d3 to 740e6ea Compare July 6, 2026 09:23
@Egge21M Egge21M force-pushed the codex/nut04-mint-quote-accounting branch from f2f9654 to 12a606e Compare July 6, 2026 09:26

@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: 12a606ed8f

ℹ️ 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".

Comment on lines +219 to +220
const canonicalQuote = mintQuoteFromBolt11Response(mintUrl, quote);
const accountingStatus = getBolt11MintQuoteAccountingStatus(canonicalQuote);

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 Validate BOLT11 accounting before deriving status

When a BOLT11 quote response has invalid accounting, e.g. amount_issued > amount_paid, this status calculation can classify it as completed as soon as amount_issued >= amount. recordMintQuoteSnapshot later ignores the invalid snapshot, but checkPendingOperation still sees the completed category and calls finalize, so a pending operation can be moved into execution based only on an invalid remote observation. Validate the BOLT11 accounting before deriving the category, similar to the onchain/BOLT12 handlers.

Useful? React with 👍 / 👎.

@Egge21M Egge21M marked this pull request as draft July 8, 2026 08:55
@Egge21M Egge21M changed the base branch from codex/nut04-quote-accounting-docs to master July 8, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant