Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ _Avoid_: Method flow, payment workflow

**Quote Observation**:
A mint response that reports the current remote state of a quote and is recorded into Coco's
canonical quote row before any Quote-backed Operation is advanced from it.
canonical quote row before any Quote-backed Operation is advanced from it. Coco evaluates Quote
Observations against the existing canonical quote row and may accept, merge, or ignore them.
_Avoid_: Quote refresh, subscription update

**Remote Quote Update Time**:
The mint-reported Unix timestamp, in seconds, at which a quote last changed remotely. Coco exposes
this separately from the local canonical quote row update time.
_Avoid_: Updated at, row timestamp

**Quote Identity**:
A methodless reference to a mint or melt quote by mint URL and quote ID. Mint quote identities and
melt quote identities are separate namespaces.
Expand All @@ -73,16 +79,26 @@ older `PENDING` observation.
_Avoid_: Payment status, melt lifecycle

**Mint Quote Claimability**:
Whether a mint quote currently has paid value that coco can claim into proofs. BOLT11 mint quotes
are claimable when their state is `PAID`; reusable mint quotes are claimable when their paid amount
exceeds their issued amount.
Whether a mint quote currently has paid value that coco can claim into proofs. A mint quote is
claimable when its paid amount exceeds its issued amount; legacy method-specific state is
compatibility metadata, not the canonical claimability model.
_Avoid_: Mint quote paid state, payment status

**Mint Quote Accounting**:
The mint-reported paid and issued totals for a mint quote. Coco treats the difference between paid
amount and issued amount as the quote's currently claimable value.
_Avoid_: Quote state, payment status

**Mint Quote Payment Observation**:
A newly observed increase in paid value for a mint quote. It is distinct from Mint Quote
Claimability because reusable mint quotes can already be claimable before another payment arrives.
_Avoid_: Mint quote paid state, payment status

**Mint Quote Reservation**:
Locally in-flight claimable value from a reusable mint quote that Coco has assigned to a
Quote-backed Operation but the mint has not yet reflected as issued accounting.
_Avoid_: Remote issued amount, proof reservation

**Quote Expiry**:
The time after which a quote can no longer receive a new payment. Expiry does not prevent claiming
value that was already paid before expiry.
Expand Down
59 changes: 59 additions & 0 deletions docs/adr/0006-cashu-ts-owns-wire-quote-normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# cashu-ts owns wire quote normalization

Status: proposed

Coco relies on cashu-ts v5 `Mint` and `Wallet` APIs to normalize raw mint quote responses from
mints, including legacy quote accounting derivation. Coco does not duplicate that wire-level
normalization in `MintAdapter`; instead, Coco owns canonical quote persistence, merge rules, stale
observation handling, and operation advancement from normalized quote snapshots.

When an incoming Quote Observation has a lower remote update timestamp than the canonical quote row,
Coco treats it as stale, ignores it for canonical quote fields, and emits no quote-updated event.
When the incoming and canonical remote update timestamps are equal but the accounting differs, Coco
treats the observation as invalid or stale, keeps the canonical row unchanged, and logs a warning.
When either side lacks a remote update timestamp, Coco falls back to comparing
`amountPaid + amountIssued` as a freshness marker. Coco accepts the observation only when that sum
increases without decreasing either accounting component. Out-of-spec accounting observations are
ignored non-destructively: Coco keeps the canonical row unchanged, emits no quote-updated event, and
logs a warning rather than breaking watcher or refresh paths.
Coco may persist a newer remote update timestamp when quote accounting and other meaningful quote
fields are unchanged, but timestamp-only freshness changes do not emit quote-updated events.
Coco may keep legacy method-specific mint quote state for compatibility, especially for BOLT11, but
mint quote behavior is driven by canonical Mint Quote Accounting rather than that state.
BOLT11 remains atomic in Coco's built-in behavior: minting from a BOLT11 quote is all-or-nothing for
the quote amount, even though readiness is derived from accounting rather than legacy state.
For reusable quotes, Coco preserves the existing local reservation model: effective local
availability subtracts in-flight Mint Quote Reservations, inferred from executing operations, and
also treats finalized local operations as issued if the mint's remote accounting has not caught up.
BOLT11 uses Mint Quote Accounting for readiness and stale protection, but it does not participate in
reusable quote reservation math because Coco treats BOLT11 mint quotes as atomic.
Coco exposes Remote Quote Update Time on canonical quote objects. Storage migrations backfill
accounting from legacy BOLT11 state when possible, preserve reusable quote accounting, and leave
Remote Quote Update Time unset because local row timestamps are not mint-reported timestamps.
Coco keeps the public BOLT11 mint quote `state` field for compatibility, marks it deprecated in
JSDoc, and avoids using it to drive mint quote behavior.
Canonical mint quote accounting fields live on the shared mint quote shape rather than inside
method-specific `quoteData`; `quoteData` is reserved for method-specific facts.
Repositories store `amountPaid`, `amountIssued`, and `remoteUpdatedAt` as first-class canonical
quote columns rather than inside method-specific JSON.
Mint quote persistence migrates away from `lastObservedRemoteState` and
`lastObservedRemoteStateAt`; those concepts belong to the old mint state model and do not apply to
melt quote state semantics.
This change does not add change-reason metadata to quote update events; event payloads continue to
carry the canonical quote, and consumers can inspect that quote if they need details.
The refactor is a public type-level breaking change because canonical mint quotes gain required
accounting fields and nullable Remote Quote Update Time. Runtime create, check, and import paths
remain compatible by deriving or normalizing legacy BOLT11 data where possible.
For explicit quote imports, Coco accepts legacy BOLT11 snapshots with `state` and `amount`, derives
canonical accounting from them, and persists the normalized canonical quote shape.
Coco also derives the deprecated public BOLT11 `state` field from accounting when a BOLT11 snapshot
has accounting but omits state, so compatibility consumers continue to receive a populated state.
Coco rejects explicitly imported mint quote accounting where issued amount exceeds paid amount. For
background Quote Observations, Coco treats that accounting as invalid, keeps the canonical row
unchanged, emits no quote-updated event, and logs a warning.
Coco persists the canonical quote method from its own route or handler context. Remote or imported
snapshots that report a conflicting method are invalid and must not be persisted under the requested
method.
Coco keeps separate local and remote update times: `updatedAt` is the local canonical row update
time in milliseconds, while `remoteUpdatedAt` is the mint-reported Remote Quote Update Time in
seconds. `remoteUpdatedAt` is nullable because legacy mints cannot provide or derive it.
Loading