Problem Statement
Coco currently treats bolt11, bolt12, and onchain as the only first-class quote-backed payment methods. Mints can advertise additional base quote struct compatible payment methods through NUT-04 and NUT-05 metadata, but applications cannot discover those Payment Method Capabilities through a focused coco API, cannot create quotes for those methods without compile-time method support, and cannot run quote-backed mint or melt operations against those quotes.
This prevents applications from using Generic Payment Methods as soon as a Trusted Mint supports them. It also forces method support into compile-time type registries even though the mint advertises the method string at runtime.
Solution
Add Generic Payment Method support to coco while keeping Built-in Payment Methods first-class.
Users should be able to discover mint and melt Payment Method Capabilities from mint metadata, create Generic Payment Method quotes using explicit generic entry points, inspect raw quote response data, and execute quote-backed mint and melt operations through coco's existing durable saga model.
Built-in Payment Methods remain typed and autocomplete-friendly. Generic Payment Methods preserve the mint-advertised method string and use generic mint and melt Payment Method Handlers. Generic mint quotes are reusable, wallet-key locked, and claimable according to paid value that has not yet been issued. Generic melt quotes use the BOLT-like quote-backed melt saga with a concrete fee reserve, defaulting missing fee_reserve to zero.
User Stories
- As an application developer, I want to list a Trusted Mint's mint Payment Method Capabilities, so that I can show users which payment methods can fund their Wallet.
- As an application developer, I want to list a Trusted Mint's melt Payment Method Capabilities, so that I can show users which payment methods can pay out of their Wallet.
- As an application developer, I want to check whether a specific method/unit pair is supported for minting, so that I can validate a user-selected payment method before creating a quote.
- As an application developer, I want to check whether a specific method/unit pair is supported for melting, so that I can validate a payout method before creating a quote.
- As an application developer, I want capability checks to use coco's existing mint-info TTL behavior, so that capability checks stay inexpensive and consistent with the rest of mint metadata handling.
- As an application developer, I want disabled NUT-04 or NUT-05 settings to be explained by single capability checks, so that I can diagnose why a method is unavailable.
- As an application developer, I want capability listing to return only actionable capabilities, so that I do not present disabled methods to users.
- As an application developer, I want
bolt11, bolt12, and onchain to remain autocomplete-friendly Built-in Payment Methods, so that common flows stay ergonomic.
- As an application developer, I want Generic Payment Method entry points to accept arbitrary method strings, so that my application can use methods introduced by mints without waiting for a coco release.
- As an application developer, I want generic quote creation to require an explicit generic input shape, so that mistyped Built-in Payment Methods do not silently take a generic path.
- As an application developer, I want generic handlers to reject Built-in Payment Method names, so that built-in validation cannot be bypassed.
- As an application developer, I want Generic Payment Methods to preserve the mint-advertised method string, so that persisted quotes and operations can be traced back to the exact mint method.
- As a Wallet user, I want coco to generate quote keys for Generic Payment Method mint quotes, so that mint operation recovery does not depend on external key material.
- As a Wallet user, I want coco to reject generic mint quotes that are not locked to coco's generated quote key, so that funds are not bound to unknown key ownership.
- As a Wallet user, I want generic mint quotes to require reusable accounting fields, so that coco can safely calculate the claimable amount.
- As an application developer, I want generic mint quote creation to include
amount, unit, and a coco-generated pubkey, so that the payer-side request can be created through the base mint quote request shape.
- As an application developer, I want generic mint quote creation to allow additional method-specific payload fields, so that future methods can accept extra quote creation data.
- As an application developer, I want coco to own
unit and pubkey in generic mint payloads, so that callers cannot accidentally create quotes that conflict with local Wallet state.
- As an application developer, I want generic mint quote responses to expose raw mint response JSON, so that method-specific fields are available even when coco does not understand them.
- As an application developer, I want raw quote data to mean the mint's unparsed JSON object, so that public quote objects match what the mint actually returned.
- As an application developer, I want generic quote refresh to replace
rawQuoteData with the latest raw mint response, so that canonical quote state reflects the latest observed mint state.
- As a Wallet user, I want generic mint quote refresh to reject a changed quote
pubkey, so that a quote cannot silently move to different key ownership.
- As a Wallet user, I want generic mint quote accounting to keep the maximum observed
amount_paid and amount_issued, so that stale mint responses do not reduce locally known paid or issued totals.
- As a Wallet user, I want generic mint operations to claim any amount up to the available paid-but-unissued amount, so that a reusable quote can be partially minted.
- As a Wallet user, I want the generic mint quote creation amount to describe what the payer must settle, not force the later mint operation amount, so that reusable quotes behave like reusable quotes.
- As a Wallet user, I want automatic mint quote claiming to include Generic Payment Method mint quotes, so that paid reusable generic quotes are processed like other reusable quotes.
- As an application developer, I want
getMintQuoteAmount() semantics to continue meaning fixed mint operation amount, so that reusable generic quotes do not appear to force a mint amount.
- As an application developer, I want generic mint quote import to remain out of scope initially, so that coco does not accept externally keyed generic quotes before key ownership rules are designed.
- As an application developer, I want generic melt quote creation to require
request, so that coco follows the base NUT-05 request shape.
- As an application developer, I want generic melt quote creation to allow method-specific payload fields excluding
unit, so that coco owns the quote unit while preserving extensibility.
- As a Wallet user, I want generic melt quotes to use a fee reserve for proof selection, so that coco can reserve enough value before executing a payout.
- As a Wallet user, I want missing generic melt
fee_reserve to default to zero, so that zero-fee methods can still work while raw quote data preserves the missing field.
- As a Wallet user, I want invalid present generic melt
fee_reserve values to reject the quote, so that malformed quote data does not cause unsafe proof selection.
- As a Wallet user, I want generic melt operations to use the same proof selection, swap, blank output, rollback, and recovery saga as Built-in Payment Methods, so that Generic Payment Methods do not create a weaker payout lifecycle.
- As a Wallet user, I want generic melt execution to support
UNPAID, PENDING, and PAID states, so that async settlement works like BOLT-style melts.
- As a Wallet user, I want
PENDING melt quotes to be able to return to UNPAID, so that failed settlement can release spendable inputs.
- As a Wallet user, I want
PAID melt quote state to be terminal, so that stale or impossible mint responses cannot roll back settled payments.
- As an application developer, I want generic melt finalized data to preserve raw final response data, so that method-specific settlement fields remain visible.
- As an application developer, I want Built-in Payment Methods to continue projecting typed finalized data where supported, so that existing built-in ergonomics remain strong.
- As an application developer, I want repositories to persist and round-trip arbitrary method strings, so that Generic Payment Method quotes and operations survive across Coco Sessions.
- As an application developer, I want quote repositories to persist
rawQuoteData when present, so that raw generic quote data is not lost after restart.
- As an application developer, I want operation repositories to round-trip generic operation method data, so that recovery has the data it needs after interruption.
- As a Wallet user, I want operation recovery to use coco's existing deterministic output recovery for generic mint operations, so that crash recovery behaves like Built-in Payment Methods.
- As a Wallet user, I want generic mint execution to use coco-owned deterministic outputs, so that proof recovery remains replay-safe.
- As an application developer, I want generic adapter quote calls to capture raw mint JSON before parsing lifecycle fields, so that raw response preservation is accurate.
- As an application developer, I want transport-level generic method validation without built-in-name rejection, so that transport modules stay concerned with endpoint validity rather than handler routing policy.
- As an application developer, I want method routing policy to live in Payment Method Handler routing, so that Built-in Payment Method rejection is not duplicated in low-level transport code.
- As an application developer, I want generic integration tests to discover compatible methods dynamically, so that tests begin exercising real Generic Payment Methods as soon as the local fakewallet mint supports them.
- As an application developer, I want integration tests to skip generic operation execution when the mint does not advertise a compatible Generic Payment Method, so that CI remains stable before mintd ships support.
- As a maintainer, I want Generic Payment Method support to be delivered in isolated slices, so that capability discovery, generic minting, shared melt saga refactoring, and generic melting can be reviewed independently.
Implementation Decisions
- Use the domain term Generic Payment Method for base quote struct compatible methods that are advertised by a mint but are not Built-in Payment Methods.
- Keep
bolt11, bolt12, and onchain as Built-in Payment Methods with narrow typed inputs, return types, autocomplete, and dedicated handlers.
- Treat this as a major breaking change. Do not keep compatibility aliases or declaration-merging extension surfaces that obscure the new built-in/generic split.
- Replace payment-method declaration merging as the extension mechanism for payment methods. Generic Payment Methods are the extension mechanism for runtime mint-advertised methods in this PRD.
- Do not expose plugin handler registration in this PRD. It remains a future extension point.
- Add public Payment Method Capability APIs on the mint-facing API surface:
- Single capability check by mint URL, operation kind, method, and unit.
- Capability listing by mint URL with optional operation kind and unit filters.
- Map operation kind to NUT-04 for mint and NUT-05 for melt.
- Use existing mint-info TTL behavior for capability checks and listing.
- For listing, return actionable capabilities. For a single check, include unsupported/disabled details and a reason.
- Add generic quote creation entry points that are explicit and distinct from Built-in Payment Method input shapes.
- Reject Built-in Payment Method names at the generic handler routing layer, not in low-level transport.
- Generic mint quote creation requires a first-class amount. Coco sends
amount, normalized unit, and a generated wallet-controlled quote pubkey, plus optional extra payload fields.
- Generic mint quote creation rejects caller payload fields that conflict with coco-owned fields such as
unit and pubkey.
- Generic mint quotes are reusable. They must include
amount_paid and amount_issued; missing or invalid values reject the quote.
- Generic mint quotes may include missing expiry; canonical quote state normalizes missing expiry to
null.
- Generic mint quotes must return the generated
pubkey. Missing or mismatched pubkey rejects creation and refresh.
- Generic mint quote operation claimability is
amount_paid - amount_issued, adjusted by local finalized sibling operations as existing reusable quote logic does.
- Generic mint quote refresh keeps maximum observed paid and issued totals to avoid regressions from stale mint responses.
- Generic mint quote requested amount is request metadata and does not constrain later mint operation amount. Generic mint operations may claim any amount up to available paid-but-unissued value.
getMintQuoteAmount() continues to mean fixed mint operation amount and returns no fixed amount for reusable generic mint quotes.
- Generic mint quote import is out of scope for the first implementation.
- Add generic mint and melt Payment Method Handlers and wire them into the default Manager automatically as fallback handlers for non-built-in methods after capability checks.
- Generic mint execution uses coco's existing deterministic output flow and upstream wallet mint execution helpers. It does not introduce a second upstream preview persistence model.
- Generic mint recovery uses existing deterministic output proof recovery when a quote was already issued or execution was interrupted.
- Generic melt quote creation requires
request. Coco owns unit; extra method-specific creation data lives in generic payload fields.
- Generic melt operation method data persists
request and extra payload fields, excluding unit.
- Generic melt quote responses use base NUT-05 fields and optional
fee_reserve. Missing fee_reserve is stored as zero. Invalid present fee_reserve rejects the quote.
- Generic melt uses one concrete fee reserve. Fee-option selection remains an onchain Built-in Payment Method concern and is out of generic melt scope.
- Generic melt execution sends the base melt payload of quote, inputs, and outputs initially. Extra execution fields are out of scope.
- Generic melt finalized data should include raw final response data. Built-in methods keep typed finalized data projections.
- Melt Quote State handling should accept
PENDING -> UNPAID as a settlement failure path. Any transition away from PAID should be ignored to preserve terminal paid state.
- Align Built-in Payment Method and Generic Payment Method designs where possible. Quote-backed minting and melting should share saga shape for output data, input selection, proof state, rollback, and Operation Recovery.
- Refactor shared melt saga behavior in an isolated slice before adding generic melt execution, so Generic Payment Methods reuse the same melt operation machinery rather than duplicating it.
- Extract shared reusable mint quote helpers where they reduce duplication, but do not initially rewrite built-in mint handlers as wrappers over generic mint.
- Add
rawQuoteData to generic quote models as the raw JSON object returned by the mint, before coco parses amounts or lifecycle fields.
rawQuoteData is public on generic quote return objects and typed as Record<string, unknown>.
- Repositories should preserve
rawQuoteData for any quote when present, including built-in quote models, even though built-ins do not require it.
- Add schema migrations for raw quote data storage for mint and melt quote tables.
- Generic quote adapter create/check calls should bypass high-level quote helpers where needed to capture raw JSON before normalization.
- Generic mint execution can still use wallet mint execution helpers because raw quote preservation only applies to quote create/check.
- Generic melt execution should use generic MintAdapter endpoints and coco's existing custom output flow rather than wallet
completeMelt previews.
- Add a small method string validator for direct generic transport calls, but keep Built-in Payment Method rejection out of the transport layer.
- Add small generic quote parsing helpers for raw object validation and lifecycle field extraction.
- Name remote response contracts
GenericMintQuoteSnapshot and GenericMeltQuoteSnapshot.
- Name public canonical quote models
GenericMintQuote and GenericMeltQuote.
- Use
BuiltInMintMethod, BuiltInMeltMethod, GenericMintQuote, and GenericMeltQuote as public vocabulary. Remove misleading default-supported compatibility names as part of the breaking change.
- Keep React package API changes out of the first core implementation. React wrappers can follow after core is stable.
- Update docs after generic mint support lands, then extend docs after generic melt support lands.
- Suggested delivery order:
- Capability API.
- Type, model, schema, and repository widening for arbitrary method strings and raw quote data.
- Generic mint handler and generic mint operations.
- Isolated shared melt saga refactor.
- Generic melt handler and generic melt operations.
- Documentation and capability-gated integration coverage.
Testing Decisions
- Prefer the highest existing seams: public core APIs for behavior, quote/operation repositories for persistence, Payment Method Handler/provider seams for routing, and the existing integration harness for end-to-end behavior.
- Unit tests should verify external behavior rather than internal helper details. Parsing helpers can be tested through quote creation/refresh behavior unless direct parser tests are needed for malformed raw JSON edge cases.
- Add tests for Payment Method Capability single checks and listing, including disabled settings, missing NUT metadata, operation kind mapping, unit filtering, and arbitrary method names.
- Add type-level tests or
ts-expect-error assertions proving Built-in Payment Methods retain narrow typed inputs/returns and generic arbitrary-string calls return generic quote/operation types rather than never or any.
- Add tests that generic create paths reject Built-in Payment Method names through handler routing.
- Add generic mint quote tests for amount/unit/pubkey payload construction, capability validation, raw quote preservation, reusable accounting validation, missing/invalid accounting rejection, pubkey mismatch rejection, expiry normalization, and unit mismatch rejection.
- Add generic mint operation tests for prepare, execute, partial claiming, claimable amount calculation, max paid/issued persistence on refresh, automatic claim processing, and Operation Recovery using deterministic output data.
- Add repository contract tests for arbitrary method quote persistence, raw quote data round-tripping, identity lookup, identity collision protection, pending quote listing, and operation method data round-tripping.
- Add schema migration tests for raw quote data columns and preservation across upgrades.
- Add generic melt quote tests for request/unit payload construction, capability validation, raw quote preservation, fee reserve normalization, missing fee reserve zero fallback, invalid fee reserve rejection, expiry requirement, state handling, and raw finalized data.
- Add melt state transition tests covering
PENDING -> UNPAID as allowed and transitions away from PAID as ignored.
- Add shared melt saga refactor tests by keeping existing Built-in Payment Method melt behavior green while adding generic melt tests through the same public operation seams.
- Add integration tests through the existing fakewallet mint harness. The generic integration path should first use the new capability API to discover compatible non-built-in methods and skip operation execution if none are advertised.
- Generic mint integration should dynamically discover a compatible mint capability, create a generic quote using the basic request of amount plus coco-owned unit/pubkey, execute the quote-backed mint operation, and verify Wallet balance changes when the mint supports it.
- Generic melt integration should be capability-gated similarly after generic melt support exists.
- Do not require a fake external custom mint server for unit tests. Use mocked adapter and wallet behavior for arbitrary method strings.
- Do not make CI depend on unshipped mintd Generic Payment Method support. Capability-gated integration tests should begin exercising real paths automatically once the local mint image supports them.
Out of Scope
- React package hooks and provider changes.
- Generic mint quote import.
- Plugin registration for custom method-specific handlers.
- Method profiles that define method-specific generic schemas beyond the base quote struct compatible contract.
- Generic onchain-like fee option selection.
- Extra generic melt execution fields beyond quote, inputs, and outputs.
- Historical raw quote response snapshots.
- Rewriting Built-in Payment Method handlers to be thin wrappers over generic handlers.
- Adding support for Generic Payment Methods that do not use the agreed base quote-compatible shapes.
- External integration fixtures beyond the existing fakewallet mint harness.
Further Notes
- The domain glossary defines Built-in Payment Method, Generic Payment Method, Payment Method Capability, Payment Method Handler, Quote-backed Operation, and Melt Quote State.
- ADR-0001 records that generic mint quotes are reusable and key locked.
- ADR-0002 records the decision to keep Built-in Payment Methods first-class while adding explicit generic method strings as a breaking API change.
- ADR-0003 records the decision to preserve raw generic quote responses as unparsed mint JSON.
- The current code already has useful seams: mint capability checks exist internally on the mint service, quote lifecycle owns quote persistence and refresh, handlers own method-specific behavior, and repositories already mostly store method strings. The PRD should deepen these modules rather than spreading generic branches across callers.
Problem Statement
Coco currently treats
bolt11,bolt12, andonchainas the only first-class quote-backed payment methods. Mints can advertise additional base quote struct compatible payment methods through NUT-04 and NUT-05 metadata, but applications cannot discover those Payment Method Capabilities through a focused coco API, cannot create quotes for those methods without compile-time method support, and cannot run quote-backed mint or melt operations against those quotes.This prevents applications from using Generic Payment Methods as soon as a Trusted Mint supports them. It also forces method support into compile-time type registries even though the mint advertises the method string at runtime.
Solution
Add Generic Payment Method support to coco while keeping Built-in Payment Methods first-class.
Users should be able to discover mint and melt Payment Method Capabilities from mint metadata, create Generic Payment Method quotes using explicit generic entry points, inspect raw quote response data, and execute quote-backed mint and melt operations through coco's existing durable saga model.
Built-in Payment Methods remain typed and autocomplete-friendly. Generic Payment Methods preserve the mint-advertised method string and use generic mint and melt Payment Method Handlers. Generic mint quotes are reusable, wallet-key locked, and claimable according to paid value that has not yet been issued. Generic melt quotes use the BOLT-like quote-backed melt saga with a concrete fee reserve, defaulting missing
fee_reserveto zero.User Stories
bolt11,bolt12, andonchainto remain autocomplete-friendly Built-in Payment Methods, so that common flows stay ergonomic.amount,unit, and a coco-generatedpubkey, so that the payer-side request can be created through the base mint quote request shape.unitandpubkeyin generic mint payloads, so that callers cannot accidentally create quotes that conflict with local Wallet state.rawQuoteDatawith the latest raw mint response, so that canonical quote state reflects the latest observed mint state.pubkey, so that a quote cannot silently move to different key ownership.amount_paidandamount_issued, so that stale mint responses do not reduce locally known paid or issued totals.getMintQuoteAmount()semantics to continue meaning fixed mint operation amount, so that reusable generic quotes do not appear to force a mint amount.request, so that coco follows the base NUT-05 request shape.unit, so that coco owns the quote unit while preserving extensibility.fee_reserveto default to zero, so that zero-fee methods can still work while raw quote data preserves the missing field.fee_reservevalues to reject the quote, so that malformed quote data does not cause unsafe proof selection.UNPAID,PENDING, andPAIDstates, so that async settlement works like BOLT-style melts.PENDINGmelt quotes to be able to return toUNPAID, so that failed settlement can release spendable inputs.PAIDmelt quote state to be terminal, so that stale or impossible mint responses cannot roll back settled payments.rawQuoteDatawhen present, so that raw generic quote data is not lost after restart.Implementation Decisions
bolt11,bolt12, andonchainas Built-in Payment Methods with narrow typed inputs, return types, autocomplete, and dedicated handlers.amount, normalizedunit, and a generated wallet-controlled quotepubkey, plus optional extra payload fields.unitandpubkey.amount_paidandamount_issued; missing or invalid values reject the quote.null.pubkey. Missing or mismatchedpubkeyrejects creation and refresh.amount_paid - amount_issued, adjusted by local finalized sibling operations as existing reusable quote logic does.getMintQuoteAmount()continues to mean fixed mint operation amount and returns no fixed amount for reusable generic mint quotes.request. Coco ownsunit; extra method-specific creation data lives in generic payload fields.requestand extra payload fields, excludingunit.fee_reserve. Missingfee_reserveis stored as zero. Invalid presentfee_reserverejects the quote.PENDING -> UNPAIDas a settlement failure path. Any transition away fromPAIDshould be ignored to preserve terminal paid state.rawQuoteDatato generic quote models as the raw JSON object returned by the mint, before coco parses amounts or lifecycle fields.rawQuoteDatais public on generic quote return objects and typed asRecord<string, unknown>.rawQuoteDatafor any quote when present, including built-in quote models, even though built-ins do not require it.completeMeltpreviews.GenericMintQuoteSnapshotandGenericMeltQuoteSnapshot.GenericMintQuoteandGenericMeltQuote.BuiltInMintMethod,BuiltInMeltMethod,GenericMintQuote, andGenericMeltQuoteas public vocabulary. Remove misleading default-supported compatibility names as part of the breaking change.Testing Decisions
ts-expect-errorassertions proving Built-in Payment Methods retain narrow typed inputs/returns and generic arbitrary-string calls return generic quote/operation types rather thanneverorany.PENDING -> UNPAIDas allowed and transitions away fromPAIDas ignored.Out of Scope
Further Notes