* fix: add proof recovery mechanism for failed wallet operations
This commit introduces a new `try_proof_operation` helper that wraps wallet
operations (swap, melt) with automatic proof recovery in case of network or
mint failures. When an operation fails, the wallet now attempts to recover by
marking proofs as unspent and swapping them to prevent loss of funds.
Fixes#1180
* Fix websocket issues and mint quotes
Fixes#1209
When checking with HTTP pulling, mint quotes would ping the ln backend if
needed to get any payment that may not be included, so the mint uses this
chance to correct its database.
This check was missing for database subscriptions
Implement automatic reconnection logic when LNbits websocket connection is lost, using exponential backoff strategy (1s to 30s max) with automatic resubscription
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
* Read the latest mint quote status in a transaction to avoid race conditions
Fixes#1162
* Reload quote for all cases
* Exit on paid invoices only for bolt11
* 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 is_empty method to nut15::Settings and configure skip_serializing_if
attribute to prevent empty NUT15 objects from appearing in serialized
mint info responses.
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>
fixes + tor feature in cdk-cli
fix: call `clone_with_prefs` to get a new isolation token
format
remove `new_isolated` from Transport trait
fix: remove tor dependencies under wasm32, disallow compilation with tor feature and wasm32
tor_transport in its own file
fixes
fmt
format
tor: implement Transport::resolve_dns_txt for TorAsync using DoH over Tor; fix tor transport trait changes after rebase; remove unused as_str() call for TorToggle in cdk-cli. Ensure compilation with features: tor,bip353
format
remove double reference
format
feat: circuits pool
format
tor_transport: deterministically select Tor client per request using index_for_request(endpoint path + query + payload)\n\n- Add index_for_request(&Url, Option<&[u8]>) using FNV-1a 64-bit (dependency-free)\n- Replace round-robin next_index() usage in request() with deterministic index\n- Adjust request() to accept Option<Vec<u8>> body to hash payload bytes\n- Update http_get/http_post/resolve_dns_txt to call new request signature\n- Keep next_index() as dead_code for potential fallback
tor_transport: implement Default by bootstrapping with default pool size (blocking)\n\n- Default now attempts to use existing Tokio runtime handle, or creates a temporary runtime\n- Preserves previous behavior for async constructors (new/with_pool_size)
tor_transport: fix Default to avoid nested runtime panic by initializing on a new thread when no Handle available\n\n- If a runtime is present, block_on via current handle\n- Otherwise, spawn a new OS thread and create a runtime inside it, then join
tor_transport: rework Default to use block_in_place + background thread runtime to avoid nested block_on inside tokio\n\n- Always create runtime on a separate OS thread; if inside tokio, enter block_in_place first\n- Avoids 'Cannot start a runtime from within a runtime' panic
fix
more fixes
tor_transport: lazy-initialize Tor client pool on first use via ensure_pool; make Default non-blocking and remove runtime gymnastics\n\n- Introduce Inner with OnceCell<Arc<Vec<TorClient>>> and configured size\n- Default/new/with_pool_size now cheap; actual arti bootstrap happens on first request\n- request() calls ensure_pool() and uses deterministic index with pool.len()\n- Keeps deterministic endpoint/method/body affinity and DoH TXT resolution\n\nThis avoids nested-runtime/block_in_place complexity and makes Default trivial.
tor_transport: make DEFAULT_TOR_POOL_SIZE public and support custom pool sizes via TorAsync::with_pool_size() (lazy)}
remove unneeded async
add salt to keyed circuit selection
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
* token: add Token::token_secrets() and spending-condition helpers
- New helpers on Token that do not require mint keysets:
- spending_conditions()
- p2pk_pubkeys()
- p2pk_refund_pubkeys()
- htlc_hashes()
- locktimes()
- Introduce token_secrets() to unify V3/V4 proof traversal and avoid duplication
- Bypass short->long keyset-id mapping since only Secret is needed for conditions
- Use &Secret for TryFrom to fix compile error
* feat(cdk): add WebSocket authentication support with comprehensive configuration
- Add WebSocket auth token injection for client connections
- Implement server-side WebSocket authentication verification
- Add configuration options for per-endpoint WebSocket auth types
- Include comprehensive documentation and example configuration
- Support clear, blind, and no-auth modes for WebSocket endpoin