Replace .unwrap() with proper error handling using ? operator and add
debug logging to help diagnose fee calculation issues in the melt flow.
Changes:
- Replace .unwrap() with ? for proper error propagation
- Add tracing log for proofs amount, amount, and change amount
feat: nut14-validation
# Implement Saga Pattern for Swap Operations with Recovery Mechanism
## Overview
This PR refactors the swap operation implementation to use the saga pattern - a distributed transaction pattern that provides reliable transaction management through explicit state tracking and compensation-based error handling. The implementation includes a robust recovery mechanism that automatically handles swap operations interrupted by crashes, power loss, or network failures.
## What Changed
**Saga Pattern Implementation:**
- Introduced a strict linear state machine for swaps: `Initial` → `SetupComplete` → `Signed` → `Completed`
- New modular `swap_saga` module with state validation, compensation logic, and saga orchestration
- Automatic rollback of database changes on failure, ensuring atomic swap operations
- Replaced previous swap implementation (`swap.rs`, `blinded_message_writer.rs`) with saga-based approach
**Recovery Mechanism:**
- Added `operation_id` and `operation_kind` columns to database schema for tracking which operation proofs belong to
- New `recover_from_bad_swaps()` method that runs on mint startup to handle incomplete swaps
- For proofs left in `PENDING` state from swap operations:
- If blind signatures exist: marks proofs as `SPENT` (swap completed but not finalized)
- If no blind signatures exist: removes proofs from database (swap failed partway through)
- Database migrations included for both PostgreSQL and SQLite
Add state machine validation for melt quote transitions to prevent
invalid state changes. Includes new error types and validation logic
for Unpaid, Pending, Paid, and Failed states.
* fix(wallet): move transaction ID calculation before database operations
* fix(sql): remove on ys from on conflict transaction insert
Since the id is created from the ys we know that if there is a conflict
the ys are the same and do not need to be updated.
* feat: bench for transactio id
* chore: fmt
* pubsub: consolidate into Spec, adopt Arc<SubscriptionId>, and wire through wallet/mint/WS/FFI
Refactor the pub/sub engine to a single Spec trait, move Event alongside it,
and propagate Arc-backed subscription IDs across the stack. This simplifies
generics, clarifies responsibilities, and preserves coalescing +
latest-on-subscribe semantics.
- **Single source of truth:** `Spec` owns `Topic`, `Event`, `SubscriptionId`,
`Context`, new_instance, and fetch_events.
- **Lean & explicit API:** Remove Topic trait split;
`Subscriber::send(Event)` carries sub-ID internally.
- **Performance/ergonomics:** `Arc<SubscriptionId>` avoids heavy clones and
makes channel/task hops trivial.
- Introduce `pub_sub/typ.rs` with:
- trait `Spec`
- trait `Event` colocated with Spec.
- Remove `pub_sub/event.rs` fold `Event` into `typ.rs`.
- Make `Pubsub<S>` generic over `Spec` and store `Arc<S>`.
- The subscriber holds `Arc<SubscriptionId>` and deduplicates the latest
entry per subscription.
- SubscriptionRequest: rename SubscriptionName → SubscriptionId; return
`Arc<...>` from `subscription_name()`.
- Remote consumer (Transport) now parameterized by `Spec`; control types
updated:
- `StreamCtrl<S>`, `SubscribeMessage<S>`, internal caches keyed by
`S::Topic`.
- Mint/wallet:
- Mint: `MintPubSubSpec` (Context = `DynMintDatabase`),
`PubSubManager(Pubsub<MintPubSubSpec>)`.
- Wallet: lightweight MintSubTopics Spec with `Context = ()`.
- IDs go Arc end-to-end:
- cdk-axum WS maps `HashMap<Arc<SubId>, JoinHandle<()>>`, publisher sends
`(Arc<SubId>, NotificationPayload)`.
- `subscription::{Params, WalletParams}` now use `Arc<...>`.
- cdk-ffi conversions & wallet glue updated.
- Integration tests updated for new types.
- Coalescing unchanged: multiple local subs to the same topic are combined
into a single remote sub.
- Backfill via `Spec::fetch_events(topics, Subscriber)`; Subscriber enforces
latest-only dedupe per subscription.
**Result:** a slimmer, more maintainable pub/sub core that’s easier to embed
across mint, wallet, transports, and FFI without sacrificing performance or
semantics.
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>
Add payment_request and payment_proof fields to Transaction model to store Lightning invoice details and payment preimages. Update database migrations and all transaction creation points across wallet operations (mint, melt, send, receive) to populate these fields.
* feat: optimize SQL balance calculation
replace proof-fetching approach with SUM aggregation
- add get_balance() method to Database trait
- implement SQL SUM aggregation in cdk-sql-common
- update total_balance() to use get_balance() instead of get_unspent_proofs()
- redb impl maintains existing behavior
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>
Co-authored-by: Cesar Rodas <cesar@rodasm.com.py>
If you had 2 Transactions that had the same timestamp the sort could be
unstable and could cause annoying issues. Now it will compare the ids if
they are equal and should always have a stable sorting implementation
now.
* fix(cdk): prevent duplicate blinded message processing with database constraints
Add unique constraints on blinded_message column in both PostgreSQL and SQLite databases, and implement application-level checks to prevent duplicate blinded messages from being processed. Also ensure proper cleanup of melt requests after successful processing.
* feat: db tests for unique
* refactor(cdk-sql): consolidate blinded messages into blind signature table
Migrate from separate blinded_messages table to unified blind_signature table.
Add signed_time column and make c column nullable to track both pending
blind messages (c=NULL) and completed signatures. Update insert/update
logic to handle upsert scenarios for blind signature completion.
* refactor(cdk-sql): remove unique constraint migration and filter queries for signed messages
Remove database-level unique constraint on blinded_message and instead filter
queries to only consider messages with signatures (c IS NOT NULL
* refactor(database): improve blinded message duplicate detection using database constraints
Replace manual duplicate checking with database constraint handling for better
reliability and simplified code flow in melt request processing.
* refactor(cdk-sql): optimize blind signature processing with batch queries
Replace individual queries per blinded message with single batch query
and HashMap lookup to eliminate N+1 query performance issue.
* fix: signed time to swap sigs
* refactor(cdk): split blinded message handling and improve duplicate detection
- Split add_melt_request_and_blinded_messages into separate methods
- Add blinded messages to database before signing in swap operations
- Improve duplicate output detection with proper error handling
- Make add_blinded_messages method accept optional quote_id for flexibility
* refactor(cdk): add BlindedMessageWriter for improved transaction rollback
- Add BlindedMessageWriter component for managing blinded message state
- Implement proper rollback mechanisms in swap operations
- Add delete_blinded_messages database interface for cleanup
- Improve error handling with better state management
* Include supported amounts instead of assuming the power of 2
The mint's signatory defines the amounts and the wallet, and the mint, when
paying, should use them instead of assuming the supported amounts are
2^(0..32), which is not part of the spec.
* Introduce FeeAndAmount struct
* feat: add FFI types and conversion logic for NUT-04 and NUT-05 settings in `cdk-ffi`
* feat: remove auth feature and deafult to having auth
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>
* feat(cdk): add amount_mintable method and improve mint quote validation
- Add MintQuote::amount_mintable() method to calculate available mint amount
- Update mint issue logic to use centralized amount calculation
- Add validation for Bolt11 payment amounts matching quote amounts
- Improve error handling and logging for quote amount mismatches
* Add consistent ordering of sql migrations
Also sort the prefix and not only the filenames
* Reorganize tests, add mint quote/payment coverage, and prevent over-issuing
Reorganizes the mint test suite into clear modules, adds comprehensive mint
quote & payment scenarios, enhances the shared test macro, and hardens SQL
logic to forbid issuing more than what’s been paid.
These tests were added:
* Add quote once; reject duplicates.
* Register multiple payments and verify aggregated amount_paid.
* Read parity between DB and in-TX views.
* Reject duplicate payment_id in same and different transactions.
* Reject over-issuing (same TX, different TX, with/without prior payments).
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>
- Add quote_id field to Transaction struct in cdk-common
- Add database migrations for quote_id column in SQLite and PostgreSQL
- Update wallet operations to populate quote_id for mint/melt transactions
- Set quote_id to None for send/receive operations without associated quotes
* feat(cdk): add generic key-value store functionality for mint databases
Implements a comprehensive KV store system with transaction support,
namespace-based organization, and validation for mint databases.
- Add KVStoreDatabase and KVStoreTransaction traits for generic storage
- Include namespace and key validation with ASCII character restrictions
- Add database migrations for kv_store table in SQLite and PostgreSQL
- Implement comprehensive test suite for KV store functionality
- Integrate KV store traits into existing Database and Transaction bounds
Add payment_method field to MeltQuote struct to track whether quotes use BOLT11 or BOLT12 payment methods. Include database migrations for both SQLite and PostgreSQL to support the new field. Update melt operations to set and use the payment method for proper routing between BOLT11 and BOLT12 endpoints.
Rename wait_any_incoming_payment to wait_payment_event and change return type
from WaitPaymentResponse stream to Event stream. This introduces a new Event
enum that wraps payment responses, making the system more extensible for
future event types.
- Add Event enum with PaymentReceived variant
- Update MintPayment trait method signature
- Refactor all payment backend implementations (LND, CLN, LNBits, fake wallet)
- Update mint and payment processor to handle new event stream forma
* Introduce Future Streams for Payments and Minting Proofs
Introduce Future Streams (`ProofStream`, `PaymentStream`) for Payments and
Proofs, an easier to use interface, async friendly, to interact for the mint
waiting for payments of mints for Bolt11 and Bolt12.
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>
* feat: add LDK Node Lightning backend with comprehensive integration
- Add new cdk-ldk-node crate implementing Lightning backend using LDK Node
- Extend MintPayment trait with start/stop methods for processor lifecycle management
- Add LDK Node configuration support to cdk-mintd with chain source and gossip options
- Enhance mint startup/shutdown to properly manage payment processor lifecycle
---------
Co-authored-by: Erik <78821053+swedishfrenchpress@users.noreply.github.com>
* refactor(cdk): defer BOLT12 invoice fetching to payment execution
Move BOLT12 invoice generation from quote creation to payment time, make
request_lookup_id optional for offers, and simplify payment structures by
removing unnecessary boxing and intermediate storage of invoices.
* feat(cdk): add Bolt12 mint quote subscription support
Extends subscription to handle Bolt12 payment method alongside existing Bolt11 support across wallet, mint, and CLI components.
* Add PostgreSQL support for mint and wallet
* Fixed bug to avoid empty calls `get_proofs_states`
* Fixed SQL bug
* Avoid redudant clone()
* Add more tests for the storage layer
* Minor enhacements
* Add a generic function to execute db operations
This function would log slow operations and log errors
* Provision a postgres db for tests
* Update deps for msrv
* Add postgres to pipeline
* feat: add psgl to example and docker
* feat: db url fmt
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>
* feat: refresh keysets
* fix(cdk): resolve keyset counter skipping index 0 in deterministic secret generation
- Modified Database::get_keyset_counter to return u32 instead of Option<u32>
- Added database migrations to increment existing keyset counters by 1
- Removed counter increment logic from wallet operations to use actual counter value
- Ensures deterministic secret generation starts from index 0 instead of skipping it
The primary purpose of this new crate is to have a common and shared codebase
for all SQL storage systems. It would force us to write standard SQL using best
practices for all databases.
This crate has been extracted from #878
* Split the database trait into read and transactions.
The transaction traits will encapsulate all database changes and also expect
READ-and-lock operations to read and lock records from the database for
exclusive access, thereby avoiding race conditions.
The Transaction trait expects a `rollback` operation on Drop unless the
transaction has been committed.
* fix: melt quote duplicate error
This change stops a second melt quote from being created
if there is an existing valid melt quote for an invoice already.
If the first melt quote has expired then we allow for a new melt quote to be created.
---------
Co-authored-by: thesimplekid <tsk@thesimplekid.com>