Procházet zdrojové kódy

Merge branch 'main' into go-ffi

asmo před 5 měsíci
rodič
revize
ddcd36bfb1
54 změnil soubory, kde provedl 1524 přidání a 733 odebrání
  1. 13 7
      .github/workflows/ci.yml
  2. 62 0
      .github/workflows/docker-publish-ldk-node-arm.yml
  3. 62 0
      .github/workflows/docker-publish-ldk-node.yml
  4. 41 2
      CHANGELOG.md
  5. 20 20
      Cargo.toml
  6. 1 1
      Dockerfile
  7. 1 1
      Dockerfile.arm
  8. 40 0
      Dockerfile.ldk-node
  9. 43 0
      Dockerfile.ldk-node.arm
  10. 1 1
      crates/cashu/src/nuts/nut10.rs
  11. 0 4
      crates/cdk-axum/Cargo.toml
  12. 0 4
      crates/cdk-axum/src/cache/backend/mod.rs
  13. 0 96
      crates/cdk-axum/src/cache/backend/redis.rs
  14. 0 34
      crates/cdk-axum/src/cache/config.rs
  15. 1 18
      crates/cdk-axum/src/cache/mod.rs
  16. 1 14
      crates/cdk-common/src/database/mint/mod.rs
  17. 2 0
      crates/cdk-common/src/error.rs
  18. 3 8
      crates/cdk-ffi/Cargo.toml
  19. 118 128
      crates/cdk-ffi/src/database.rs
  20. 2 2
      crates/cdk-ffi/src/error.rs
  21. 441 103
      crates/cdk-ffi/src/types.rs
  22. 2 3
      crates/cdk-ffi/src/wallet.rs
  23. 2 0
      crates/cdk-integration-tests/src/bin/start_regtest_mints.rs
  24. 3 6
      crates/cdk-integration-tests/src/init_pure_tests.rs
  25. 8 0
      crates/cdk-integration-tests/src/shared.rs
  26. 1 5
      crates/cdk-integration-tests/tests/mint.rs
  27. 0 1
      crates/cdk-mintd/Cargo.toml
  28. 47 0
      crates/cdk-mintd/README.md
  29. 20 4
      crates/cdk-mintd/example.config.toml
  30. 34 0
      crates/cdk-mintd/src/config.rs
  31. 3 0
      crates/cdk-mintd/src/env_vars/common.rs
  32. 33 1
      crates/cdk-mintd/src/env_vars/database.rs
  33. 23 0
      crates/cdk-mintd/src/env_vars/info.rs
  34. 15 0
      crates/cdk-mintd/src/env_vars/mod.rs
  35. 86 22
      crates/cdk-mintd/src/lib.rs
  36. 7 0
      crates/cdk-postgres/start_db_for_test.sh
  37. 29 5
      crates/cdk-sql-common/build.rs
  38. 0 9
      crates/cdk-sql-common/src/mint/auth/migrations.rs
  39. 7 4
      crates/cdk-sql-common/src/mint/auth/mod.rs
  40. 0 36
      crates/cdk-sql-common/src/mint/migrations.rs
  41. 1 0
      crates/cdk-sql-common/src/mint/migrations/postgres/20250916221000_drop_config_table.sql
  42. 1 0
      crates/cdk-sql-common/src/mint/migrations/sqlite/20250916221000_drop_config_table.sql
  43. 6 101
      crates/cdk-sql-common/src/mint/mod.rs
  44. 0 29
      crates/cdk-sql-common/src/wallet/migrations.rs
  45. 3 1
      crates/cdk-sql-common/src/wallet/mod.rs
  46. 12 1
      crates/cdk-sqlite/src/mint/memory.rs
  47. 33 0
      crates/cdk/src/mint/builder.rs
  48. 2 2
      crates/cdk/src/mint/issue/mod.rs
  49. 3 3
      crates/cdk/src/mint/melt.rs
  50. 119 26
      crates/cdk/src/mint/mod.rs
  51. 12 6
      crates/cdk/src/wallet/multi_mint_wallet.rs
  52. 118 0
      docker-compose.ldk-node.yaml
  53. 40 25
      docker-compose.yaml
  54. 2 0
      justfile

+ 13 - 7
.github/workflows/ci.yml

@@ -103,8 +103,7 @@ jobs:
             # HTTP/API layer - consolidated
             -p cdk-axum,
             -p cdk-axum --no-default-features,
-            -p cdk-axum --no-default-features --features redis,
-            -p cdk-axum --no-default-features --features "redis swagger",
+            -p cdk-axum --no-default-features --features swagger,
             
             # Lightning backends
             -p cdk-cln,
@@ -121,16 +120,13 @@ jobs:
             
             # FFI bindings
             -p cdk-ffi,
-            -p cdk-ffi --no-default-features,
-            -p cdk-ffi --no-default-features --features auth,
-            -p cdk-ffi --no-default-features --features bip353,
             
             # Binaries
             --bin cdk-cli,
             --bin cdk-cli --features sqlcipher,
             --bin cdk-cli --features redb,
             --bin cdk-mintd,
-            --bin cdk-mintd --features redis,
+
             --bin cdk-mintd --features sqlcipher,
             --bin cdk-mintd --no-default-features --features lnd --features sqlite,
             --bin cdk-mintd --no-default-features --features cln --features postgres,
@@ -341,7 +337,7 @@ jobs:
             -p cdk --no-default-features --features "wallet auth",
             -p cdk --no-default-features --features "http_subscription",
             -p cdk-axum,
-            -p cdk-axum --no-default-features --features redis,
+
             -p cdk-lnbits,
             -p cdk-fake-wallet,
             -p cdk-cln,
@@ -443,6 +439,16 @@ jobs:
     steps:
       - name: checkout
         uses: actions/checkout@v4
+      - name: Free Disk Space (Ubuntu)
+        uses: jlumbroso/free-disk-space@main
+        with:
+          tool-cache: false
+          android: true
+          dotnet: true
+          haskell: true
+          large-packages: true
+          docker-images: true
+          swap-storage: true
       - name: Install Nix
         uses: DeterminateSystems/nix-installer-action@v17
       - name: Nix Cache

+ 62 - 0
.github/workflows/docker-publish-ldk-node-arm.yml

@@ -0,0 +1,62 @@
+name: Publish Docker Image LDK Node ARM64
+
+on:
+  release:
+    types: [published]
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: 'Tag to build and publish'
+        required: true
+        default: 'latest'
+
+env:
+  REGISTRY: docker.io
+  IMAGE_NAME: cashubtc/mintd
+
+jobs:
+  build-and-push:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Login to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+          tags: |
+            type=raw,value=ldk-node-arm64,enable=${{ github.event_name == 'release' }}
+            type=semver,pattern={{version}}-ldk-node-arm64
+            type=semver,pattern={{major}}.{{minor}}-ldk-node-arm64
+            type=ref,event=branch,suffix=-ldk-node-arm64
+            type=ref,event=pr,suffix=-ldk-node-arm64
+            type=sha,suffix=-ldk-node-arm64
+            ${{ github.event.inputs.tag != '' && format('{0}-ldk-node-arm64', github.event.inputs.tag) || '' }}
+
+      # Build and push ARM64 image with architecture suffix
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          push: true
+          platforms: linux/arm64
+          file: ./Dockerfile.ldk-node.arm
+          tags: ${{ steps.meta.outputs.tags }}-arm64
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max

+ 62 - 0
.github/workflows/docker-publish-ldk-node.yml

@@ -0,0 +1,62 @@
+name: Publish Docker Image LDK Node AMD64
+
+on:
+  release:
+    types: [published]
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: 'Tag to build and publish'
+        required: true
+        default: 'latest'
+
+env:
+  REGISTRY: docker.io
+  IMAGE_NAME: cashubtc/mintd
+
+jobs:
+  build-and-push:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Login to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+          tags: |
+            type=raw,value=ldk-node,enable=${{ github.event_name == 'release' }}
+            type=semver,pattern={{version}}-ldk-node
+            type=semver,pattern={{major}}.{{minor}}-ldk-node
+            type=ref,event=branch,suffix=-ldk-node
+            type=ref,event=pr,suffix=-ldk-node
+            type=sha,suffix=-ldk-node
+            ${{ github.event.inputs.tag != '' && format('{0}-ldk-node', github.event.inputs.tag) || '' }}
+
+      # Build and push AMD64 image with architecture suffix
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          file: ./Dockerfile.ldk-node
+          push: true
+          platforms: linux/amd64
+          tags: ${{ steps.meta.outputs.tags }}-amd64
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max

+ 41 - 2
CHANGELOG.md

@@ -4,7 +4,11 @@
 <!-- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -->
 <!-- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -->
 
-## [Unreleased]
+## [0.13.0](https://github.com/cashubtc/cdk/releases/tag/v0.13.0)
+
+### Summary
+
+Version 0.13.0 marks a major milestone for mobile development with the introduction of comprehensive native mobile bindings that enable building Cashu wallets for iOS and Android using Swift and Kotlin. The release introduces cdk-ffi, a new Foreign Function Interface crate that provides UniFFI-based bindings for Swift, Kotlin, and Python, with full wallet functionality including multi-mint support, BOLT12 payments, BIP-353 address resolution, and advanced features like P2PK conditions and authentication. Mobile bindings are distributed through dedicated repositories at https://github.com/cashubtc/cdk-kotlin and https://github.com/cashubtc/cdk-swift that provide native package management for Android/JVM and iOS/macOS platforms respectively. The release also delivers significant infrastructure improvements including an event-driven payment architecture with real-time notifications, enhanced database layer with generic key-value storage, improved HTTP transport with proxy support and BIP-353 DNS resolution, and new operational features like Prometheus metrics collection and dedicated authentication database support.
 
 ### Added
 - cdk-common: New `Event` enum for payment event handling with `PaymentReceived` variant ([thesimplekid]).
@@ -18,6 +22,17 @@
 - cdk-sql-common: Implementation of `MintKVStoreDatabase` trait for SQL-based databases with namespace support ([thesimplekid]).
 - cdk-common: Added `quote_id` field to `Transaction` struct for tracking associated mint or melt quote IDs ([thesimplekid]).
 - cdk-sql-common: Database migration to add `quote_id` column to transactions table for SQLite and PostgreSQL ([thesimplekid]).
+- cdk: Added `amount_mintable()` helper and stricter mint quote validation ([thesimplekid]).
+- cdk-sql-common: Added persistent `melt_request` storage with associated blinded messages; new migrations for SQLite and PostgreSQL ([thesimplekid]).
+- cdk-cln: Persist last `pay_index` in the mint KV store to avoid missed events across restarts ([thesimplekid]).
+- cdk: HTTP subscriptions emit BOLT12 notifications per NUT-17 ([crodas]).
+- cdk: DNS TXT resolution in HttpTransport and MintConnector for BIP‑353 lookups ([crodas]).
+- cdk-ffi: Wallet FFI bindings and async constructors ([davidcaseria]).
+- cdk-prometheus: New metrics crate and optional embedded Prometheus server; integrated metrics across HTTP, database, payments, and mint (feature: `prometheus`) ([asmo]).
+- cdk-mintd: Optional Prometheus metrics server with configurable address/port via config and env vars (feature: `prometheus`) ([thesimplekid]).
+- cdk-ldk-node: Web UI improvements (dynamic status, navigation, mobile support) ([erik]).
+- cdk-postgres: Dedicated auth database support with separate schema and migrations when auth is enabled ([thesimplekid]/[asmo]).
+
 
 ### Changed
 - cdk-common: Refactored `MintPayment` trait method `wait_any_incoming_payment` to `wait_payment_event` with event-driven architecture ([thesimplekid]).
@@ -25,9 +40,33 @@
 - cdk: Updated mint payment handling to process payment events through new `Event` enum pattern ([thesimplekid]).
 - cashu: Updated BOLT12 payment method specification from NUT-24 to NUT-25 ([thesimplekid]).
 - cdk: Updated BOLT12 import references from nut24 to nut25 module ([thesimplekid]).
+- cdk: Do not fallback from WebSocket to HTTP on first error in subscriptions; retry only when appropriate ([crodas]).
+- cdk: Abstracted HTTP Transport; centralized `pay_request` logic into the cdk library ([lollerfirst]).
+- cdk: MultiMintWallet refactor for clearer APIs and behavior when managing multiple mints ([davidcaseria]/[thesimplekid]).
+- cdk: Treat `None` proxy host matcher as "apply to all hosts" for HTTP transport ([lollerfirst]).
+- cdk: QuoteId handling unified as string in APIs and storage ([thesimplekid]).
+- cdk-axum: Close WebSocket connections sooner on shutdown/errors to avoid dangling clients ([crodas]).
+- cdk-sql-common: Consistent ordering for SQL migrations and build uses `OUT_DIR` for embedded migration files ([vnprc]).
+- cdk-signatory: Updated protobuf to latest spec and added signatory-related DB migrations ([crodas]).
+- cdk-redb: Bumped dependency version ([thesimplekid]).
 
-### Fixied
+### Fixed
 - cdk: Wallet melt track and use payment method from quote for BOLT11/BOLT12 routing ([thesimplekid]).
+- cdk: Improve error response details and mapping across HTTP/WS paths ([thesimplekid]).
+- cdk: Fix config being overwritten on startup in certain scenarios ([thesimplekid]).
+- cdk: WASM compatibility fixes for HTTP subscriptions and time handling (use `instant`) ([gudnuf]).
+- cdk-postgres: Fix reconnection in connection pool and migration prefixes.
+- cdk: Check keyset max order when generating/using keysets ([thesimplekid]).
+- cdk: Correct error code returned for duplicate signature conditions ([lollerfirst]).
+- cdk: Ensure all mint quotes are returned in listings ([thesimplekid]).
+- cdk-axum: Improve error response detail structure ([thesimplekid]).
+
+
+## [0.12.1](https://github.com/cashubtc/cdk/releases/tag/v0.12.1)
+
+### Fixed
+- cdk-postgres: TLS support for PostgreSQL connections ([asmogo]).
+- cdk: patch sha-512 derivation -> sha-256 derivation ([lollerfirst]).
 
 ## [0.12.0](https://github.com/cashubtc/cdk/releases/tag/v0.12.0)
 

+ 20 - 20
Cargo.toml

@@ -33,7 +33,7 @@ rust-version = "1.85.0"
 license = "MIT"
 homepage = "https://github.com/cashubtc/cdk"
 repository = "https://github.com/cashubtc/cdk.git"
-version = "0.12.0"
+version = "0.13.0"
 readme = "README.md"
 
 [workspace.dependencies]
@@ -43,25 +43,25 @@ axum = { version = "0.8.1", features = ["ws"] }
 bitcoin = { version = "0.32.2", features = ["base64", "serde", "rand", "rand-std"] }
 bip39 = { version = "2.0", features = ["rand"] }
 jsonwebtoken = "9.2.0"
-cashu = { path = "./crates/cashu", version = "=0.12.0" }
-cdk = { path = "./crates/cdk", default-features = false, version = "=0.12.0" }
-cdk-common = { path = "./crates/cdk-common", default-features = false, version = "=0.12.0" }
-cdk-axum = { path = "./crates/cdk-axum", default-features = false, version = "=0.12.0" }
-cdk-cln = { path = "./crates/cdk-cln", version = "=0.12.0" }
-cdk-lnbits = { path = "./crates/cdk-lnbits", version = "=0.12.0" }
-cdk-lnd = { path = "./crates/cdk-lnd", version = "=0.12.0" }
-cdk-ldk-node = { path = "./crates/cdk-ldk-node", version = "=0.12.0" }
-cdk-fake-wallet = { path = "./crates/cdk-fake-wallet", version = "=0.12.0" }
-cdk-ffi = { path = "./crates/cdk-ffi", version = "=0.12.0" }
-cdk-payment-processor = { path = "./crates/cdk-payment-processor", default-features = true, version = "=0.12.0" }
-cdk-mint-rpc = { path = "./crates/cdk-mint-rpc", version = "=0.12.0" }
-cdk-redb = { path = "./crates/cdk-redb", default-features = true, version = "=0.12.0" }
-cdk-sql-common = { path = "./crates/cdk-sql-common", default-features = true, version = "=0.12.0" }
-cdk-sqlite = { path = "./crates/cdk-sqlite", default-features = true, version = "=0.12.0" }
-cdk-postgres = { path = "./crates/cdk-postgres", default-features = true, version = "=0.12.0" }
-cdk-signatory = { path = "./crates/cdk-signatory", version = "=0.12.0", default-features = false }
-cdk-mintd = { path = "./crates/cdk-mintd", version = "=0.12.0", default-features = false }
-cdk-prometheus = { path = "./crates/cdk-prometheus", version = "=0.12.0", default-features = false }
+cashu = { path = "./crates/cashu", version = "=0.13.0" }
+cdk = { path = "./crates/cdk", default-features = false, version = "=0.13.0" }
+cdk-common = { path = "./crates/cdk-common", default-features = false, version = "=0.13.0" }
+cdk-axum = { path = "./crates/cdk-axum", default-features = false, version = "=0.13.0" }
+cdk-cln = { path = "./crates/cdk-cln", version = "=0.13.0" }
+cdk-lnbits = { path = "./crates/cdk-lnbits", version = "=0.13.0" }
+cdk-lnd = { path = "./crates/cdk-lnd", version = "=0.13.0" }
+cdk-ldk-node = { path = "./crates/cdk-ldk-node", version = "=0.13.0" }
+cdk-fake-wallet = { path = "./crates/cdk-fake-wallet", version = "=0.13.0" }
+cdk-ffi = { path = "./crates/cdk-ffi", version = "=0.13.0" }
+cdk-payment-processor = { path = "./crates/cdk-payment-processor", default-features = true, version = "=0.13.0" }
+cdk-mint-rpc = { path = "./crates/cdk-mint-rpc", version = "=0.13.0" }
+cdk-redb = { path = "./crates/cdk-redb", default-features = true, version = "=0.13.0" }
+cdk-sql-common = { path = "./crates/cdk-sql-common", default-features = true, version = "=0.13.0" }
+cdk-sqlite = { path = "./crates/cdk-sqlite", default-features = true, version = "=0.13.0" }
+cdk-postgres = { path = "./crates/cdk-postgres", default-features = true, version = "=0.13.0" }
+cdk-signatory = { path = "./crates/cdk-signatory", version = "=0.13.0", default-features = false }
+cdk-mintd = { path = "./crates/cdk-mintd", version = "=0.13.0", default-features = false }
+cdk-prometheus = { path = "./crates/cdk-prometheus", version = "=0.13.0", default-features = false }
 clap = { version = "4.5.31", features = ["derive"] }
 ciborium = { version = "0.2.2", default-features = false, features = ["std"] }
 cbor-diag = "0.1.12"

+ 1 - 1
Dockerfile

@@ -10,7 +10,7 @@ COPY Cargo.toml ./Cargo.toml
 COPY crates ./crates
 
 # Start the Nix daemon and develop the environment
-RUN nix develop --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features redis --features prometheus
+RUN nix develop --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features postgres --features prometheus
 
 # Create a runtime stage
 FROM debian:trixie-slim

+ 1 - 1
Dockerfile.arm

@@ -13,7 +13,7 @@ COPY crates ./crates
 RUN echo 'filter-syscalls = false' > /etc/nix/nix.conf
 
 # Start the Nix daemon and develop the environment
-RUN nix develop --extra-platforms aarch64-linux --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features redis
+RUN nix develop --extra-platforms aarch64-linux --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features postgres
 
 # Create a runtime stage
 FROM debian:trixie-slim

+ 40 - 0
Dockerfile.ldk-node

@@ -0,0 +1,40 @@
+# Use the official NixOS image as the base image
+FROM nixos/nix:latest AS builder
+
+# Set the working directory
+WORKDIR /usr/src/app
+
+# Copy workspace files and crates directory into the container
+COPY flake.nix ./flake.nix
+COPY Cargo.toml ./Cargo.toml
+COPY crates ./crates
+
+# Start the Nix daemon and develop the environment
+RUN nix develop --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features ldk-node --features prometheus --features postgres
+
+# Create a runtime stage
+FROM debian:trixie-slim
+
+# Set the working directory
+WORKDIR /usr/src/app
+
+# Install needed runtime dependencies (if any)
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends patchelf && \
+    rm -rf /var/lib/apt/lists/*
+
+# Copy the built application from the build stage
+COPY --from=builder /usr/src/app/target/release/cdk-mintd /usr/local/bin/cdk-mintd
+
+# Detect the architecture and set the interpreter accordingly
+RUN ARCH=$(uname -m) && \
+    if [ "$ARCH" = "aarch64" ]; then \
+        patchelf --set-interpreter /lib/ld-linux-aarch64.so.1 /usr/local/bin/cdk-mintd; \
+    elif [ "$ARCH" = "x86_64" ]; then \
+        patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 /usr/local/bin/cdk-mintd; \
+    else \
+        echo "Unsupported architecture: $ARCH"; exit 1; \
+    fi
+
+# Set the entry point for the container
+CMD ["cdk-mintd"]

+ 43 - 0
Dockerfile.ldk-node.arm

@@ -0,0 +1,43 @@
+# Use the official NixOS image as the base image
+FROM nixos/nix:latest AS builder
+
+# Set the working directory
+WORKDIR /usr/src/app
+
+# Copy workspace files and crates directory into the container
+COPY flake.nix ./flake.nix
+COPY Cargo.toml ./Cargo.toml
+COPY crates ./crates
+
+# Create a nix config file to disable syscall filtering
+RUN echo 'filter-syscalls = false' > /etc/nix/nix.conf
+
+# Start the Nix daemon and develop the environment
+RUN nix develop --extra-platforms aarch64-linux --extra-experimental-features nix-command --extra-experimental-features flakes --command cargo build --release --bin cdk-mintd --features ldk-node --features prometheus --features postgres
+
+# Create a runtime stage
+FROM debian:trixie-slim
+
+# Set the working directory
+WORKDIR /usr/src/app
+
+# Install needed runtime dependencies (if any)
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends patchelf && \
+    rm -rf /var/lib/apt/lists/*
+
+# Copy the built application from the build stage
+COPY --from=builder /usr/src/app/target/release/cdk-mintd /usr/local/bin/cdk-mintd
+
+# Detect the architecture and set the interpreter accordingly
+RUN ARCH=$(uname -m) && \
+    if [ "$ARCH" = "aarch64" ]; then \
+        patchelf --set-interpreter /lib/ld-linux-aarch64.so.1 /usr/local/bin/cdk-mintd; \
+    elif [ "$ARCH" = "x86_64" ]; then \
+        patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 /usr/local/bin/cdk-mintd; \
+    else \
+        echo "Unsupported architecture: $ARCH"; exit 1; \
+    fi
+
+# Set the entry point for the container
+CMD ["cdk-mintd"]

+ 1 - 1
crates/cashu/src/nuts/nut10.rs

@@ -30,7 +30,7 @@ pub enum Kind {
     HTLC,
 }
 
-/// Secert Date
+/// Secret Date
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct SecretData {
     /// Unique random string

+ 0 - 4
crates/cdk-axum/Cargo.toml

@@ -12,7 +12,6 @@ readme = "README.md"
 
 [features]
 default = ["auth"]
-redis = ["dep:redis"]
 swagger = ["cdk/swagger", "dep:utoipa"]
 auth = ["cdk/auth"]
 prometheus = ["dep:cdk-prometheus"]
@@ -34,9 +33,6 @@ paste = "1.0.15"
 serde.workspace = true
 uuid.workspace = true
 sha2 = "0.10.8"
-redis = { version = "0.31.0", features = [
-    "tokio-rustls-comp",
-], optional = true }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 uuid = { workspace = true, features = ["js"] }

+ 0 - 4
crates/cdk-axum/src/cache/backend/mod.rs

@@ -1,7 +1,3 @@
 mod memory;
-#[cfg(feature = "redis")]
-mod redis;
 
 pub use self::memory::InMemoryHttpCache;
-#[cfg(feature = "redis")]
-pub use self::redis::{Config as RedisConfig, HttpCacheRedis};

+ 0 - 96
crates/cdk-axum/src/cache/backend/redis.rs

@@ -1,96 +0,0 @@
-use std::time::Duration;
-
-use redis::AsyncCommands;
-use serde::{Deserialize, Serialize};
-
-use crate::cache::{HttpCacheKey, HttpCacheStorage};
-
-/// Redis cache storage for the HTTP cache.
-///
-/// This cache storage backend uses Redis to store the cache.
-pub struct HttpCacheRedis {
-    cache_ttl: Duration,
-    prefix: Option<Vec<u8>>,
-    client: redis::Client,
-}
-
-/// Configuration for the Redis cache storage.
-#[derive(Debug, Clone, Serialize, Deserialize, Default)]
-pub struct Config {
-    /// Commong key prefix
-    pub key_prefix: Option<String>,
-
-    /// Connection string to the Redis server.
-    pub connection_string: String,
-}
-
-impl HttpCacheRedis {
-    /// Create a new Redis cache.
-    pub fn new(client: redis::Client) -> Self {
-        Self {
-            client,
-            prefix: None,
-            cache_ttl: Duration::from_secs(60),
-        }
-    }
-
-    /// Set a prefix for the cache keys.
-    ///
-    /// This is useful to have all the HTTP cache keys under a common prefix,
-    /// some sort of namespace, to make management of the database easier.
-    pub fn set_prefix(mut self, prefix: Vec<u8>) -> Self {
-        self.prefix = Some(prefix);
-        self
-    }
-}
-
-#[async_trait::async_trait]
-impl HttpCacheStorage for HttpCacheRedis {
-    fn set_expiration_times(&mut self, cache_ttl: Duration, _cache_tti: Duration) {
-        self.cache_ttl = cache_ttl;
-    }
-
-    async fn get(&self, key: &HttpCacheKey) -> Option<Vec<u8>> {
-        let mut conn = self
-            .client
-            .get_multiplexed_tokio_connection()
-            .await
-            .map_err(|err| {
-                tracing::error!("Failed to get redis connection: {:?}", err);
-                err
-            })
-            .ok()?;
-
-        let mut db_key = self.prefix.clone().unwrap_or_default();
-        db_key.extend(&**key);
-
-        conn.get(db_key)
-            .await
-            .map_err(|err| {
-                tracing::error!("Failed to get value from redis: {:?}", err);
-                err
-            })
-            .ok()?
-    }
-
-    async fn set(&self, key: HttpCacheKey, value: Vec<u8>) {
-        let mut db_key = self.prefix.clone().unwrap_or_default();
-        db_key.extend(&*key);
-
-        let mut conn = match self.client.get_multiplexed_tokio_connection().await {
-            Ok(conn) => conn,
-            Err(err) => {
-                tracing::error!("Failed to get redis connection: {:?}", err);
-                return;
-            }
-        };
-
-        let _: Result<(), _> = conn
-            .set_ex(db_key, value, self.cache_ttl.as_secs())
-            .await
-            .map_err(|err| {
-                tracing::error!("Failed to set value in redis: {:?}", err);
-                err
-            });
-    }
-}

+ 0 - 34
crates/cdk-axum/src/cache/config.rs

@@ -2,11 +2,6 @@ use serde::{Deserialize, Serialize};
 
 pub const ENV_CDK_MINTD_CACHE_BACKEND: &str = "CDK_MINTD_CACHE_BACKEND";
 
-#[cfg(feature = "redis")]
-pub const ENV_CDK_MINTD_CACHE_REDIS_URL: &str = "CDK_MINTD_CACHE_REDIS_URL";
-#[cfg(feature = "redis")]
-pub const ENV_CDK_MINTD_CACHE_REDIS_KEY_PREFIX: &str = "CDK_MINTD_CACHE_REDIS_KEY_PREFIX";
-
 pub const ENV_CDK_MINTD_CACHE_TTI: &str = "CDK_MINTD_CACHE_TTI";
 pub const ENV_CDK_MINTD_CACHE_TTL: &str = "CDK_MINTD_CACHE_TTL";
 
@@ -16,27 +11,12 @@ pub const ENV_CDK_MINTD_CACHE_TTL: &str = "CDK_MINTD_CACHE_TTL";
 pub enum Backend {
     #[default]
     Memory,
-    #[cfg(feature = "redis")]
-    Redis(super::backend::RedisConfig),
 }
 
 impl Backend {
     pub fn from_env_str(backend_str: &str) -> Option<Self> {
         match backend_str.to_lowercase().as_str() {
             "memory" => Some(Self::Memory),
-            #[cfg(feature = "redis")]
-            "redis" => {
-                // Get Redis configuration from environment
-                let connection_string = std::env::var(ENV_CDK_MINTD_CACHE_REDIS_URL)
-                    .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
-
-                let key_prefix = std::env::var(ENV_CDK_MINTD_CACHE_REDIS_KEY_PREFIX).ok();
-
-                Some(Self::Redis(super::backend::RedisConfig {
-                    connection_string,
-                    key_prefix,
-                }))
-            }
             _ => None,
         }
     }
@@ -66,20 +46,6 @@ impl Config {
         if let Ok(backend_str) = env::var(ENV_CDK_MINTD_CACHE_BACKEND) {
             if let Some(backend) = Backend::from_env_str(&backend_str) {
                 self.backend = backend;
-
-                // If Redis backend is selected, parse Redis configuration
-                #[cfg(feature = "redis")]
-                if matches!(self.backend, Backend::Redis(_)) {
-                    let connection_string = env::var(ENV_CDK_MINTD_CACHE_REDIS_URL)
-                        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
-
-                    let key_prefix = env::var(ENV_CDK_MINTD_CACHE_REDIS_KEY_PREFIX).ok();
-
-                    self.backend = Backend::Redis(super::backend::RedisConfig {
-                        connection_string,
-                        key_prefix,
-                    });
-                }
             }
         }
 

+ 1 - 18
crates/cdk-axum/src/cache/mod.rs

@@ -7,7 +7,7 @@
 //! idempotent operations.
 //!
 //! This mod also provides common backend implementations as well, such as In
-//! Memory (default) and Redis.
+//! Memory (default).
 use std::ops::Deref;
 use std::sync::Arc;
 use std::time::Duration;
@@ -89,23 +89,6 @@ impl From<config::Config> for HttpCache {
                 Duration::from_secs(config.tti.unwrap_or(DEFAULT_TTI_SECS)),
                 None,
             ),
-            #[cfg(feature = "redis")]
-            config::Backend::Redis(redis_config) => {
-                let client = redis::Client::open(redis_config.connection_string)
-                    .expect("Failed to create Redis client");
-                let storage = HttpCacheRedis::new(client).set_prefix(
-                    redis_config
-                        .key_prefix
-                        .unwrap_or_default()
-                        .as_bytes()
-                        .to_vec(),
-                );
-                Self::new(
-                    Duration::from_secs(config.ttl.unwrap_or(DEFAULT_TTL_SECS)),
-                    Duration::from_secs(config.tti.unwrap_or(DEFAULT_TTI_SECS)),
-                    Some(Box::new(storage)),
-                )
-            }
         }
     }
 }

+ 1 - 14
crates/cdk-common/src/database/mint/mod.rs

@@ -4,10 +4,9 @@ use std::collections::HashMap;
 
 use async_trait::async_trait;
 use cashu::quote_id::QuoteId;
-use cashu::{Amount, MintInfo};
+use cashu::Amount;
 
 use super::Error;
-use crate::common::QuoteTTL;
 use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote};
 use crate::nuts::{
     BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
@@ -396,7 +395,6 @@ pub trait KVStoreTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
 }
 
 /// Base database writer
-#[async_trait]
 pub trait Transaction<'a, Error>:
     DbTransactionFinalizer<Err = Error>
     + QuotesTransaction<'a, Err = Error>
@@ -404,11 +402,6 @@ pub trait Transaction<'a, Error>:
     + ProofsTransaction<'a, Err = Error>
     + KVStoreTransaction<'a, Error>
 {
-    /// Set [`QuoteTTL`]
-    async fn set_quote_ttl(&mut self, quote_ttl: QuoteTTL) -> Result<(), Error>;
-
-    /// Set [`MintInfo`]
-    async fn set_mint_info(&mut self, mint_info: MintInfo) -> Result<(), Error>;
 }
 
 /// Key-Value Store Database trait
@@ -457,12 +450,6 @@ pub trait Database<Error>:
     async fn begin_transaction<'a>(
         &'a self,
     ) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
-
-    /// Get [`MintInfo`]
-    async fn get_mint_info(&self) -> Result<MintInfo, Error>;
-
-    /// Get [`QuoteTTL`]
-    async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error>;
 }
 
 /// Type alias for Mint Database

+ 2 - 0
crates/cdk-common/src/error.rs

@@ -388,9 +388,11 @@ pub enum Error {
     NUT20(#[from] crate::nuts::nut20::Error),
     /// NUT21 Error
     #[error(transparent)]
+    #[cfg(feature = "auth")]
     NUT21(#[from] crate::nuts::nut21::Error),
     /// NUT22 Error
     #[error(transparent)]
+    #[cfg(feature = "auth")]
     NUT22(#[from] crate::nuts::nut22::Error),
     /// NUT23 Error
     #[error(transparent)]

+ 3 - 8
crates/cdk-ffi/Cargo.toml

@@ -5,22 +5,17 @@ edition.workspace = true
 license.workspace = true
 repository.workspace = true
 rust-version.workspace = true
+description = "FFI bindings for cdk wallet"
+homepage = "https://github.com/cashubtc/cdk"
 
 [lib]
 crate-type = ["cdylib", "staticlib", "rlib"]
 name = "cdk_ffi"
 
-[features]
-default = ["auth", "bip353"]
-auth = ["cdk/auth", "cdk-common/auth", "cashu/auth"]
-bip353 = ["cdk/bip353"]
-
 [dependencies]
 async-trait = { workspace = true }
 bip39 = { workspace = true }
-cashu = { workspace = true }
-cdk = { workspace = true, default-features = false, features = ["wallet"] }
-cdk-common = { workspace = true }
+cdk = { workspace = true, default-features = false, features = ["wallet", "auth", "bip353"] }
 cdk-sqlite = { workspace = true }
 ctor = "0.2"
 futures = { workspace = true }

+ 118 - 128
crates/cdk-ffi/src/database.rs

@@ -3,7 +3,7 @@
 use std::collections::HashMap;
 use std::sync::Arc;
 
-use cdk_common::database::WalletDatabase as CdkWalletDatabase;
+use cdk::cdk_database::WalletDatabase as CdkWalletDatabase;
 use cdk_sqlite::wallet::WalletSqliteDatabase as CdkWalletSqliteDatabase;
 
 use crate::error::FfiError;
@@ -161,13 +161,13 @@ impl std::fmt::Debug for WalletDatabaseBridge {
 
 #[async_trait::async_trait]
 impl CdkWalletDatabase for WalletDatabaseBridge {
-    type Err = cdk_common::database::Error;
+    type Err = cdk::cdk_database::Error;
 
     // Mint Management
     async fn add_mint(
         &self,
-        mint_url: cdk_common::mint_url::MintUrl,
-        mint_info: Option<cdk_common::nuts::MintInfo>,
+        mint_url: cdk::mint_url::MintUrl,
+        mint_info: Option<cdk::nuts::MintInfo>,
     ) -> Result<(), Self::Err> {
         let ffi_mint_url = mint_url.into();
         let ffi_mint_info = mint_info.map(Into::into);
@@ -175,45 +175,44 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
         self.ffi_db
             .add_mint(ffi_mint_url, ffi_mint_info)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
-    async fn remove_mint(&self, mint_url: cdk_common::mint_url::MintUrl) -> Result<(), Self::Err> {
+    async fn remove_mint(&self, mint_url: cdk::mint_url::MintUrl) -> Result<(), Self::Err> {
         let ffi_mint_url = mint_url.into();
         self.ffi_db
             .remove_mint(ffi_mint_url)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn get_mint(
         &self,
-        mint_url: cdk_common::mint_url::MintUrl,
-    ) -> Result<Option<cdk_common::nuts::MintInfo>, Self::Err> {
+        mint_url: cdk::mint_url::MintUrl,
+    ) -> Result<Option<cdk::nuts::MintInfo>, Self::Err> {
         let ffi_mint_url = mint_url.into();
         let result = self
             .ffi_db
             .get_mint(ffi_mint_url)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result.map(Into::into))
     }
 
     async fn get_mints(
         &self,
-    ) -> Result<HashMap<cdk_common::mint_url::MintUrl, Option<cdk_common::nuts::MintInfo>>, Self::Err>
-    {
+    ) -> Result<HashMap<cdk::mint_url::MintUrl, Option<cdk::nuts::MintInfo>>, Self::Err> {
         let result = self
             .ffi_db
             .get_mints()
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
 
         let mut cdk_result = HashMap::new();
         for (ffi_mint_url, mint_info_opt) in result {
-            let cdk_url = ffi_mint_url.try_into().map_err(|e: FfiError| {
-                cdk_common::database::Error::Database(e.to_string().into())
-            })?;
+            let cdk_url = ffi_mint_url
+                .try_into()
+                .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))?;
             cdk_result.insert(cdk_url, mint_info_opt.map(Into::into));
         }
         Ok(cdk_result)
@@ -221,22 +220,22 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
 
     async fn update_mint_url(
         &self,
-        old_mint_url: cdk_common::mint_url::MintUrl,
-        new_mint_url: cdk_common::mint_url::MintUrl,
+        old_mint_url: cdk::mint_url::MintUrl,
+        new_mint_url: cdk::mint_url::MintUrl,
     ) -> Result<(), Self::Err> {
         let ffi_old_mint_url = old_mint_url.into();
         let ffi_new_mint_url = new_mint_url.into();
         self.ffi_db
             .update_mint_url(ffi_old_mint_url, ffi_new_mint_url)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     // Keyset Management
     async fn add_mint_keysets(
         &self,
-        mint_url: cdk_common::mint_url::MintUrl,
-        keysets: Vec<cdk_common::nuts::KeySetInfo>,
+        mint_url: cdk::mint_url::MintUrl,
+        keysets: Vec<cdk::nuts::KeySetInfo>,
     ) -> Result<(), Self::Err> {
         let ffi_mint_url = mint_url.into();
         let ffi_keysets: Vec<KeySetInfo> = keysets.into_iter().map(Into::into).collect();
@@ -244,74 +243,72 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
         self.ffi_db
             .add_mint_keysets(ffi_mint_url, ffi_keysets)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn get_mint_keysets(
         &self,
-        mint_url: cdk_common::mint_url::MintUrl,
-    ) -> Result<Option<Vec<cdk_common::nuts::KeySetInfo>>, Self::Err> {
+        mint_url: cdk::mint_url::MintUrl,
+    ) -> Result<Option<Vec<cdk::nuts::KeySetInfo>>, Self::Err> {
         let ffi_mint_url = mint_url.into();
         let result = self
             .ffi_db
             .get_mint_keysets(ffi_mint_url)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result.map(|keysets| keysets.into_iter().map(Into::into).collect()))
     }
 
     async fn get_keyset_by_id(
         &self,
-        keyset_id: &cdk_common::nuts::Id,
-    ) -> Result<Option<cdk_common::nuts::KeySetInfo>, Self::Err> {
+        keyset_id: &cdk::nuts::Id,
+    ) -> Result<Option<cdk::nuts::KeySetInfo>, Self::Err> {
         let ffi_id = (*keyset_id).into();
         let result = self
             .ffi_db
             .get_keyset_by_id(ffi_id)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result.map(Into::into))
     }
 
     // Mint Quote Management
-    async fn add_mint_quote(&self, quote: cdk_common::wallet::MintQuote) -> Result<(), Self::Err> {
+    async fn add_mint_quote(&self, quote: cdk::wallet::MintQuote) -> Result<(), Self::Err> {
         let ffi_quote = quote.into();
         self.ffi_db
             .add_mint_quote(ffi_quote)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn get_mint_quote(
         &self,
         quote_id: &str,
-    ) -> Result<Option<cdk_common::wallet::MintQuote>, Self::Err> {
+    ) -> Result<Option<cdk::wallet::MintQuote>, Self::Err> {
         let result = self
             .ffi_db
             .get_mint_quote(quote_id.to_string())
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result
             .map(|q| {
-                q.try_into().map_err(|e: FfiError| {
-                    cdk_common::database::Error::Database(e.to_string().into())
-                })
+                q.try_into()
+                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
             })
             .transpose()?)
     }
 
-    async fn get_mint_quotes(&self) -> Result<Vec<cdk_common::wallet::MintQuote>, Self::Err> {
+    async fn get_mint_quotes(&self) -> Result<Vec<cdk::wallet::MintQuote>, Self::Err> {
         let result = self
             .ffi_db
             .get_mint_quotes()
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result
             .into_iter()
             .map(|q| {
-                q.try_into().map_err(|e: FfiError| {
-                    cdk_common::database::Error::Database(e.to_string().into())
-                })
+                q.try_into()
+                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
             })
             .collect::<Result<Vec<_>, _>>()?)
     }
@@ -320,48 +317,46 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
         self.ffi_db
             .remove_mint_quote(quote_id.to_string())
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     // Melt Quote Management
-    async fn add_melt_quote(&self, quote: cdk_common::wallet::MeltQuote) -> Result<(), Self::Err> {
+    async fn add_melt_quote(&self, quote: cdk::wallet::MeltQuote) -> Result<(), Self::Err> {
         let ffi_quote = quote.into();
         self.ffi_db
             .add_melt_quote(ffi_quote)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn get_melt_quote(
         &self,
         quote_id: &str,
-    ) -> Result<Option<cdk_common::wallet::MeltQuote>, Self::Err> {
+    ) -> Result<Option<cdk::wallet::MeltQuote>, Self::Err> {
         let result = self
             .ffi_db
             .get_melt_quote(quote_id.to_string())
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result
             .map(|q| {
-                q.try_into().map_err(|e: FfiError| {
-                    cdk_common::database::Error::Database(e.to_string().into())
-                })
+                q.try_into()
+                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
             })
             .transpose()?)
     }
 
-    async fn get_melt_quotes(&self) -> Result<Vec<cdk_common::wallet::MeltQuote>, Self::Err> {
+    async fn get_melt_quotes(&self) -> Result<Vec<cdk::wallet::MeltQuote>, Self::Err> {
         let result = self
             .ffi_db
             .get_melt_quotes()
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
         Ok(result
             .into_iter()
             .map(|q| {
-                q.try_into().map_err(|e: FfiError| {
-                    cdk_common::database::Error::Database(e.to_string().into())
-                })
+                q.try_into()
+                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
             })
             .collect::<Result<Vec<_>, _>>()?)
     }
@@ -370,52 +365,49 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
         self.ffi_db
             .remove_melt_quote(quote_id.to_string())
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     // Keys Management
-    async fn add_keys(&self, keyset: cashu::KeySet) -> Result<(), Self::Err> {
+    async fn add_keys(&self, keyset: cdk::nuts::KeySet) -> Result<(), Self::Err> {
         let ffi_keyset: KeySet = keyset.into();
         self.ffi_db
             .add_keys(ffi_keyset)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
-    async fn get_keys(
-        &self,
-        id: &cdk_common::nuts::Id,
-    ) -> Result<Option<cdk_common::nuts::Keys>, Self::Err> {
+    async fn get_keys(&self, id: &cdk::nuts::Id) -> Result<Option<cdk::nuts::Keys>, Self::Err> {
         let ffi_id: Id = (*id).into();
         let result = self
             .ffi_db
             .get_keys(ffi_id)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
 
         // Convert FFI Keys back to CDK Keys using TryFrom
         result
             .map(|ffi_keys| {
-                ffi_keys.try_into().map_err(|e: FfiError| {
-                    cdk_common::database::Error::Database(e.to_string().into())
-                })
+                ffi_keys
+                    .try_into()
+                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
             })
             .transpose()
     }
 
-    async fn remove_keys(&self, id: &cdk_common::nuts::Id) -> Result<(), Self::Err> {
+    async fn remove_keys(&self, id: &cdk::nuts::Id) -> Result<(), Self::Err> {
         let ffi_id = (*id).into();
         self.ffi_db
             .remove_keys(ffi_id)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     // Proof Management
     async fn update_proofs(
         &self,
-        added: Vec<cdk_common::common::ProofInfo>,
-        removed_ys: Vec<cdk_common::nuts::PublicKey>,
+        added: Vec<cdk::types::ProofInfo>,
+        removed_ys: Vec<cdk::nuts::PublicKey>,
     ) -> Result<(), Self::Err> {
         let ffi_added: Vec<ProofInfo> = added.into_iter().map(Into::into).collect();
         let ffi_removed_ys: Vec<PublicKey> = removed_ys.into_iter().map(Into::into).collect();
@@ -423,16 +415,16 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
         self.ffi_db
             .update_proofs(ffi_added, ffi_removed_ys)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn get_proofs(
         &self,
-        mint_url: Option<cdk_common::mint_url::MintUrl>,
-        unit: Option<cdk_common::nuts::CurrencyUnit>,
-        state: Option<Vec<cdk_common::nuts::State>>,
-        spending_conditions: Option<Vec<cdk_common::nuts::SpendingConditions>>,
-    ) -> Result<Vec<cdk_common::common::ProofInfo>, Self::Err> {
+        mint_url: Option<cdk::mint_url::MintUrl>,
+        unit: Option<cdk::nuts::CurrencyUnit>,
+        state: Option<Vec<cdk::nuts::State>>,
+        spending_conditions: Option<Vec<cdk::nuts::SpendingConditions>>,
+    ) -> Result<Vec<cdk::types::ProofInfo>, Self::Err> {
         let ffi_mint_url = mint_url.map(Into::into);
         let ffi_unit = unit.map(Into::into);
         let ffi_state = state.map(|s| s.into_iter().map(Into::into).collect());
@@ -443,41 +435,40 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
             .ffi_db
             .get_proofs(ffi_mint_url, ffi_unit, ffi_state, ffi_spending_conditions)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
 
         // Convert back to CDK ProofInfo
-        let cdk_result: Result<Vec<cdk_common::common::ProofInfo>, cdk_common::database::Error> =
-            result
-                .into_iter()
-                .map(|info| {
-                    Ok(cdk_common::common::ProofInfo {
-                        proof: info.proof.inner.clone(),
-                        y: info.y.try_into().map_err(|e: FfiError| {
-                            cdk_common::database::Error::Database(e.to_string().into())
-                        })?,
-                        mint_url: info.mint_url.try_into().map_err(|e: FfiError| {
-                            cdk_common::database::Error::Database(e.to_string().into())
+        let cdk_result: Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> = result
+            .into_iter()
+            .map(|info| {
+                Ok(cdk::types::ProofInfo {
+                    proof: info.proof.inner.clone(),
+                    y: info.y.try_into().map_err(|e: FfiError| {
+                        cdk::cdk_database::Error::Database(e.to_string().into())
+                    })?,
+                    mint_url: info.mint_url.try_into().map_err(|e: FfiError| {
+                        cdk::cdk_database::Error::Database(e.to_string().into())
+                    })?,
+                    state: info.state.into(),
+                    spending_condition: info
+                        .spending_condition
+                        .map(|sc| sc.try_into())
+                        .transpose()
+                        .map_err(|e: FfiError| {
+                            cdk::cdk_database::Error::Database(e.to_string().into())
                         })?,
-                        state: info.state.into(),
-                        spending_condition: info
-                            .spending_condition
-                            .map(|sc| sc.try_into())
-                            .transpose()
-                            .map_err(|e: FfiError| {
-                                cdk_common::database::Error::Database(e.to_string().into())
-                            })?,
-                        unit: info.unit.into(),
-                    })
+                    unit: info.unit.into(),
                 })
-                .collect();
+            })
+            .collect();
 
         cdk_result
     }
 
     async fn update_proofs_state(
         &self,
-        ys: Vec<cdk_common::nuts::PublicKey>,
-        state: cdk_common::nuts::State,
+        ys: Vec<cdk::nuts::PublicKey>,
+        state: cdk::nuts::State,
     ) -> Result<(), Self::Err> {
         let ffi_ys: Vec<PublicKey> = ys.into_iter().map(Into::into).collect();
         let ffi_state = state.into();
@@ -485,57 +476,57 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
         self.ffi_db
             .update_proofs_state(ffi_ys, ffi_state)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     // Keyset Counter Management
     async fn increment_keyset_counter(
         &self,
-        keyset_id: &cdk_common::nuts::Id,
+        keyset_id: &cdk::nuts::Id,
         count: u32,
     ) -> Result<u32, Self::Err> {
         let ffi_id = (*keyset_id).into();
         self.ffi_db
             .increment_keyset_counter(ffi_id, count)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     // Transaction Management
     async fn add_transaction(
         &self,
-        transaction: cdk_common::wallet::Transaction,
+        transaction: cdk::wallet::types::Transaction,
     ) -> Result<(), Self::Err> {
         let ffi_transaction = transaction.into();
         self.ffi_db
             .add_transaction(ffi_transaction)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn get_transaction(
         &self,
-        transaction_id: cdk_common::wallet::TransactionId,
-    ) -> Result<Option<cdk_common::wallet::Transaction>, Self::Err> {
+        transaction_id: cdk::wallet::types::TransactionId,
+    ) -> Result<Option<cdk::wallet::types::Transaction>, Self::Err> {
         let ffi_id = transaction_id.into();
         let result = self
             .ffi_db
             .get_transaction(ffi_id)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
 
         result
             .map(|tx| tx.try_into())
             .transpose()
-            .map_err(|e: FfiError| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn list_transactions(
         &self,
-        mint_url: Option<cdk_common::mint_url::MintUrl>,
-        direction: Option<cdk_common::wallet::TransactionDirection>,
-        unit: Option<cdk_common::nuts::CurrencyUnit>,
-    ) -> Result<Vec<cdk_common::wallet::Transaction>, Self::Err> {
+        mint_url: Option<cdk::mint_url::MintUrl>,
+        direction: Option<cdk::wallet::types::TransactionDirection>,
+        unit: Option<cdk::nuts::CurrencyUnit>,
+    ) -> Result<Vec<cdk::wallet::types::Transaction>, Self::Err> {
         let ffi_mint_url = mint_url.map(Into::into);
         let ffi_direction = direction.map(Into::into);
         let ffi_unit = unit.map(Into::into);
@@ -544,24 +535,24 @@ impl CdkWalletDatabase for WalletDatabaseBridge {
             .ffi_db
             .list_transactions(ffi_mint_url, ffi_direction, ffi_unit)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))?;
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
 
         result
             .into_iter()
             .map(|tx| tx.try_into())
             .collect::<Result<Vec<_>, FfiError>>()
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 
     async fn remove_transaction(
         &self,
-        transaction_id: cdk_common::wallet::TransactionId,
+        transaction_id: cdk::wallet::types::TransactionId,
     ) -> Result<(), Self::Err> {
         let ffi_id = transaction_id.into();
         self.ffi_db
             .remove_transaction(ffi_id)
             .await
-            .map_err(|e| cdk_common::database::Error::Database(e.to_string().into()))
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
     }
 }
 
@@ -690,8 +681,7 @@ impl WalletDatabase for WalletSqliteDatabase {
         keysets: Vec<KeySetInfo>,
     ) -> Result<(), FfiError> {
         let cdk_mint_url = mint_url.try_into()?;
-        let cdk_keysets: Vec<cdk_common::nuts::KeySetInfo> =
-            keysets.into_iter().map(Into::into).collect();
+        let cdk_keysets: Vec<cdk::nuts::KeySetInfo> = keysets.into_iter().map(Into::into).collect();
         self.inner
             .add_mint_keysets(cdk_mint_url, cdk_keysets)
             .await
@@ -791,10 +781,10 @@ impl WalletDatabase for WalletSqliteDatabase {
 
     // Keys Management
     async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError> {
-        // Convert FFI KeySet to cashu::KeySet
-        let cashu_keyset: cashu::KeySet = keyset.try_into()?;
+        // Convert FFI KeySet to cdk::nuts::KeySet
+        let cdk_keyset: cdk::nuts::KeySet = keyset.try_into()?;
         self.inner
-            .add_keys(cashu_keyset)
+            .add_keys(cdk_keyset)
             .await
             .map_err(|e| FfiError::Database { msg: e.to_string() })
     }
@@ -824,10 +814,10 @@ impl WalletDatabase for WalletSqliteDatabase {
         removed_ys: Vec<PublicKey>,
     ) -> Result<(), FfiError> {
         // Convert FFI types to CDK types
-        let cdk_added: Result<Vec<cdk_common::common::ProofInfo>, FfiError> = added
+        let cdk_added: Result<Vec<cdk::types::ProofInfo>, FfiError> = added
             .into_iter()
             .map(|info| {
-                Ok::<cdk_common::common::ProofInfo, FfiError>(cdk_common::common::ProofInfo {
+                Ok::<cdk::types::ProofInfo, FfiError>(cdk::types::ProofInfo {
                     proof: info.proof.inner.clone(),
                     y: info.y.try_into()?,
                     mint_url: info.mint_url.try_into()?,
@@ -842,7 +832,7 @@ impl WalletDatabase for WalletSqliteDatabase {
             .collect();
         let cdk_added = cdk_added?;
 
-        let cdk_removed_ys: Result<Vec<cdk_common::nuts::PublicKey>, FfiError> =
+        let cdk_removed_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
             removed_ys.into_iter().map(|pk| pk.try_into()).collect();
         let cdk_removed_ys = cdk_removed_ys?;
 
@@ -862,7 +852,7 @@ impl WalletDatabase for WalletSqliteDatabase {
         let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
         let cdk_unit = unit.map(Into::into);
         let cdk_state = state.map(|s| s.into_iter().map(Into::into).collect());
-        let cdk_spending_conditions: Option<Vec<cdk_common::nuts::SpendingConditions>> =
+        let cdk_spending_conditions: Option<Vec<cdk::nuts::SpendingConditions>> =
             spending_conditions
                 .map(|sc| {
                     sc.into_iter()
@@ -885,7 +875,7 @@ impl WalletDatabase for WalletSqliteDatabase {
         ys: Vec<PublicKey>,
         state: ProofState,
     ) -> Result<(), FfiError> {
-        let cdk_ys: Result<Vec<cdk_common::nuts::PublicKey>, FfiError> =
+        let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
             ys.into_iter().map(|pk| pk.try_into()).collect();
         let cdk_ys = cdk_ys?;
         let cdk_state = state.into();
@@ -908,7 +898,7 @@ impl WalletDatabase for WalletSqliteDatabase {
     // Transaction Management
     async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError> {
         // Convert FFI Transaction to CDK Transaction using TryFrom
-        let cdk_transaction: cdk_common::wallet::Transaction = transaction.try_into()?;
+        let cdk_transaction: cdk::wallet::types::Transaction = transaction.try_into()?;
 
         self.inner
             .add_transaction(cdk_transaction)
@@ -960,6 +950,6 @@ impl WalletDatabase for WalletSqliteDatabase {
 /// Helper function to create a CDK database from the FFI trait
 pub fn create_cdk_database_from_ffi(
     ffi_db: Arc<dyn WalletDatabase>,
-) -> Arc<dyn CdkWalletDatabase<Err = cdk_common::database::Error> + Send + Sync> {
+) -> Arc<dyn CdkWalletDatabase<Err = cdk::cdk_database::Error> + Send + Sync> {
     Arc::new(WalletDatabaseBridge::new(ffi_db))
 }

+ 2 - 2
crates/cdk-ffi/src/error.rs

@@ -107,8 +107,8 @@ impl From<cdk::amount::Error> for FfiError {
     }
 }
 
-impl From<cdk_common::nut00::Error> for FfiError {
-    fn from(err: cdk_common::nut00::Error) -> Self {
+impl From<cdk::nuts::nut00::Error> for FfiError {
+    fn from(err: cdk::nuts::nut00::Error) -> Self {
         FfiError::Generic {
             msg: err.to_string(),
         }

+ 441 - 103
crates/cdk-ffi/src/types.rs

@@ -5,8 +5,8 @@ use std::str::FromStr;
 use std::sync::Mutex;
 
 use cdk::nuts::{CurrencyUnit as CdkCurrencyUnit, State as CdkState};
+use cdk::pub_sub::SubId;
 use cdk::Amount as CdkAmount;
-use cdk_common::pub_sub::SubId;
 use serde::{Deserialize, Serialize};
 
 use crate::error::FfiError;
@@ -388,34 +388,32 @@ pub enum SendKind {
     OfflineTolerance { tolerance: Amount },
 }
 
-impl From<SendKind> for cdk_common::wallet::SendKind {
+impl From<SendKind> for cdk::wallet::SendKind {
     fn from(kind: SendKind) -> Self {
         match kind {
-            SendKind::OnlineExact => cdk_common::wallet::SendKind::OnlineExact,
+            SendKind::OnlineExact => cdk::wallet::SendKind::OnlineExact,
             SendKind::OnlineTolerance { tolerance } => {
-                cdk_common::wallet::SendKind::OnlineTolerance(tolerance.into())
+                cdk::wallet::SendKind::OnlineTolerance(tolerance.into())
             }
-            SendKind::OfflineExact => cdk_common::wallet::SendKind::OfflineExact,
+            SendKind::OfflineExact => cdk::wallet::SendKind::OfflineExact,
             SendKind::OfflineTolerance { tolerance } => {
-                cdk_common::wallet::SendKind::OfflineTolerance(tolerance.into())
+                cdk::wallet::SendKind::OfflineTolerance(tolerance.into())
             }
         }
     }
 }
 
-impl From<cdk_common::wallet::SendKind> for SendKind {
-    fn from(kind: cdk_common::wallet::SendKind) -> Self {
+impl From<cdk::wallet::SendKind> for SendKind {
+    fn from(kind: cdk::wallet::SendKind) -> Self {
         match kind {
-            cdk_common::wallet::SendKind::OnlineExact => SendKind::OnlineExact,
-            cdk_common::wallet::SendKind::OnlineTolerance(tolerance) => SendKind::OnlineTolerance {
+            cdk::wallet::SendKind::OnlineExact => SendKind::OnlineExact,
+            cdk::wallet::SendKind::OnlineTolerance(tolerance) => SendKind::OnlineTolerance {
+                tolerance: tolerance.into(),
+            },
+            cdk::wallet::SendKind::OfflineExact => SendKind::OfflineExact,
+            cdk::wallet::SendKind::OfflineTolerance(tolerance) => SendKind::OfflineTolerance {
                 tolerance: tolerance.into(),
             },
-            cdk_common::wallet::SendKind::OfflineExact => SendKind::OfflineExact,
-            cdk_common::wallet::SendKind::OfflineTolerance(tolerance) => {
-                SendKind::OfflineTolerance {
-                    tolerance: tolerance.into(),
-                }
-            }
         }
     }
 }
@@ -1299,8 +1297,8 @@ pub struct Melted {
 
 // MeltQuoteState is just an alias for nut05::QuoteState, so we don't need a separate implementation
 
-impl From<cdk_common::common::Melted> for Melted {
-    fn from(melted: cdk_common::common::Melted) -> Self {
+impl From<cdk::types::Melted> for Melted {
+    fn from(melted: cdk::types::Melted) -> Self {
         Self {
             state: melted.state.into(),
             preimage: melted.preimage,
@@ -1469,9 +1467,314 @@ impl From<SupportedSettings> for cdk::nuts::nut06::SupportedSettings {
     }
 }
 
-/// FFI-compatible Nuts settings (simplified - only includes basic boolean flags)
+// -----------------------------
+// NUT-04/05 FFI Types
+// -----------------------------
+
+/// FFI-compatible MintMethodSettings (NUT-04)
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct MintMethodSettings {
+    pub method: PaymentMethod,
+    pub unit: CurrencyUnit,
+    pub min_amount: Option<Amount>,
+    pub max_amount: Option<Amount>,
+    /// For bolt11, whether mint supports setting invoice description
+    pub description: Option<bool>,
+}
+
+impl From<cdk::nuts::nut04::MintMethodSettings> for MintMethodSettings {
+    fn from(s: cdk::nuts::nut04::MintMethodSettings) -> Self {
+        let description = match s.options {
+            Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description }) => Some(description),
+            _ => None,
+        };
+        Self {
+            method: s.method.into(),
+            unit: s.unit.into(),
+            min_amount: s.min_amount.map(Into::into),
+            max_amount: s.max_amount.map(Into::into),
+            description,
+        }
+    }
+}
+
+impl TryFrom<MintMethodSettings> for cdk::nuts::nut04::MintMethodSettings {
+    type Error = FfiError;
+
+    fn try_from(s: MintMethodSettings) -> Result<Self, Self::Error> {
+        let options = match (s.method.clone(), s.description) {
+            (PaymentMethod::Bolt11, Some(description)) => {
+                Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description })
+            }
+            _ => None,
+        };
+        Ok(Self {
+            method: s.method.into(),
+            unit: s.unit.into(),
+            min_amount: s.min_amount.map(Into::into),
+            max_amount: s.max_amount.map(Into::into),
+            options,
+        })
+    }
+}
+
+/// FFI-compatible Nut04 Settings
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct Nut04Settings {
+    pub methods: Vec<MintMethodSettings>,
+    pub disabled: bool,
+}
+
+impl From<cdk::nuts::nut04::Settings> for Nut04Settings {
+    fn from(s: cdk::nuts::nut04::Settings) -> Self {
+        Self {
+            methods: s.methods.into_iter().map(Into::into).collect(),
+            disabled: s.disabled,
+        }
+    }
+}
+
+impl TryFrom<Nut04Settings> for cdk::nuts::nut04::Settings {
+    type Error = FfiError;
+
+    fn try_from(s: Nut04Settings) -> Result<Self, Self::Error> {
+        Ok(Self {
+            methods: s
+                .methods
+                .into_iter()
+                .map(TryInto::try_into)
+                .collect::<Result<_, _>>()?,
+            disabled: s.disabled,
+        })
+    }
+}
+
+/// FFI-compatible MeltMethodSettings (NUT-05)
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct MeltMethodSettings {
+    pub method: PaymentMethod,
+    pub unit: CurrencyUnit,
+    pub min_amount: Option<Amount>,
+    pub max_amount: Option<Amount>,
+    /// For bolt11, whether mint supports amountless invoices
+    pub amountless: Option<bool>,
+}
+
+impl From<cdk::nuts::nut05::MeltMethodSettings> for MeltMethodSettings {
+    fn from(s: cdk::nuts::nut05::MeltMethodSettings) -> Self {
+        let amountless = match s.options {
+            Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless }) => Some(amountless),
+            _ => None,
+        };
+        Self {
+            method: s.method.into(),
+            unit: s.unit.into(),
+            min_amount: s.min_amount.map(Into::into),
+            max_amount: s.max_amount.map(Into::into),
+            amountless,
+        }
+    }
+}
+
+impl TryFrom<MeltMethodSettings> for cdk::nuts::nut05::MeltMethodSettings {
+    type Error = FfiError;
+
+    fn try_from(s: MeltMethodSettings) -> Result<Self, Self::Error> {
+        let options = match (s.method.clone(), s.amountless) {
+            (PaymentMethod::Bolt11, Some(amountless)) => {
+                Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless })
+            }
+            _ => None,
+        };
+        Ok(Self {
+            method: s.method.into(),
+            unit: s.unit.into(),
+            min_amount: s.min_amount.map(Into::into),
+            max_amount: s.max_amount.map(Into::into),
+            options,
+        })
+    }
+}
+
+/// FFI-compatible Nut05 Settings
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct Nut05Settings {
+    pub methods: Vec<MeltMethodSettings>,
+    pub disabled: bool,
+}
+
+impl From<cdk::nuts::nut05::Settings> for Nut05Settings {
+    fn from(s: cdk::nuts::nut05::Settings) -> Self {
+        Self {
+            methods: s.methods.into_iter().map(Into::into).collect(),
+            disabled: s.disabled,
+        }
+    }
+}
+
+impl TryFrom<Nut05Settings> for cdk::nuts::nut05::Settings {
+    type Error = FfiError;
+
+    fn try_from(s: Nut05Settings) -> Result<Self, Self::Error> {
+        Ok(Self {
+            methods: s
+                .methods
+                .into_iter()
+                .map(TryInto::try_into)
+                .collect::<Result<_, _>>()?,
+            disabled: s.disabled,
+        })
+    }
+}
+
+/// FFI-compatible ProtectedEndpoint (for auth nuts)
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct ProtectedEndpoint {
+    /// HTTP method (GET, POST, etc.)
+    pub method: String,
+    /// Endpoint path
+    pub path: String,
+}
+
+/// FFI-compatible ClearAuthSettings (NUT-21)
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct ClearAuthSettings {
+    /// OpenID Connect discovery URL
+    pub openid_discovery: String,
+    /// OAuth 2.0 client ID
+    pub client_id: String,
+    /// Protected endpoints requiring clear authentication
+    pub protected_endpoints: Vec<ProtectedEndpoint>,
+}
+
+/// FFI-compatible BlindAuthSettings (NUT-22)
+#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
+pub struct BlindAuthSettings {
+    /// Maximum number of blind auth tokens that can be minted per request
+    pub bat_max_mint: u64,
+    /// Protected endpoints requiring blind authentication
+    pub protected_endpoints: Vec<ProtectedEndpoint>,
+}
+
+impl From<cdk::nuts::ClearAuthSettings> for ClearAuthSettings {
+    fn from(settings: cdk::nuts::ClearAuthSettings) -> Self {
+        Self {
+            openid_discovery: settings.openid_discovery,
+            client_id: settings.client_id,
+            protected_endpoints: settings
+                .protected_endpoints
+                .into_iter()
+                .map(Into::into)
+                .collect(),
+        }
+    }
+}
+
+impl TryFrom<ClearAuthSettings> for cdk::nuts::ClearAuthSettings {
+    type Error = FfiError;
+
+    fn try_from(settings: ClearAuthSettings) -> Result<Self, Self::Error> {
+        Ok(Self {
+            openid_discovery: settings.openid_discovery,
+            client_id: settings.client_id,
+            protected_endpoints: settings
+                .protected_endpoints
+                .into_iter()
+                .map(|e| e.try_into())
+                .collect::<Result<Vec<_>, _>>()?,
+        })
+    }
+}
+
+impl From<cdk::nuts::BlindAuthSettings> for BlindAuthSettings {
+    fn from(settings: cdk::nuts::BlindAuthSettings) -> Self {
+        Self {
+            bat_max_mint: settings.bat_max_mint,
+            protected_endpoints: settings
+                .protected_endpoints
+                .into_iter()
+                .map(Into::into)
+                .collect(),
+        }
+    }
+}
+
+impl TryFrom<BlindAuthSettings> for cdk::nuts::BlindAuthSettings {
+    type Error = FfiError;
+
+    fn try_from(settings: BlindAuthSettings) -> Result<Self, Self::Error> {
+        Ok(Self {
+            bat_max_mint: settings.bat_max_mint,
+            protected_endpoints: settings
+                .protected_endpoints
+                .into_iter()
+                .map(|e| e.try_into())
+                .collect::<Result<Vec<_>, _>>()?,
+        })
+    }
+}
+
+impl From<cdk::nuts::ProtectedEndpoint> for ProtectedEndpoint {
+    fn from(endpoint: cdk::nuts::ProtectedEndpoint) -> Self {
+        Self {
+            method: match endpoint.method {
+                cdk::nuts::Method::Get => "GET".to_string(),
+                cdk::nuts::Method::Post => "POST".to_string(),
+            },
+            path: endpoint.path.to_string(),
+        }
+    }
+}
+
+impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
+    type Error = FfiError;
+
+    fn try_from(endpoint: ProtectedEndpoint) -> Result<Self, Self::Error> {
+        let method = match endpoint.method.as_str() {
+            "GET" => cdk::nuts::Method::Get,
+            "POST" => cdk::nuts::Method::Post,
+            _ => {
+                return Err(FfiError::Generic {
+                    msg: format!(
+                        "Invalid HTTP method: {}. Only GET and POST are supported",
+                        endpoint.method
+                    ),
+                })
+            }
+        };
+
+        // Convert path string to RoutePath by matching against known paths
+        let route_path = match endpoint.path.as_str() {
+            "/v1/mint/quote/bolt11" => cdk::nuts::RoutePath::MintQuoteBolt11,
+            "/v1/mint/bolt11" => cdk::nuts::RoutePath::MintBolt11,
+            "/v1/melt/quote/bolt11" => cdk::nuts::RoutePath::MeltQuoteBolt11,
+            "/v1/melt/bolt11" => cdk::nuts::RoutePath::MeltBolt11,
+            "/v1/swap" => cdk::nuts::RoutePath::Swap,
+            "/v1/checkstate" => cdk::nuts::RoutePath::Checkstate,
+            "/v1/restore" => cdk::nuts::RoutePath::Restore,
+            "/v1/auth/blind/mint" => cdk::nuts::RoutePath::MintBlindAuth,
+            "/v1/mint/quote/bolt12" => cdk::nuts::RoutePath::MintQuoteBolt12,
+            "/v1/mint/bolt12" => cdk::nuts::RoutePath::MintBolt12,
+            "/v1/melt/quote/bolt12" => cdk::nuts::RoutePath::MeltQuoteBolt12,
+            "/v1/melt/bolt12" => cdk::nuts::RoutePath::MeltBolt12,
+            _ => {
+                return Err(FfiError::Generic {
+                    msg: format!("Unknown route path: {}", endpoint.path),
+                })
+            }
+        };
+
+        Ok(cdk::nuts::ProtectedEndpoint::new(method, route_path))
+    }
+}
+
+/// FFI-compatible Nuts settings (extended to include NUT-04 and NUT-05 settings)
 #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
 pub struct Nuts {
+    /// NUT04 Settings
+    pub nut04: Nut04Settings,
+    /// NUT05 Settings
+    pub nut05: Nut05Settings,
     /// NUT07 Settings - Token state check
     pub nut07_supported: bool,
     /// NUT08 Settings - Lightning fee return
@@ -1488,6 +1791,10 @@ pub struct Nuts {
     pub nut14_supported: bool,
     /// NUT20 Settings - Web sockets
     pub nut20_supported: bool,
+    /// NUT21 Settings - Clear authentication
+    pub nut21: Option<ClearAuthSettings>,
+    /// NUT22 Settings - Blind authentication
+    pub nut22: Option<BlindAuthSettings>,
     /// Supported currency units for minting
     pub mint_units: Vec<CurrencyUnit>,
     /// Supported currency units for melting
@@ -1496,7 +1803,20 @@ pub struct Nuts {
 
 impl From<cdk::nuts::Nuts> for Nuts {
     fn from(nuts: cdk::nuts::Nuts) -> Self {
+        let mint_units = nuts
+            .supported_mint_units()
+            .into_iter()
+            .map(|u| u.clone().into())
+            .collect();
+        let melt_units = nuts
+            .supported_melt_units()
+            .into_iter()
+            .map(|u| u.clone().into())
+            .collect();
+
         Self {
+            nut04: nuts.nut04.clone().into(),
+            nut05: nuts.nut05.clone().into(),
             nut07_supported: nuts.nut07.supported,
             nut08_supported: nuts.nut08.supported,
             nut09_supported: nuts.nut09.supported,
@@ -1505,20 +1825,54 @@ impl From<cdk::nuts::Nuts> for Nuts {
             nut12_supported: nuts.nut12.supported,
             nut14_supported: nuts.nut14.supported,
             nut20_supported: nuts.nut20.supported,
-            mint_units: nuts
-                .supported_mint_units()
-                .into_iter()
-                .map(|u| u.clone().into())
-                .collect(),
-            melt_units: nuts
-                .supported_melt_units()
-                .into_iter()
-                .map(|u| u.clone().into())
-                .collect(),
+            nut21: nuts.nut21.map(Into::into),
+            nut22: nuts.nut22.map(Into::into),
+            mint_units,
+            melt_units,
         }
     }
 }
 
+impl TryFrom<Nuts> for cdk::nuts::Nuts {
+    type Error = FfiError;
+
+    fn try_from(n: Nuts) -> Result<Self, Self::Error> {
+        Ok(Self {
+            nut04: n.nut04.try_into()?,
+            nut05: n.nut05.try_into()?,
+            nut07: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut07_supported,
+            },
+            nut08: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut08_supported,
+            },
+            nut09: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut09_supported,
+            },
+            nut10: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut10_supported,
+            },
+            nut11: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut11_supported,
+            },
+            nut12: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut12_supported,
+            },
+            nut14: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut14_supported,
+            },
+            nut15: Default::default(),
+            nut17: Default::default(),
+            nut19: Default::default(),
+            nut20: cdk::nuts::nut06::SupportedSettings {
+                supported: n.nut20_supported,
+            },
+            nut21: n.nut21.map(|s| s.try_into()).transpose()?,
+            nut22: n.nut22.map(|s| s.try_into()).transpose()?,
+        })
+    }
+}
+
 impl Nuts {
     /// Convert Nuts to JSON string
     pub fn to_json(&self) -> Result<String, FfiError> {
@@ -1588,8 +1942,10 @@ impl From<cdk::nuts::MintInfo> for MintInfo {
     }
 }
 
-impl From<MintInfo> for cdk_common::nuts::MintInfo {
+impl From<MintInfo> for cdk::nuts::MintInfo {
     fn from(info: MintInfo) -> Self {
+        // Convert FFI Nuts back to cdk::nuts::Nuts (best-effort)
+        let nuts_cdk: cdk::nuts::Nuts = info.nuts.clone().try_into().unwrap_or_default();
         Self {
             name: info.name,
             pubkey: info.pubkey.and_then(|p| p.parse().ok()),
@@ -1599,7 +1955,7 @@ impl From<MintInfo> for cdk_common::nuts::MintInfo {
             contact: info
                 .contact
                 .map(|contacts| contacts.into_iter().map(Into::into).collect()),
-            nuts: cdk_common::nuts::Nuts::default(), // Simplified conversion
+            nuts: nuts_cdk,
             icon_url: info.icon_url,
             urls: info.urls,
             motd: info.motd,
@@ -1856,8 +2212,8 @@ pub struct Transaction {
     pub quote_id: Option<String>,
 }
 
-impl From<cdk_common::wallet::Transaction> for Transaction {
-    fn from(tx: cdk_common::wallet::Transaction) -> Self {
+impl From<cdk::wallet::types::Transaction> for Transaction {
+    fn from(tx: cdk::wallet::types::Transaction) -> Self {
         Self {
             id: tx.id().into(),
             mint_url: tx.mint_url.into(),
@@ -1875,11 +2231,11 @@ impl From<cdk_common::wallet::Transaction> for Transaction {
 }
 
 /// Convert FFI Transaction to CDK Transaction
-impl TryFrom<Transaction> for cdk_common::wallet::Transaction {
+impl TryFrom<Transaction> for cdk::wallet::types::Transaction {
     type Error = FfiError;
 
     fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
-        let cdk_ys: Result<Vec<cdk_common::nuts::PublicKey>, _> =
+        let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, _> =
             tx.ys.into_iter().map(|pk| pk.try_into()).collect();
         let cdk_ys = cdk_ys?;
 
@@ -1926,20 +2282,20 @@ pub enum TransactionDirection {
     Outgoing,
 }
 
-impl From<cdk_common::wallet::TransactionDirection> for TransactionDirection {
-    fn from(direction: cdk_common::wallet::TransactionDirection) -> Self {
+impl From<cdk::wallet::types::TransactionDirection> for TransactionDirection {
+    fn from(direction: cdk::wallet::types::TransactionDirection) -> Self {
         match direction {
-            cdk_common::wallet::TransactionDirection::Incoming => TransactionDirection::Incoming,
-            cdk_common::wallet::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
+            cdk::wallet::types::TransactionDirection::Incoming => TransactionDirection::Incoming,
+            cdk::wallet::types::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
         }
     }
 }
 
-impl From<TransactionDirection> for cdk_common::wallet::TransactionDirection {
+impl From<TransactionDirection> for cdk::wallet::types::TransactionDirection {
     fn from(direction: TransactionDirection) -> Self {
         match direction {
-            TransactionDirection::Incoming => cdk_common::wallet::TransactionDirection::Incoming,
-            TransactionDirection::Outgoing => cdk_common::wallet::TransactionDirection::Outgoing,
+            TransactionDirection::Incoming => cdk::wallet::types::TransactionDirection::Incoming,
+            TransactionDirection::Outgoing => cdk::wallet::types::TransactionDirection::Outgoing,
         }
     }
 }
@@ -1975,32 +2331,31 @@ impl TransactionId {
     /// Create from proofs
     pub fn from_proofs(proofs: &Proofs) -> Result<Self, FfiError> {
         let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
-        let id = cdk_common::wallet::TransactionId::from_proofs(cdk_proofs)?;
+        let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?;
         Ok(Self {
             hex: id.to_string(),
         })
     }
 }
 
-impl From<cdk_common::wallet::TransactionId> for TransactionId {
-    fn from(id: cdk_common::wallet::TransactionId) -> Self {
+impl From<cdk::wallet::types::TransactionId> for TransactionId {
+    fn from(id: cdk::wallet::types::TransactionId) -> Self {
         Self {
             hex: id.to_string(),
         }
     }
 }
 
-impl TryFrom<TransactionId> for cdk_common::wallet::TransactionId {
+impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
     type Error = FfiError;
 
     fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
-        cdk_common::wallet::TransactionId::from_hex(&id.hex)
+        cdk::wallet::types::TransactionId::from_hex(&id.hex)
             .map_err(|e| FfiError::InvalidHex { msg: e.to_string() })
     }
 }
 
 /// FFI-compatible AuthProof
-#[cfg(feature = "auth")]
 #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
 pub struct AuthProof {
     /// Keyset ID
@@ -2013,9 +2368,8 @@ pub struct AuthProof {
     pub y: String,
 }
 
-#[cfg(feature = "auth")]
-impl From<cdk_common::AuthProof> for AuthProof {
-    fn from(auth_proof: cdk_common::AuthProof) -> Self {
+impl From<cdk::nuts::AuthProof> for AuthProof {
+    fn from(auth_proof: cdk::nuts::AuthProof) -> Self {
         Self {
             keyset_id: auth_proof.keyset_id.to_string(),
             secret: auth_proof.secret.to_string(),
@@ -2028,28 +2382,26 @@ impl From<cdk_common::AuthProof> for AuthProof {
     }
 }
 
-#[cfg(feature = "auth")]
-impl TryFrom<AuthProof> for cdk_common::AuthProof {
+impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
     type Error = FfiError;
 
     fn try_from(auth_proof: AuthProof) -> Result<Self, Self::Error> {
         use std::str::FromStr;
         Ok(Self {
-            keyset_id: cdk_common::Id::from_str(&auth_proof.keyset_id)
+            keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
                 .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
             secret: {
                 use std::str::FromStr;
-                cdk_common::secret::Secret::from_str(&auth_proof.secret)
+                cdk::secret::Secret::from_str(&auth_proof.secret)
                     .map_err(|e| FfiError::Serialization { msg: e.to_string() })?
             },
-            c: cdk_common::PublicKey::from_str(&auth_proof.c)
+            c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
                 .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
             dleq: None, // FFI doesn't expose DLEQ proofs for simplicity
         })
     }
 }
 
-#[cfg(feature = "auth")]
 impl AuthProof {
     /// Convert AuthProof to JSON string
     pub fn to_json(&self) -> Result<String, FfiError> {
@@ -2058,14 +2410,12 @@ impl AuthProof {
 }
 
 /// Decode AuthProof from JSON string
-#[cfg(feature = "auth")]
 #[uniffi::export]
 pub fn decode_auth_proof(json: String) -> Result<AuthProof, FfiError> {
     Ok(serde_json::from_str(&json)?)
 }
 
 /// Encode AuthProof to JSON string
-#[cfg(feature = "auth")]
 #[uniffi::export]
 pub fn encode_auth_proof(proof: AuthProof) -> Result<String, FfiError> {
     Ok(serde_json::to_string(&proof)?)
@@ -2150,7 +2500,7 @@ pub struct SubscribeParams {
     pub id: Option<String>,
 }
 
-impl From<SubscribeParams> for cdk_common::subscription::Params {
+impl From<SubscribeParams> for cdk::nuts::nut17::Params<cdk::pub_sub::SubId> {
     fn from(params: SubscribeParams) -> Self {
         let sub_id = params
             .id
@@ -2329,8 +2679,8 @@ pub struct KeySetInfo {
     pub input_fee_ppk: u64,
 }
 
-impl From<cdk_common::nuts::KeySetInfo> for KeySetInfo {
-    fn from(keyset: cdk_common::nuts::KeySetInfo) -> Self {
+impl From<cdk::nuts::KeySetInfo> for KeySetInfo {
+    fn from(keyset: cdk::nuts::KeySetInfo) -> Self {
         Self {
             id: keyset.id.to_string(),
             unit: keyset.unit.into(),
@@ -2340,17 +2690,11 @@ impl From<cdk_common::nuts::KeySetInfo> for KeySetInfo {
     }
 }
 
-impl From<KeySetInfo> for cdk_common::nuts::KeySetInfo {
+impl From<KeySetInfo> for cdk::nuts::KeySetInfo {
     fn from(keyset: KeySetInfo) -> Self {
         use std::str::FromStr;
         Self {
-            id: cdk_common::nuts::Id::from_str(&keyset.id).unwrap_or_else(|_| {
-                // Create a dummy keyset for empty mint keys
-                use std::collections::BTreeMap;
-                let empty_map = BTreeMap::new();
-                let empty_keys = cdk_common::nut01::MintKeys::new(empty_map);
-                cdk_common::nuts::Id::from(&empty_keys)
-            }),
+            id: cdk::nuts::Id::from_str(&keyset.id).unwrap(),
             unit: keyset.unit.into(),
             active: keyset.active,
             final_expiry: None,
@@ -2386,15 +2730,15 @@ pub struct PublicKey {
     pub hex: String,
 }
 
-impl From<cdk_common::nuts::PublicKey> for PublicKey {
-    fn from(key: cdk_common::nuts::PublicKey) -> Self {
+impl From<cdk::nuts::PublicKey> for PublicKey {
+    fn from(key: cdk::nuts::PublicKey) -> Self {
         Self {
             hex: key.to_string(),
         }
     }
 }
 
-impl TryFrom<PublicKey> for cdk_common::nuts::PublicKey {
+impl TryFrom<PublicKey> for cdk::nuts::PublicKey {
     type Error = FfiError;
 
     fn try_from(key: PublicKey) -> Result<Self, Self::Error> {
@@ -2417,8 +2761,8 @@ pub struct Keys {
     pub keys: HashMap<u64, String>,
 }
 
-impl From<cdk_common::nuts::Keys> for Keys {
-    fn from(keys: cdk_common::nuts::Keys) -> Self {
+impl From<cdk::nuts::Keys> for Keys {
+    fn from(keys: cdk::nuts::Keys) -> Self {
         // Keys doesn't have id and unit - we'll need to get these from context
         // For now, use placeholder values
         Self {
@@ -2433,7 +2777,7 @@ impl From<cdk_common::nuts::Keys> for Keys {
     }
 }
 
-impl TryFrom<Keys> for cdk_common::nuts::Keys {
+impl TryFrom<Keys> for cdk::nuts::Keys {
     type Error = FfiError;
 
     fn try_from(keys: Keys) -> Result<Self, Self::Error> {
@@ -2443,13 +2787,13 @@ impl TryFrom<Keys> for cdk_common::nuts::Keys {
         // Convert the HashMap to BTreeMap with proper types
         let mut keys_map = BTreeMap::new();
         for (amount_u64, pubkey_hex) in keys.keys {
-            let amount = cashu::Amount::from(amount_u64);
-            let pubkey = cashu::PublicKey::from_str(&pubkey_hex)
+            let amount = cdk::Amount::from(amount_u64);
+            let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
                 .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
             keys_map.insert(amount, pubkey);
         }
 
-        Ok(cdk_common::nuts::Keys::new(keys_map))
+        Ok(cdk::nuts::Keys::new(keys_map))
     }
 }
 
@@ -2485,8 +2829,8 @@ pub struct KeySet {
     pub final_expiry: Option<u64>,
 }
 
-impl From<cashu::KeySet> for KeySet {
-    fn from(keyset: cashu::KeySet) -> Self {
+impl From<cdk::nuts::KeySet> for KeySet {
+    fn from(keyset: cdk::nuts::KeySet) -> Self {
         Self {
             id: keyset.id.to_string(),
             unit: keyset.unit.into(),
@@ -2501,7 +2845,7 @@ impl From<cashu::KeySet> for KeySet {
     }
 }
 
-impl TryFrom<KeySet> for cashu::KeySet {
+impl TryFrom<KeySet> for cdk::nuts::KeySet {
     type Error = FfiError;
 
     fn try_from(keyset: KeySet) -> Result<Self, Self::Error> {
@@ -2509,23 +2853,23 @@ impl TryFrom<KeySet> for cashu::KeySet {
         use std::str::FromStr;
 
         // Convert id
-        let id = cashu::Id::from_str(&keyset.id)
+        let id = cdk::nuts::Id::from_str(&keyset.id)
             .map_err(|e| FfiError::Serialization { msg: e.to_string() })?;
 
         // Convert unit
-        let unit: cashu::CurrencyUnit = keyset.unit.into();
+        let unit: cdk::nuts::CurrencyUnit = keyset.unit.into();
 
         // Convert keys
         let mut keys_map = BTreeMap::new();
         for (amount_u64, pubkey_hex) in keyset.keys {
-            let amount = cashu::Amount::from(amount_u64);
-            let pubkey = cashu::PublicKey::from_str(&pubkey_hex)
+            let amount = cdk::Amount::from(amount_u64);
+            let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
                 .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
             keys_map.insert(amount, pubkey);
         }
-        let keys = cashu::Keys::new(keys_map);
+        let keys = cdk::nuts::Keys::new(keys_map);
 
-        Ok(cashu::KeySet {
+        Ok(cdk::nuts::KeySet {
             id,
             unit,
             keys,
@@ -2570,8 +2914,8 @@ pub struct ProofInfo {
     pub unit: CurrencyUnit,
 }
 
-impl From<cdk_common::common::ProofInfo> for ProofInfo {
-    fn from(info: cdk_common::common::ProofInfo) -> Self {
+impl From<cdk::types::ProofInfo> for ProofInfo {
+    fn from(info: cdk::types::ProofInfo) -> Self {
         Self {
             proof: std::sync::Arc::new(info.proof.into()),
             y: info.y.into(),
@@ -2586,15 +2930,15 @@ impl From<cdk_common::common::ProofInfo> for ProofInfo {
 /// Decode ProofInfo from JSON string
 #[uniffi::export]
 pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
-    let info: cdk_common::common::ProofInfo = serde_json::from_str(&json)?;
+    let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
     Ok(info.into())
 }
 
 /// Encode ProofInfo to JSON string
 #[uniffi::export]
 pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
-    // Convert to cdk_common::common::ProofInfo for serialization
-    let cdk_info = cdk_common::common::ProofInfo {
+    // Convert to cdk::types::ProofInfo for serialization
+    let cdk_info = cdk::types::ProofInfo {
         proof: info.proof.inner.clone(),
         y: info.y.try_into()?,
         mint_url: info.mint_url.try_into()?,
@@ -2614,23 +2958,17 @@ pub struct Id {
     pub hex: String,
 }
 
-impl From<cdk_common::nuts::Id> for Id {
-    fn from(id: cdk_common::nuts::Id) -> Self {
+impl From<cdk::nuts::Id> for Id {
+    fn from(id: cdk::nuts::Id) -> Self {
         Self {
             hex: id.to_string(),
         }
     }
 }
 
-impl From<Id> for cdk_common::nuts::Id {
+impl From<Id> for cdk::nuts::Id {
     fn from(id: Id) -> Self {
         use std::str::FromStr;
-        Self::from_str(&id.hex).unwrap_or_else(|_| {
-            // Create a dummy keyset for empty mint keys
-            use std::collections::BTreeMap;
-            let empty_map = BTreeMap::new();
-            let empty_keys = cdk_common::nut01::MintKeys::new(empty_map);
-            cdk_common::nuts::Id::from(&empty_keys)
-        })
+        Self::from_str(&id.hex).unwrap()
     }
 }

+ 2 - 3
crates/cdk-ffi/src/wallet.rs

@@ -348,7 +348,7 @@ impl Wallet {
         &self,
         params: SubscribeParams,
     ) -> Result<std::sync::Arc<ActiveSubscription>, FfiError> {
-        let cdk_params: cdk_common::subscription::Params = params.clone().into();
+        let cdk_params: cdk::nuts::nut17::Params<cdk::pub_sub::SubId> = params.clone().into();
         let sub_id = cdk_params.id.to_string();
         let active_sub = self.inner.subscribe(cdk_params).await;
         Ok(std::sync::Arc::new(ActiveSubscription::new(
@@ -404,7 +404,7 @@ impl Wallet {
 }
 
 /// BIP353 methods for Wallet
-#[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
+#[cfg(not(target_arch = "wasm32"))]
 #[uniffi::export(async_runtime = "tokio")]
 impl Wallet {
     /// Get a quote for a BIP353 melt
@@ -426,7 +426,6 @@ impl Wallet {
 }
 
 /// Auth methods for Wallet
-#[cfg(feature = "auth")]
 #[uniffi::export(async_runtime = "tokio")]
 impl Wallet {
     /// Set Clear Auth Token (CAT) for authentication

+ 2 - 0
crates/cdk-integration-tests/src/bin/start_regtest_mints.rs

@@ -266,6 +266,7 @@ fn create_ldk_settings(
 ) -> cdk_mintd::config::Settings {
     cdk_mintd::config::Settings {
         info: cdk_mintd::config::Info {
+            quote_ttl: None,
             url: format!("http://127.0.0.1:{port}"),
             listen_host: "127.0.0.1".to_string(),
             listen_port: port,
@@ -294,6 +295,7 @@ fn create_ldk_settings(
         fake_wallet: None,
         grpc_processor: None,
         database: cdk_mintd::config::Database::default(),
+        auth_database: None,
         mint_management_rpc: None,
         prometheus: None,
         auth: None,

+ 3 - 6
crates/cdk-integration-tests/src/init_pure_tests.rs

@@ -11,7 +11,7 @@ use bip39::Mnemonic;
 use cashu::quote_id::QuoteId;
 use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
 use cdk::amount::SplitTarget;
-use cdk::cdk_database::{self, MintDatabase, WalletDatabase};
+use cdk::cdk_database::{self, WalletDatabase};
 use cdk::mint::{MintBuilder, MintMeltLimits};
 use cdk::nuts::nut00::ProofsMethods;
 use cdk::nuts::{
@@ -271,17 +271,14 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
         .with_description("pure test mint".to_string())
         .with_urls(vec!["https://aaa".to_string()]);
 
-    let tx_localstore = localstore.clone();
-    let mut tx = tx_localstore.begin_transaction().await?;
-
     let quote_ttl = QuoteTTL::new(10000, 10000);
-    tx.set_quote_ttl(quote_ttl).await?;
-    tx.commit().await?;
 
     let mint = mint_builder
         .build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
         .await?;
 
+    mint.set_quote_ttl(quote_ttl).await?;
+
     mint.start().await?;
 
     Ok(mint)

+ 8 - 0
crates/cdk-integration-tests/src/shared.rs

@@ -181,6 +181,8 @@ pub fn create_fake_wallet_settings(
     cdk_mintd::config::Settings {
         info: cdk_mintd::config::Info {
             url: format!("http://127.0.0.1:{port}"),
+            quote_ttl: None,
+
             listen_host: "127.0.0.1".to_string(),
             listen_port: port,
             seed: None,
@@ -217,6 +219,7 @@ pub fn create_fake_wallet_settings(
             engine: DatabaseEngine::from_str(database).expect("valid database"),
             postgres: None,
         },
+        auth_database: None,
         mint_management_rpc: None,
         auth: None,
         prometheus: Some(Default::default()),
@@ -233,6 +236,8 @@ pub fn create_cln_settings(
     cdk_mintd::config::Settings {
         info: cdk_mintd::config::Info {
             url: format!("http://127.0.0.1:{port}"),
+            quote_ttl: None,
+
             listen_host: "127.0.0.1".to_string(),
             listen_port: port,
             seed: None,
@@ -264,6 +269,7 @@ pub fn create_cln_settings(
         fake_wallet: None,
         grpc_processor: None,
         database: cdk_mintd::config::Database::default(),
+        auth_database: None,
         mint_management_rpc: None,
         auth: None,
         prometheus: Some(Default::default()),
@@ -278,6 +284,7 @@ pub fn create_lnd_settings(
 ) -> cdk_mintd::config::Settings {
     cdk_mintd::config::Settings {
         info: cdk_mintd::config::Info {
+            quote_ttl: None,
             url: format!("http://127.0.0.1:{port}"),
             listen_host: "127.0.0.1".to_string(),
             listen_port: port,
@@ -310,6 +317,7 @@ pub fn create_lnd_settings(
         fake_wallet: None,
         grpc_processor: None,
         database: cdk_mintd::config::Database::default(),
+        auth_database: None,
         mint_management_rpc: None,
         auth: None,
         prometheus: Some(Default::default()),

+ 1 - 5
crates/cdk-integration-tests/tests/mint.rs

@@ -15,7 +15,6 @@ use std::collections::{HashMap, HashSet};
 use std::sync::Arc;
 
 use bip39::Mnemonic;
-use cdk::cdk_database::MintDatabase;
 use cdk::mint::{MintBuilder, MintMeltLimits};
 use cdk::nuts::{CurrencyUnit, PaymentMethod};
 use cdk::types::{FeeReserve, QuoteTTL};
@@ -64,12 +63,9 @@ async fn test_correct_keyset() {
         .build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
         .await
         .unwrap();
-    let mut tx = localstore.begin_transaction().await.unwrap();
 
     let quote_ttl = QuoteTTL::new(10000, 10000);
-    tx.set_quote_ttl(quote_ttl).await.unwrap();
-
-    tx.commit().await.unwrap();
+    mint.set_quote_ttl(quote_ttl).await.unwrap();
 
     let active = mint.get_active_keysets();
 

+ 0 - 1
crates/cdk-mintd/Cargo.toml

@@ -26,7 +26,6 @@ grpc-processor = ["dep:cdk-payment-processor", "cdk-signatory/grpc"]
 sqlcipher = ["sqlite", "cdk-sqlite/sqlcipher"]
 # MSRV is not committed to with swagger enabled
 swagger = ["cdk-axum/swagger", "dep:utoipa", "dep:utoipa-swagger-ui"]
-redis = ["cdk-axum/redis"]
 auth = ["cdk/auth", "cdk-axum/auth", "cdk-sqlite?/auth", "cdk-postgres?/auth"]
 prometheus = ["cdk/prometheus", "dep:cdk-prometheus", "cdk-sqlite?/prometheus", "cdk-axum/prometheus"]
 

+ 47 - 0
crates/cdk-mintd/README.md

@@ -151,6 +151,53 @@ After setup and first run, your directory will look like:
 - Log directories and files
 - Lightning backend data directories
 
+## Docker Usage
+
+CDK Mintd provides ready-to-use Docker images with multiple Lightning backend options.
+
+### Quick Start
+
+#### Standard mint with fakewallet backend (testing only):
+```bash
+docker-compose up
+```
+
+#### Mint with LDK Node backend:
+```bash
+# Option 1: Use dedicated ldk-node compose file
+docker-compose -f docker-compose.ldk-node.yaml up
+
+# Option 2: Use main compose file with profile
+docker-compose --profile ldk-node up
+```
+
+### Available Images
+
+- **`cashubtc/mintd:latest`** - Standard mint with default features
+- **`cashubtc/mintd-ldk-node:latest`** - Mint with LDK Node support
+
+### Configuration via Environment Variables
+
+All configuration can be done through environment variables:
+
+```yaml
+environment:
+  - CDK_MINTD_LN_BACKEND=ldk-node
+  - CDK_MINTD_DATABASE=sqlite
+  - CDK_MINTD_LISTEN_HOST=0.0.0.0
+  - CDK_MINTD_LISTEN_PORT=8085
+  - CDK_MINTD_LDK_NODE_NETWORK=testnet
+  - CDK_MINTD_LDK_NODE_ESPLORA_URL=https://blockstream.info/testnet/api
+```
+
+### Monitoring
+
+Both Prometheus metrics and Grafana dashboards are included:
+- Prometheus: http://localhost:9090
+- Grafana: http://localhost:3011 (admin/admin)
+
+For detailed Docker documentation, see [README-ldk-node.md](../../README-ldk-node.md).
+
 ## Testing Your Mint
 
 1. **Verify the mint is running**:

+ 20 - 4
crates/cdk-mintd/example.config.toml

@@ -7,6 +7,12 @@ mnemonic = ""
 # input_fee_ppk = 0
 # enable_swagger_ui = false
 
+[info.quote_ttl]
+# Prefer explicit fields over inline tables for readability and ease of overrides
+mint_ttl = 600
+melt_ttl = 120
+
+
 [info.logging]
 # Where to output logs: "stdout", "file", or "both" (default: "both")
 # Note: "stdout" actually outputs to stderr (standard error stream)
@@ -27,13 +33,10 @@ enabled = false
 #port = 9090
 # 
 [info.http_cache]
-# memory or redis
+# backend type: memory (default)
 backend = "memory"
 ttl = 60
 tti = 60
-# `key_prefix` and `connection_string` required for redis
-# key_prefix = "mintd"
-# connection_string = "redis://localhost"
 
 # NOTE: If [mint_management_rpc] is enabled these values will only be used on first start up.
 # Further changes must be made through the rpc.
@@ -68,6 +71,19 @@ max_connections = 20
 # Connection timeout in seconds (optional, defaults to 10)
 connection_timeout_seconds = 10
 
+# Auth database configuration (optional, only used when auth is enabled)
+[auth_database.postgres]
+# PostgreSQL connection URL for authentication database
+# Can also be set via CDK_MINTD_AUTH_POSTGRES_URL environment variable
+# Environment variables take precedence over config file settings
+url = "postgresql://user:password@localhost:5432/cdk_mint_auth"
+# TLS mode: "disable", "prefer", "require" (optional, defaults to "disable")
+tls_mode = "disable"
+# Maximum number of connections in the pool (optional, defaults to 20)
+max_connections = 20
+# Connection timeout in seconds (optional, defaults to 10)
+connection_timeout_seconds = 10
+
 [ln]
 # Required ln backend `cln`, `lnd`, `fakewallet`, 'lnbits', 'ldknode'
 ln_backend = "fakewallet"

+ 34 - 0
crates/cdk-mintd/src/config.rs

@@ -4,6 +4,7 @@ use bitcoin::hashes::{sha256, Hash};
 use cdk::nuts::{CurrencyUnit, PublicKey};
 use cdk::Amount;
 use cdk_axum::cache;
+use cdk_common::common::QuoteTTL;
 use config::{Config, ConfigError, File};
 use serde::{Deserialize, Serialize};
 
@@ -68,6 +69,12 @@ pub struct Info {
     ///
     /// This requires `mintd` was built with the `swagger` feature flag.
     pub enable_swagger_ui: Option<bool>,
+
+    /// Optional persisted quote TTL values (seconds) to initialize the database with
+    /// when RPC is disabled or on first-run when RPC is enabled.
+    /// If not provided, defaults are used.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub quote_ttl: Option<QuoteTTL>,
 }
 
 impl Default for Info {
@@ -84,6 +91,7 @@ impl Default for Info {
             http_cache: cache::Config::default(),
             enable_swagger_ui: None,
             logging: LoggingConfig::default(),
+            quote_ttl: None,
         }
     }
 }
@@ -359,6 +367,30 @@ pub struct Database {
     pub postgres: Option<PostgresConfig>,
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct AuthDatabase {
+    pub postgres: Option<PostgresAuthConfig>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct PostgresAuthConfig {
+    pub url: String,
+    pub tls_mode: Option<String>,
+    pub max_connections: Option<usize>,
+    pub connection_timeout_seconds: Option<u64>,
+}
+
+impl Default for PostgresAuthConfig {
+    fn default() -> Self {
+        Self {
+            url: String::new(),
+            tls_mode: Some("disable".to_string()),
+            max_connections: Some(20),
+            connection_timeout_seconds: Some(10),
+        }
+    }
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct PostgresConfig {
     pub url: String,
@@ -449,6 +481,8 @@ pub struct Settings {
     pub fake_wallet: Option<FakeWallet>,
     pub grpc_processor: Option<GrpcProcessor>,
     pub database: Database,
+    #[cfg(feature = "auth")]
+    pub auth_database: Option<AuthDatabase>,
     #[cfg(feature = "management-rpc")]
     pub mint_management_rpc: Option<MintManagementRpc>,
     pub auth: Option<Auth>,

+ 3 - 0
crates/cdk-mintd/src/env_vars/common.rs

@@ -14,6 +14,9 @@ pub const ENV_SECONDS_QUOTE_VALID: &str = "CDK_MINTD_SECONDS_QUOTE_VALID";
 pub const ENV_CACHE_SECONDS: &str = "CDK_MINTD_CACHE_SECONDS";
 pub const ENV_EXTEND_CACHE_SECONDS: &str = "CDK_MINTD_EXTEND_CACHE_SECONDS";
 pub const ENV_INPUT_FEE_PPK: &str = "CDK_MINTD_INPUT_FEE_PPK";
+pub const ENV_QUOTE_TTL_MINT: &str = "CDK_MINTD_QUOTE_TTL_MINT";
+pub const ENV_QUOTE_TTL_MELT: &str = "CDK_MINTD_QUOTE_TTL_MELT";
+
 pub const ENV_ENABLE_SWAGGER: &str = "CDK_MINTD_ENABLE_SWAGGER";
 pub const ENV_LOGGING_OUTPUT: &str = "CDK_MINTD_LOGGING_OUTPUT";
 pub const ENV_LOGGING_CONSOLE_LEVEL: &str = "CDK_MINTD_LOGGING_CONSOLE_LEVEL";

+ 33 - 1
crates/cdk-mintd/src/env_vars/database.rs

@@ -2,13 +2,19 @@
 
 use std::env;
 
-use crate::config::PostgresConfig;
+use crate::config::{PostgresAuthConfig, PostgresConfig};
 
 pub const ENV_POSTGRES_URL: &str = "CDK_MINTD_POSTGRES_URL";
 pub const ENV_POSTGRES_TLS_MODE: &str = "CDK_MINTD_POSTGRES_TLS_MODE";
 pub const ENV_POSTGRES_MAX_CONNECTIONS: &str = "CDK_MINTD_POSTGRES_MAX_CONNECTIONS";
 pub const ENV_POSTGRES_CONNECTION_TIMEOUT: &str = "CDK_MINTD_POSTGRES_CONNECTION_TIMEOUT_SECONDS";
 
+pub const ENV_AUTH_POSTGRES_URL: &str = "CDK_MINTD_AUTH_POSTGRES_URL";
+pub const ENV_AUTH_POSTGRES_TLS_MODE: &str = "CDK_MINTD_AUTH_POSTGRES_TLS_MODE";
+pub const ENV_AUTH_POSTGRES_MAX_CONNECTIONS: &str = "CDK_MINTD_AUTH_POSTGRES_MAX_CONNECTIONS";
+pub const ENV_AUTH_POSTGRES_CONNECTION_TIMEOUT: &str =
+    "CDK_MINTD_AUTH_POSTGRES_CONNECTION_TIMEOUT_SECONDS";
+
 impl PostgresConfig {
     pub fn from_env(mut self) -> Self {
         // Check for new PostgreSQL URL env var first, then fallback to legacy DATABASE_URL
@@ -38,3 +44,29 @@ impl PostgresConfig {
         self
     }
 }
+
+impl PostgresAuthConfig {
+    pub fn from_env(mut self) -> Self {
+        if let Ok(url) = env::var(ENV_AUTH_POSTGRES_URL) {
+            self.url = url;
+        }
+
+        if let Ok(tls_mode) = env::var(ENV_AUTH_POSTGRES_TLS_MODE) {
+            self.tls_mode = Some(tls_mode);
+        }
+
+        if let Ok(max_connections) = env::var(ENV_AUTH_POSTGRES_MAX_CONNECTIONS) {
+            if let Ok(parsed) = max_connections.parse::<usize>() {
+                self.max_connections = Some(parsed);
+            }
+        }
+
+        if let Ok(timeout) = env::var(ENV_AUTH_POSTGRES_CONNECTION_TIMEOUT) {
+            if let Ok(parsed) = timeout.parse::<u64>() {
+                self.connection_timeout_seconds = Some(parsed);
+            }
+        }
+
+        self
+    }
+}

+ 23 - 0
crates/cdk-mintd/src/env_vars/info.rs

@@ -3,6 +3,8 @@
 use std::env;
 use std::str::FromStr;
 
+use cdk_common::common::QuoteTTL;
+
 use super::common::*;
 use crate::config::{Info, LoggingOutput};
 
@@ -85,6 +87,27 @@ impl Info {
 
         self.http_cache = self.http_cache.from_env();
 
+        // Quote TTL from env
+        let mut mint_ttl_env: Option<u64> = None;
+        let mut melt_ttl_env: Option<u64> = None;
+        if let Ok(mint_ttl_str) = env::var(ENV_QUOTE_TTL_MINT) {
+            if let Ok(v) = mint_ttl_str.parse::<u64>() {
+                mint_ttl_env = Some(v);
+            }
+        }
+        if let Ok(melt_ttl_str) = env::var(ENV_QUOTE_TTL_MELT) {
+            if let Ok(v) = melt_ttl_str.parse::<u64>() {
+                melt_ttl_env = Some(v);
+            }
+        }
+        if mint_ttl_env.is_some() || melt_ttl_env.is_some() {
+            let current = self.quote_ttl.unwrap_or_default();
+            self.quote_ttl = Some(QuoteTTL {
+                mint_ttl: mint_ttl_env.unwrap_or(current.mint_ttl),
+                melt_ttl: melt_ttl_env.unwrap_or(current.melt_ttl),
+            });
+        }
+
         self
     }
 }

+ 15 - 0
crates/cdk-mintd/src/env_vars/mod.rs

@@ -75,6 +75,21 @@ impl Settings {
             );
         }
 
+        // Parse auth database configuration from environment variables (when auth is enabled)
+        #[cfg(feature = "auth")]
+        {
+            self.auth_database = Some(crate::config::AuthDatabase {
+                postgres: Some(
+                    self.auth_database
+                        .clone()
+                        .unwrap_or_default()
+                        .postgres
+                        .unwrap_or_default()
+                        .from_env(),
+                ),
+            });
+        }
+
         self.info = self.info.clone().from_env();
         self.mint_info = self.mint_info.clone().from_env();
         self.ln = self.ln.clone().from_env();

+ 86 - 22
crates/cdk-mintd/src/lib.rs

@@ -36,14 +36,14 @@ use cdk::nuts::CurrencyUnit;
 #[cfg(feature = "auth")]
 use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
 use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
-use cdk::types::QuoteTTL;
 use cdk_axum::cache::HttpCache;
+use cdk_common::common::QuoteTTL;
 use cdk_common::database::DynMintDatabase;
 // internal crate modules
 #[cfg(feature = "prometheus")]
 use cdk_common::payment::MetricsMintPayment;
 use cdk_common::payment::MintPayment;
-#[cfg(feature = "auth")]
+#[cfg(all(feature = "auth", feature = "postgres"))]
 use cdk_postgres::MintPgAuthDatabase;
 #[cfg(feature = "postgres")]
 use cdk_postgres::MintPgDatabase;
@@ -663,16 +663,20 @@ async fn setup_authentication(
             DatabaseEngine::Postgres => {
                 #[cfg(feature = "postgres")]
                 {
-                    // Get the PostgreSQL configuration, ensuring it exists
-                    let pg_config = settings.database.postgres.as_ref().ok_or_else(|| {
-                        anyhow!("PostgreSQL configuration is required when using PostgreSQL engine")
+                    // Require dedicated auth database configuration - no fallback to main database
+                    let auth_db_config = settings.auth_database.as_ref().ok_or_else(|| {
+                        anyhow!("Auth database configuration is required when using PostgreSQL with authentication. Set [auth_database] section in config file or CDK_MINTD_AUTH_POSTGRES_URL environment variable")
+                    })?;
+
+                    let auth_pg_config = auth_db_config.postgres.as_ref().ok_or_else(|| {
+                        anyhow!("PostgreSQL auth database configuration is required when using PostgreSQL with authentication. Set [auth_database.postgres] section in config file or CDK_MINTD_AUTH_POSTGRES_URL environment variable")
                     })?;
 
-                    if pg_config.url.is_empty() {
-                        bail!("PostgreSQL URL is required for auth database. Set it in config file [database.postgres] section or via CDK_MINTD_POSTGRES_URL/CDK_MINTD_DATABASE_URL environment variable");
+                    if auth_pg_config.url.is_empty() {
+                        bail!("Auth database PostgreSQL URL is required and cannot be empty. Set it in config file [auth_database.postgres] section or via CDK_MINTD_AUTH_POSTGRES_URL environment variable");
                     }
 
-                    Arc::new(MintPgAuthDatabase::new(pg_config.url.as_str()).await?)
+                    Arc::new(MintPgAuthDatabase::new(auth_pg_config.url.as_str()).await?)
                 }
                 #[cfg(not(feature = "postgres"))]
                 {
@@ -890,16 +894,21 @@ async fn start_services_with_shutdown(
         }
     }
 
+    // Determine the desired QuoteTTL from config/env or fall back to defaults
+    let desired_quote_ttl: QuoteTTL = settings.info.quote_ttl.unwrap_or_default();
+
     if rpc_enabled {
         if mint.mint_info().await.is_err() {
             tracing::info!("Mint info not set on mint, setting.");
+            // First boot with RPC enabled: seed from config
             mint.set_mint_info(mint_builder_info).await?;
-            mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
+            mint.set_quote_ttl(desired_quote_ttl).await?;
         } else {
-            if mint.localstore().get_quote_ttl().await.is_err() {
-                mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
+            // If QuoteTTL has never been persisted, seed it now from config
+            if !mint.quote_ttl_is_persisted().await? {
+                mint.set_quote_ttl(desired_quote_ttl).await?;
             }
-            // Add version information
+            // Add/refresh version information without altering stored mint_info fields
             let mint_version = MintVersion::new(
                 "cdk-mintd".to_string(),
                 CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
@@ -911,9 +920,18 @@ async fn start_services_with_shutdown(
             tracing::info!("Mint info already set, not using config file settings.");
         }
     } else {
-        tracing::info!("RPC not enabled, using mint info from config.");
+        // RPC disabled: config is source of truth on every boot
+        tracing::info!("RPC not enabled, using mint info and quote TTL from config.");
+        let mut mint_builder_info = mint_builder_info;
+
+        if let Ok(mint_info) = mint.mint_info().await {
+            if mint_builder_info.pubkey.is_none() {
+                mint_builder_info.pubkey = mint_info.pubkey;
+            }
+        }
+
         mint.set_mint_info(mint_builder_info).await?;
-        mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
+        mint.set_quote_ttl(desired_quote_ttl).await?;
     }
 
     let mint_info = mint.mint_info().await?;
@@ -1120,11 +1138,39 @@ pub async fn run_mintd_with_shutdown(
 
     let mint_builder = MintBuilder::new(localstore);
 
+    // If RPC is enabled and DB contains mint_info already, initialize the builder from DB.
+    // This ensures subsequent builder modifications (like version injection) can respect stored values.
+    let maybe_mint_builder = {
+        #[cfg(feature = "management-rpc")]
+        {
+            if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
+                if rpc_settings.enabled {
+                    // Best-effort: pull DB state into builder if present
+                    let mut tmp = mint_builder;
+                    if let Err(e) = tmp.init_from_db_if_present().await {
+                        tracing::warn!("Failed to init builder from DB: {}", e);
+                    }
+                    tmp
+                } else {
+                    mint_builder
+                }
+            } else {
+                mint_builder
+            }
+        }
+        #[cfg(not(feature = "management-rpc"))]
+        {
+            mint_builder
+        }
+    };
+
     let mint_builder =
-        configure_mint_builder(settings, mint_builder, runtime, work_dir, Some(kv)).await?;
+        configure_mint_builder(settings, maybe_mint_builder, runtime, work_dir, Some(kv)).await?;
     #[cfg(feature = "auth")]
     let mint_builder = setup_authentication(settings, work_dir, mint_builder, db_password).await?;
 
+    let config_mint_info = mint_builder.current_mint_info();
+
     let mint = build_mint(settings, keystore, mint_builder).await?;
 
     tracing::debug!("Mint built from builder.");
@@ -1136,19 +1182,37 @@ pub async fn run_mintd_with_shutdown(
     // Pending melt quotes where the payment has **failed** inputs are reset to unspent
     mint.check_pending_melt_quotes().await?;
 
-    let result = start_services_with_shutdown(
+    start_services_with_shutdown(
         mint.clone(),
         settings,
         work_dir,
-        mint.mint_info().await?,
+        config_mint_info,
         shutdown_signal,
         routers,
     )
-    .await;
+    .await
+}
 
-    // Ensure any remaining tracing data is flushed
-    // This is particularly important for file-based logging
-    tracing::debug!("Flushing remaining trace data");
+#[cfg(test)]
+mod tests {
+    use super::*;
 
-    result
+    #[test]
+    fn test_postgres_auth_url_validation() {
+        // Test that the auth database config requires explicit configuration
+
+        // Test empty URL
+        let auth_config = config::PostgresAuthConfig {
+            url: "".to_string(),
+            ..Default::default()
+        };
+        assert!(auth_config.url.is_empty());
+
+        // Test non-empty URL
+        let auth_config = config::PostgresAuthConfig {
+            url: "postgresql://user:password@localhost:5432/auth_db".to_string(),
+            ..Default::default()
+        };
+        assert!(!auth_config.url.is_empty());
+    }
 }

+ 7 - 0
crates/cdk-postgres/start_db_for_test.sh

@@ -27,4 +27,11 @@ docker exec -e PGPASSWORD="${DB_PASS}" "${CONTAINER_NAME}" \
 docker exec -e PGPASSWORD="${DB_PASS}" "${CONTAINER_NAME}" \
   psql -U "${DB_USER}" -d "${DB_NAME}" -c "CREATE DATABASE mintdb_auth;"
 
+# Export environment variables for both main and auth databases
 export DATABASE_URL="host=localhost user=${DB_USER} password=${DB_PASS} dbname=${DB_NAME} port=${DB_PORT}"
+export CDK_MINTD_POSTGRES_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/mintdb"
+export CDK_MINTD_AUTH_POSTGRES_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/mintdb_auth"
+
+echo "Database URLs configured:"
+echo "Main database: ${CDK_MINTD_POSTGRES_URL}"
+echo "Auth database: ${CDK_MINTD_AUTH_POSTGRES_URL}"

+ 29 - 5
crates/cdk-sql-common/build.rs

@@ -1,4 +1,5 @@
 use std::cmp::Ordering;
+use std::env;
 use std::fs::{self, File};
 use std::io::Write;
 use std::path::{Path, PathBuf};
@@ -7,11 +8,22 @@ fn main() {
     // Step 1: Find `migrations/` folder recursively
     let root = Path::new("src");
 
+    // Get the OUT_DIR from Cargo - this is writable
+    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by Cargo"));
+
     for migration_path in find_migrations_dirs(root) {
-        // Step 3: Output file path (e.g., `src/db/migrations.rs`)
+        // Step 3: Output file path to OUT_DIR instead of source directory
         let parent = migration_path.parent().unwrap();
-        let skip_path = parent.to_str().unwrap_or_default().len();
-        let dest_path = parent.join("migrations.rs");
+
+        // Create a unique filename based on the migration path to avoid conflicts
+        let migration_name = parent
+            .strip_prefix("src")
+            .unwrap_or(parent)
+            .to_str()
+            .unwrap_or("default")
+            .replace("/", "_")
+            .replace("\\", "_");
+        let dest_path = out_dir.join(format!("migrations_{}.rs", migration_name));
         let mut out_file = File::create(&dest_path).expect("Failed to create migrations.rs");
 
         let skip_name = migration_path.to_str().unwrap_or_default().len();
@@ -89,10 +101,22 @@ fn main() {
             };
 
             let rel_name = &path.file_name().unwrap().to_str().unwrap();
-            let rel_path = &path.to_str().unwrap().replace("\\", "/")[skip_path..]; // for Windows
+
+            // Copy migration file to OUT_DIR
+            let relative_path = path.strip_prefix(root).unwrap();
+            let dest_migration_file = out_dir.join(relative_path);
+            if let Some(parent) = dest_migration_file.parent() {
+                fs::create_dir_all(parent)
+                    .expect("Failed to create migration directory in OUT_DIR");
+            }
+            fs::copy(path, &dest_migration_file).expect("Failed to copy migration file to OUT_DIR");
+
+            // Use path relative to OUT_DIR for include_str
+            let relative_to_out_dir = relative_path.to_str().unwrap().replace("\\", "/");
             writeln!(
                 out_file,
-                "    (\"{prefix}\", \"{rel_name}\", include_str!(r#\".{rel_path}\"#)),"
+                "    (\"{prefix}\", \"{rel_name}\", include_str!(r#\"{}\"#)),",
+                relative_to_out_dir
             )
             .unwrap();
             println!("cargo:rerun-if-changed={}", path.display());

+ 0 - 9
crates/cdk-sql-common/src/mint/auth/migrations.rs

@@ -1,9 +0,0 @@
-/// @generated
-/// Auto-generated by build.rs
-pub static MIGRATIONS: &[(&str, &str, &str)] = &[
-    ("postgres", "1_init.sql", include_str!(r#"./migrations/postgres/1_init.sql"#)),
-    ("postgres", "20250822104351_rename_blind_message_y_to_b.sql", include_str!(r#"./migrations/postgres/20250822104351_rename_blind_message_y_to_b.sql"#)),
-    ("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
-    ("sqlite", "20250109143347_init.sql", include_str!(r#"./migrations/sqlite/20250109143347_init.sql"#)),
-    ("sqlite", "20250822104351_rename_blind_message_y_to_b.sql", include_str!(r#"./migrations/sqlite/20250822104351_rename_blind_message_y_to_b.sql"#)),
-];

+ 7 - 4
crates/cdk-sql-common/src/mint/auth/mod.rs

@@ -54,8 +54,9 @@ where
 }
 
 #[rustfmt::skip]
-mod migrations;
-
+mod migrations {
+    include!(concat!(env!("OUT_DIR"), "/migrations_mint_auth.rs"));
+}
 
 #[async_trait]
 impl<RM> MintAuthTransaction<database::Error> for SQLTransaction<RM>
@@ -197,9 +198,11 @@ where
         for (endpoint, auth) in protected_endpoints.iter() {
             if let Err(err) = query(
                 r#"
-                 INSERT OR REPLACE INTO protected_endpoints
+                 INSERT INTO protected_endpoints
                  (endpoint, auth)
-                 VALUES (:endpoint, :auth);
+                 VALUES (:endpoint, :auth)
+                 ON CONFLICT (endpoint) DO UPDATE SET
+                 auth = EXCLUDED.auth;
                  "#,
             )?
             .bind("endpoint", serde_json::to_string(endpoint)?)

+ 0 - 36
crates/cdk-sql-common/src/mint/migrations.rs

@@ -1,36 +0,0 @@
-/// @generated
-/// Auto-generated by build.rs
-pub static MIGRATIONS: &[(&str, &str, &str)] = &[
-    ("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.sql"#)),
-    ("postgres", "2_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/postgres/2_remove_request_lookup_kind_constraints.sql"#)),
-    ("postgres", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/postgres/20250901090000_add_kv_store.sql"#)),
-    ("postgres", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/postgres/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
-    ("postgres", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/postgres/20250903200000_add_signatory_amounts.sql"#)),
-    ("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
-    ("sqlite", "20240612124932_init.sql", include_str!(r#"./migrations/sqlite/20240612124932_init.sql"#)),
-    ("sqlite", "20240618195700_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618195700_quote_state.sql"#)),
-    ("sqlite", "20240626092101_nut04_state.sql", include_str!(r#"./migrations/sqlite/20240626092101_nut04_state.sql"#)),
-    ("sqlite", "20240703122347_request_lookup_id.sql", include_str!(r#"./migrations/sqlite/20240703122347_request_lookup_id.sql"#)),
-    ("sqlite", "20240710145043_input_fee.sql", include_str!(r#"./migrations/sqlite/20240710145043_input_fee.sql"#)),
-    ("sqlite", "20240711183109_derivation_path_index.sql", include_str!(r#"./migrations/sqlite/20240711183109_derivation_path_index.sql"#)),
-    ("sqlite", "20240718203721_allow_unspent.sql", include_str!(r#"./migrations/sqlite/20240718203721_allow_unspent.sql"#)),
-    ("sqlite", "20240811031111_update_mint_url.sql", include_str!(r#"./migrations/sqlite/20240811031111_update_mint_url.sql"#)),
-    ("sqlite", "20240919103407_proofs_quote_id.sql", include_str!(r#"./migrations/sqlite/20240919103407_proofs_quote_id.sql"#)),
-    ("sqlite", "20240923153640_melt_requests.sql", include_str!(r#"./migrations/sqlite/20240923153640_melt_requests.sql"#)),
-    ("sqlite", "20240930101140_dleq_for_sigs.sql", include_str!(r#"./migrations/sqlite/20240930101140_dleq_for_sigs.sql"#)),
-    ("sqlite", "20241108093102_mint_mint_quote_pubkey.sql", include_str!(r#"./migrations/sqlite/20241108093102_mint_mint_quote_pubkey.sql"#)),
-    ("sqlite", "20250103201327_amount_to_pay_msats.sql", include_str!(r#"./migrations/sqlite/20250103201327_amount_to_pay_msats.sql"#)),
-    ("sqlite", "20250129200912_remove_mint_url.sql", include_str!(r#"./migrations/sqlite/20250129200912_remove_mint_url.sql"#)),
-    ("sqlite", "20250129230326_add_config_table.sql", include_str!(r#"./migrations/sqlite/20250129230326_add_config_table.sql"#)),
-    ("sqlite", "20250307213652_keyset_id_as_foreign_key.sql", include_str!(r#"./migrations/sqlite/20250307213652_keyset_id_as_foreign_key.sql"#)),
-    ("sqlite", "20250406091754_mint_time_of_quotes.sql", include_str!(r#"./migrations/sqlite/20250406091754_mint_time_of_quotes.sql"#)),
-    ("sqlite", "20250406093755_mint_created_time_signature.sql", include_str!(r#"./migrations/sqlite/20250406093755_mint_created_time_signature.sql"#)),
-    ("sqlite", "20250415093121_drop_keystore_foreign.sql", include_str!(r#"./migrations/sqlite/20250415093121_drop_keystore_foreign.sql"#)),
-    ("sqlite", "20250626120251_rename_blind_message_y_to_b.sql", include_str!(r#"./migrations/sqlite/20250626120251_rename_blind_message_y_to_b.sql"#)),
-    ("sqlite", "20250706101057_bolt12.sql", include_str!(r#"./migrations/sqlite/20250706101057_bolt12.sql"#)),
-    ("sqlite", "20250812132015_drop_melt_request.sql", include_str!(r#"./migrations/sqlite/20250812132015_drop_melt_request.sql"#)),
-    ("sqlite", "20250819200000_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/sqlite/20250819200000_remove_request_lookup_kind_constraints.sql"#)),
-    ("sqlite", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/sqlite/20250901090000_add_kv_store.sql"#)),
-    ("sqlite", "20250902140000_add_melt_request_and_blinded_messages.sql", include_str!(r#"./migrations/sqlite/20250902140000_add_melt_request_and_blinded_messages.sql"#)),
-    ("sqlite", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/sqlite/20250903200000_add_signatory_amounts.sql"#)),
-];

+ 1 - 0
crates/cdk-sql-common/src/mint/migrations/postgres/20250916221000_drop_config_table.sql

@@ -0,0 +1 @@
+DROP TABLE IF EXISTS config;

+ 1 - 0
crates/cdk-sql-common/src/mint/migrations/sqlite/20250916221000_drop_config_table.sql

@@ -0,0 +1 @@
+DROP TABLE IF EXISTS config;

+ 6 - 101
crates/cdk-sql-common/src/mint/mod.rs

@@ -15,7 +15,6 @@ use std::sync::Arc;
 
 use async_trait::async_trait;
 use bitcoin::bip32::DerivationPath;
-use cdk_common::common::QuoteTTL;
 use cdk_common::database::mint::validate_kvstore_params;
 use cdk_common::database::{
     self, ConversionError, Error, MintDatabase, MintDbWriterFinalizer, MintKeyDatabaseTransaction,
@@ -33,7 +32,7 @@ use cdk_common::state::check_state_transition;
 use cdk_common::util::unix_time;
 use cdk_common::{
     Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
-    MintInfo, PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
+    PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
 };
 use lightning_invoice::Bolt11Invoice;
 use migrations::MIGRATIONS;
@@ -52,8 +51,9 @@ use crate::{
 mod auth;
 
 #[rustfmt::skip]
-mod migrations;
-
+mod migrations {
+    include!(concat!(env!("OUT_DIR"), "/migrations_mint.rs"));
+}
 
 #[cfg(feature = "auth")]
 pub use auth::SQLMintAuthDatabase;
@@ -102,26 +102,6 @@ where
         .collect::<Result<HashMap<_, _>, _>>()
 }
 
-#[inline(always)]
-async fn set_to_config<C, V>(conn: &C, id: &str, value: &V) -> Result<(), Error>
-where
-    C: DatabaseExecutor + Send + Sync,
-    V: ?Sized + serde::Serialize,
-{
-    query(
-        r#"
-        INSERT INTO config (id, value) VALUES (:id, :value)
-            ON CONFLICT(id) DO UPDATE SET value = excluded.value
-            "#,
-    )?
-    .bind("id", id.to_owned())
-    .bind("value", serde_json::to_string(&value)?)
-    .execute(conn)
-    .await?;
-
-    Ok(())
-}
-
 impl<RM> SQLMintDatabase<RM>
 where
     RM: DatabasePool + 'static,
@@ -145,21 +125,6 @@ where
         tx.commit().await?;
         Ok(())
     }
-
-    #[inline(always)]
-    async fn fetch_from_config<R>(&self, id: &str) -> Result<R, Error>
-    where
-        R: serde::de::DeserializeOwned,
-    {
-        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
-        let value = column_as_string!(query(r#"SELECT value FROM config WHERE id = :id LIMIT 1"#)?
-            .bind("id", id.to_owned())
-            .pluck(&*conn)
-            .await?
-            .ok_or(Error::UnknownQuoteTTL)?);
-
-        Ok(serde_json::from_str(&value)?)
-    }
 }
 
 #[async_trait]
@@ -307,18 +272,8 @@ where
 }
 
 #[async_trait]
-impl<RM> database::MintTransaction<'_, Error> for SQLTransaction<RM>
-where
-    RM: DatabasePool + 'static,
-{
-    async fn set_mint_info(&mut self, mint_info: MintInfo) -> Result<(), Error> {
-        Ok(set_to_config(&self.inner, "mint_info", &mint_info).await?)
-    }
-
-    async fn set_quote_ttl(&mut self, quote_ttl: QuoteTTL) -> Result<(), Error> {
-        Ok(set_to_config(&self.inner, "quote_ttl", &quote_ttl).await?)
-    }
-}
+impl<RM> database::MintTransaction<'_, Error> for SQLTransaction<RM> where RM: DatabasePool + 'static
+{}
 
 #[async_trait]
 impl<RM> MintDbWriterFinalizer for SQLTransaction<RM>
@@ -2048,56 +2003,6 @@ where
 
         Ok(Box::new(tx))
     }
-
-    async fn get_mint_info(&self) -> Result<MintInfo, Error> {
-        #[cfg(feature = "prometheus")]
-        METRICS.inc_in_flight_requests("get_mint_info");
-
-        #[cfg(feature = "prometheus")]
-        let start_time = std::time::Instant::now();
-
-        let result = self.fetch_from_config("mint_info").await;
-
-        #[cfg(feature = "prometheus")]
-        {
-            let success = result.is_ok();
-
-            METRICS.record_mint_operation("get_mint_info", success);
-            METRICS.record_mint_operation_histogram(
-                "get_mint_info",
-                success,
-                start_time.elapsed().as_secs_f64(),
-            );
-            METRICS.dec_in_flight_requests("get_mint_info");
-        }
-
-        Ok(result?)
-    }
-
-    async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error> {
-        #[cfg(feature = "prometheus")]
-        METRICS.inc_in_flight_requests("get_quote_ttl");
-
-        #[cfg(feature = "prometheus")]
-        let start_time = std::time::Instant::now();
-
-        let result = self.fetch_from_config("quote_ttl").await;
-
-        #[cfg(feature = "prometheus")]
-        {
-            let success = result.is_ok();
-
-            METRICS.record_mint_operation("get_quote_ttl", success);
-            METRICS.record_mint_operation_histogram(
-                "get_quote_ttl",
-                success,
-                start_time.elapsed().as_secs_f64(),
-            );
-            METRICS.dec_in_flight_requests("get_quote_ttl");
-        }
-
-        Ok(result?)
-    }
 }
 
 fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo, Error> {

+ 0 - 29
crates/cdk-sql-common/src/wallet/migrations.rs

@@ -1,29 +0,0 @@
-/// @generated
-/// Auto-generated by build.rs
-pub static MIGRATIONS: &[(&str, &str, &str)] = &[
-    ("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.sql"#)),
-    ("postgres", "20250831215438_melt_quote_method.sql", include_str!(r#"./migrations/postgres/20250831215438_melt_quote_method.sql"#)),
-    ("postgres", "20250906200000_add_transaction_quote_id.sql", include_str!(r#"./migrations/postgres/20250906200000_add_transaction_quote_id.sql"#)),
-    ("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
-    ("sqlite", "20240612132920_init.sql", include_str!(r#"./migrations/sqlite/20240612132920_init.sql"#)),
-    ("sqlite", "20240618200350_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618200350_quote_state.sql"#)),
-    ("sqlite", "20240626091921_nut04_state.sql", include_str!(r#"./migrations/sqlite/20240626091921_nut04_state.sql"#)),
-    ("sqlite", "20240710144711_input_fee.sql", include_str!(r#"./migrations/sqlite/20240710144711_input_fee.sql"#)),
-    ("sqlite", "20240810214105_mint_icon_url.sql", include_str!(r#"./migrations/sqlite/20240810214105_mint_icon_url.sql"#)),
-    ("sqlite", "20240810233905_update_mint_url.sql", include_str!(r#"./migrations/sqlite/20240810233905_update_mint_url.sql"#)),
-    ("sqlite", "20240902151515_icon_url.sql", include_str!(r#"./migrations/sqlite/20240902151515_icon_url.sql"#)),
-    ("sqlite", "20240902210905_mint_time.sql", include_str!(r#"./migrations/sqlite/20240902210905_mint_time.sql"#)),
-    ("sqlite", "20241011125207_mint_urls.sql", include_str!(r#"./migrations/sqlite/20241011125207_mint_urls.sql"#)),
-    ("sqlite", "20241108092756_wallet_mint_quote_secretkey.sql", include_str!(r#"./migrations/sqlite/20241108092756_wallet_mint_quote_secretkey.sql"#)),
-    ("sqlite", "20250214135017_mint_tos.sql", include_str!(r#"./migrations/sqlite/20250214135017_mint_tos.sql"#)),
-    ("sqlite", "20250310111513_drop_nostr_last_checked.sql", include_str!(r#"./migrations/sqlite/20250310111513_drop_nostr_last_checked.sql"#)),
-    ("sqlite", "20250314082116_allow_pending_spent.sql", include_str!(r#"./migrations/sqlite/20250314082116_allow_pending_spent.sql"#)),
-    ("sqlite", "20250323152040_wallet_dleq_proofs.sql", include_str!(r#"./migrations/sqlite/20250323152040_wallet_dleq_proofs.sql"#)),
-    ("sqlite", "20250401120000_add_transactions_table.sql", include_str!(r#"./migrations/sqlite/20250401120000_add_transactions_table.sql"#)),
-    ("sqlite", "20250616144830_add_keyset_expiry.sql", include_str!(r#"./migrations/sqlite/20250616144830_add_keyset_expiry.sql"#)),
-    ("sqlite", "20250707093445_bolt12.sql", include_str!(r#"./migrations/sqlite/20250707093445_bolt12.sql"#)),
-    ("sqlite", "20250729111701_keyset_v2_u32.sql", include_str!(r#"./migrations/sqlite/20250729111701_keyset_v2_u32.sql"#)),
-    ("sqlite", "20250812084621_keyset_plus_one.sql", include_str!(r#"./migrations/sqlite/20250812084621_keyset_plus_one.sql"#)),
-    ("sqlite", "20250831215438_melt_quote_method.sql", include_str!(r#"./migrations/sqlite/20250831215438_melt_quote_method.sql"#)),
-    ("sqlite", "20250906200000_add_transaction_quote_id.sql", include_str!(r#"./migrations/sqlite/20250906200000_add_transaction_quote_id.sql"#)),
-];

+ 3 - 1
crates/cdk-sql-common/src/wallet/mod.rs

@@ -28,7 +28,9 @@ use crate::{
 };
 
 #[rustfmt::skip]
-mod migrations;
+mod migrations {
+    include!(concat!(env!("OUT_DIR"), "/migrations_wallet.rs"));
+}
 
 /// Wallet SQLite Database
 #[derive(Debug, Clone)]

+ 12 - 1
crates/cdk-sqlite/src/mint/memory.rs

@@ -8,6 +8,10 @@ use cdk_common::MintInfo;
 
 use super::MintSqliteDatabase;
 
+const CDK_MINT_PRIMARY_NAMESPACE: &str = "cdk_mint";
+const CDK_MINT_CONFIG_SECONDARY_NAMESPACE: &str = "config";
+const CDK_MINT_CONFIG_KV_KEY: &str = "mint_info";
+
 /// Creates a new in-memory [`MintSqliteDatabase`] instance
 pub async fn empty() -> Result<MintSqliteDatabase, database::Error> {
     #[cfg(not(feature = "sqlcipher"))]
@@ -54,7 +58,14 @@ pub async fn new_with_state(
 
     tx.add_proofs(pending_proofs, None).await?;
     tx.add_proofs(spent_proofs, None).await?;
-    tx.set_mint_info(mint_info).await?;
+    let mint_info_bytes = serde_json::to_vec(&mint_info)?;
+    tx.kv_write(
+        CDK_MINT_PRIMARY_NAMESPACE,
+        CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+        CDK_MINT_CONFIG_KV_KEY,
+        &mint_info_bytes,
+    )
+    .await?;
     tx.commit().await?;
 
     Ok(db)

+ 33 - 0
crates/cdk/src/mint/builder.rs

@@ -82,6 +82,31 @@ impl MintBuilder {
         self
     }
 
+    /// Initialize builder's MintInfo from the database if present.
+    /// If not present or parsing fails, keeps the current MintInfo.
+    pub async fn init_from_db_if_present(&mut self) -> Result<(), cdk_database::Error> {
+        // Attempt to read existing mint_info from the KV store
+        let bytes_opt = self
+            .localstore
+            .kv_read(
+                super::CDK_MINT_PRIMARY_NAMESPACE,
+                super::CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                super::CDK_MINT_CONFIG_KV_KEY,
+            )
+            .await?;
+
+        if let Some(bytes) = bytes_opt {
+            if let Ok(info) = serde_json::from_slice::<MintInfo>(&bytes) {
+                self.mint_info = info;
+            } else {
+                // If parsing fails, leave the current builder state untouched
+                tracing::warn!("Failed to parse existing mint_info from DB; using builder state");
+            }
+        }
+
+        Ok(())
+    }
+
     /// Set blind auth settings
     #[cfg(feature = "auth")]
     pub fn with_blind_auth(
@@ -128,6 +153,14 @@ impl MintBuilder {
         self
     }
 
+    /// Get a clone of the current MintInfo configured on the builder
+    /// This allows using config-derived settings to initialize persistent state
+    /// before any attempt to read from the database, which avoids first-run
+    /// failures when the DB is empty.
+    pub fn current_mint_info(&self) -> MintInfo {
+        self.mint_info.clone()
+    }
+
     /// Set terms of service URL
     pub fn with_tos_url(mut self, tos_url: String) -> Self {
         self.mint_info.tos_url = Some(tos_url);

+ 2 - 2
crates/cdk/src/mint/issue/mod.rs

@@ -168,7 +168,7 @@ impl Mint {
         &self,
         mint_quote_request: &MintQuoteRequest,
     ) -> Result<(), Error> {
-        let mint_info = self.localstore.get_mint_info().await?;
+        let mint_info = self.mint_info().await?;
 
         let unit = mint_quote_request.unit();
         let amount = mint_quote_request.amount();
@@ -246,7 +246,7 @@ impl Mint {
 
             let payment_options = match mint_quote_request {
                 MintQuoteRequest::Bolt11(bolt11_request) => {
-                    let mint_ttl = self.localstore.get_quote_ttl().await?.mint_ttl;
+                    let mint_ttl = self.quote_ttl().await?.mint_ttl;
 
                     let quote_expiry = unix_time() + mint_ttl;
 

+ 3 - 3
crates/cdk/src/mint/melt.rs

@@ -43,7 +43,7 @@ impl Mint {
         request: String,
         options: Option<MeltOptions>,
     ) -> Result<(), Error> {
-        let mint_info = self.localstore.get_mint_info().await?;
+        let mint_info = self.mint_info().await?;
         let nut05 = mint_info.nuts.nut05;
 
         ensure_cdk!(!nut05.disabled, Error::MeltingDisabled);
@@ -196,7 +196,7 @@ impl Mint {
                 Error::UnsupportedUnit
             })?;
 
-        let melt_ttl = self.localstore.get_quote_ttl().await?.melt_ttl;
+        let melt_ttl = self.quote_ttl().await?.melt_ttl;
 
         let quote = MeltQuote::new(
             MeltPaymentRequest::Bolt11 {
@@ -856,7 +856,7 @@ impl Mint {
     }
 
     /// Process melt request marking proofs as spent
-    /// The melt request must be verifyed using [`Self::verify_melt_request`]
+    /// The melt request must be verified using [`Self::verify_melt_request`]
     /// before calling [`Self::process_melt_request`]
     #[instrument(skip_all)]
     pub async fn process_melt_request(

+ 119 - 26
crates/cdk/src/mint/mod.rs

@@ -50,6 +50,11 @@ pub use builder::{MintBuilder, MintMeltLimits};
 pub use cdk_common::mint::{MeltQuote, MintKeySetInfo, MintQuote};
 pub use verification::Verification;
 
+const CDK_MINT_PRIMARY_NAMESPACE: &str = "cdk_mint";
+const CDK_MINT_CONFIG_SECONDARY_NAMESPACE: &str = "config";
+const CDK_MINT_CONFIG_KV_KEY: &str = "mint_info";
+const CDK_MINT_QUOTE_TTL_KV_KEY: &str = "quote_ttl";
+
 /// Cashu Mint
 #[derive(Clone)]
 pub struct Mint {
@@ -150,26 +155,60 @@ impl Mint {
                 .count()
         );
 
-        let mint_info = if mint_info.pubkey.is_none() {
-            let mut info = mint_info;
-            info.pubkey = Some(keysets.pubkey);
-            info
-        } else {
-            mint_info
-        };
+        // Persist missing pubkey early to avoid losing it on next boot and ensure stable identity across restarts
+        let mut computed_info = mint_info;
+        if computed_info.pubkey.is_none() {
+            computed_info.pubkey = Some(keysets.pubkey);
+        }
 
-        let mint_store = localstore.clone();
-        let mut tx = mint_store.begin_transaction().await?;
-        tx.set_mint_info(mint_info.clone()).await?;
-        tx.set_quote_ttl(QuoteTTL::default()).await?;
-        tx.commit().await?;
+        match localstore
+            .kv_read(
+                CDK_MINT_PRIMARY_NAMESPACE,
+                CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                CDK_MINT_CONFIG_KV_KEY,
+            )
+            .await?
+        {
+            Some(bytes) => {
+                let mut stored: MintInfo = serde_json::from_slice(&bytes)?;
+                let mut mutated = false;
+                if stored.pubkey.is_none() && computed_info.pubkey.is_some() {
+                    stored.pubkey = computed_info.pubkey;
+                    mutated = true;
+                }
+                if mutated {
+                    let updated = serde_json::to_vec(&stored)?;
+                    let mut tx = localstore.begin_transaction().await?;
+                    tx.kv_write(
+                        CDK_MINT_PRIMARY_NAMESPACE,
+                        CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                        CDK_MINT_CONFIG_KV_KEY,
+                        &updated,
+                    )
+                    .await?;
+                    tx.commit().await?;
+                }
+            }
+            None => {
+                let bytes = serde_json::to_vec(&computed_info)?;
+                let mut tx = localstore.begin_transaction().await?;
+                tx.kv_write(
+                    CDK_MINT_PRIMARY_NAMESPACE,
+                    CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                    CDK_MINT_CONFIG_KV_KEY,
+                    &bytes,
+                )
+                .await?;
+                tx.commit().await?;
+            }
+        }
 
         Ok(Self {
             signatory,
             pubsub_manager: Arc::new(localstore.clone().into()),
             localstore,
             #[cfg(feature = "auth")]
-            oidc_client: mint_info.nuts.nut21.as_ref().map(|nut21| {
+            oidc_client: computed_info.nuts.nut21.as_ref().map(|nut21| {
                 OidcClient::new(
                     nut21.openid_discovery.clone(),
                     Some(nut21.client_id.clone()),
@@ -373,7 +412,17 @@ impl Mint {
     /// Get mint info
     #[instrument(skip_all)]
     pub async fn mint_info(&self) -> Result<MintInfo, Error> {
-        let mint_info = self.localstore.get_mint_info().await?;
+        let mint_info = self
+            .localstore
+            .kv_read(
+                CDK_MINT_PRIMARY_NAMESPACE,
+                CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                CDK_MINT_CONFIG_KV_KEY,
+            )
+            .await?
+            .ok_or(Error::CouldNotGetMintInfo)?;
+
+        let mint_info: MintInfo = serde_json::from_slice(&mint_info)?;
 
         #[cfg(feature = "auth")]
         let mint_info = if let Some(auth_db) = self.auth_localstore.as_ref() {
@@ -415,27 +464,78 @@ impl Mint {
     /// Set mint info
     #[instrument(skip_all)]
     pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
+        tracing::info!("Updating mint info");
+        let mint_info_bytes = serde_json::to_vec(&mint_info)?;
         let mut tx = self.localstore.begin_transaction().await?;
-        tx.set_mint_info(mint_info).await?;
-        Ok(tx.commit().await?)
+        tx.kv_write(
+            CDK_MINT_PRIMARY_NAMESPACE,
+            CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+            CDK_MINT_CONFIG_KV_KEY,
+            &mint_info_bytes,
+        )
+        .await?;
+        tx.commit().await?;
+        Ok(())
     }
 
     /// Get quote ttl
     #[instrument(skip_all)]
     pub async fn quote_ttl(&self) -> Result<QuoteTTL, Error> {
-        Ok(self.localstore.get_quote_ttl().await?)
+        let quote_ttl_bytes = self
+            .localstore
+            .kv_read(
+                CDK_MINT_PRIMARY_NAMESPACE,
+                CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                CDK_MINT_QUOTE_TTL_KV_KEY,
+            )
+            .await?;
+
+        match quote_ttl_bytes {
+            Some(bytes) => {
+                let quote_ttl: QuoteTTL = serde_json::from_slice(&bytes)?;
+                Ok(quote_ttl)
+            }
+            None => {
+                // Return default if not found
+                Ok(QuoteTTL::default())
+            }
+        }
     }
 
     /// Set quote ttl
     #[instrument(skip_all)]
     pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
+        let quote_ttl_bytes = serde_json::to_vec(&quote_ttl)?;
         let mut tx = self.localstore.begin_transaction().await?;
-        tx.set_quote_ttl(quote_ttl).await?;
-        Ok(tx.commit().await?)
+        tx.kv_write(
+            CDK_MINT_PRIMARY_NAMESPACE,
+            CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+            CDK_MINT_QUOTE_TTL_KV_KEY,
+            &quote_ttl_bytes,
+        )
+        .await?;
+        tx.commit().await?;
+        Ok(())
     }
 
     /// For each backend starts a task that waits for any invoice to be paid
     /// Once invoice is paid mint quote status is updated
+    /// Returns true if a QuoteTTL is persisted in the database. This is used to avoid overwriting
+    /// explicit configuration with defaults when the TTL has already been set by an operator.
+    #[instrument(skip_all)]
+    pub async fn quote_ttl_is_persisted(&self) -> Result<bool, Error> {
+        let quote_ttl_bytes = self
+            .localstore
+            .kv_read(
+                CDK_MINT_PRIMARY_NAMESPACE,
+                CDK_MINT_CONFIG_SECONDARY_NAMESPACE,
+                CDK_MINT_QUOTE_TTL_KV_KEY,
+            )
+            .await?;
+
+        Ok(quote_ttl_bytes.is_some())
+    }
+
     #[instrument(skip_all)]
     async fn wait_for_paid_invoices(
         payment_processors: &HashMap<PaymentProcessorKey, DynMintPayment>,
@@ -627,13 +727,6 @@ impl Mint {
                     return Err(Error::AmountUndefined);
                 }
 
-                if mint_quote.payment_method == PaymentMethod::Bolt11
-                    && mint_quote.amount != Some(payment_amount_quote_unit)
-                {
-                    tracing::error!("Bolt11 incoming payment should equal mint quote.");
-                    return Err(Error::IncorrectQuoteAmount);
-                }
-
                 tracing::debug!(
                     "Payment received amount in quote unit {} {}",
                     mint_quote.unit,

+ 12 - 6
crates/cdk/src/wallet/multi_mint_wallet.rs

@@ -242,7 +242,13 @@ impl MultiMintWallet {
             if mint_has_proofs_for_unit {
                 // Add mint to the MultiMintWallet if not already present
                 if !self.has_mint(&mint_url).await {
-                    self.add_mint(mint_url, None).await?;
+                    if let Err(err) = self.add_mint(mint_url.clone(), None).await {
+                        tracing::error!(
+                            "Could not add {} to wallet {}.",
+                            mint_url,
+                            err.to_string()
+                        );
+                    }
                 }
             }
         }
@@ -801,17 +807,17 @@ impl MultiMintWallet {
     /// # async fn example(wallet: MultiMintWallet) -> Result<(), Box<dyn std::error::Error>> {
     /// // Receive from a trusted mint
     /// let token = "cashuAey...";
-    /// let amount = wallet.receive(token, MultiMintReceiveOptions::default()).await?;
+    /// let amount = wallet
+    ///     .receive(token, MultiMintReceiveOptions::default())
+    ///     .await?;
     ///
     /// // Receive from untrusted mint and add it to the wallet
-    /// let options = MultiMintReceiveOptions::default()
-    ///     .allow_untrusted(true);
+    /// let options = MultiMintReceiveOptions::default().allow_untrusted(true);
     /// let amount = wallet.receive(token, options).await?;
     ///
     /// // Receive from untrusted mint, transfer to trusted mint, then remove untrusted mint
     /// let trusted_mint: MintUrl = "https://trusted.mint".parse()?;
-    /// let options = MultiMintReceiveOptions::default()
-    ///     .transfer_to_mint(Some(trusted_mint));
+    /// let options = MultiMintReceiveOptions::default().transfer_to_mint(Some(trusted_mint));
     /// let amount = wallet.receive(token, options).await?;
     /// # Ok(())
     /// # }

+ 118 - 0
docker-compose.ldk-node.yaml

@@ -0,0 +1,118 @@
+version: '3.8'
+
+services:
+  # CDK Mint service with LDK Node backend
+
+  prometheus:
+    image: prom/prometheus:latest
+    ports:
+      - "9090:9090"
+    volumes:
+      - ./misc/provisioning/prometheus.yml:/etc/prometheus/prometheus.yml:ro
+    command:
+      - '--config.file=/etc/prometheus/prometheus.yml'
+      - '--storage.tsdb.path=/prometheus'
+      - '--web.console.libraries=/etc/prometheus/console_libraries'
+      - '--web.console.templates=/etc/prometheus/consoles'
+      - '--web.enable-lifecycle'
+      - '--enable-feature=otlp-write-receiver'
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
+    networks:
+      - cdk
+
+  # Grafana for visualization
+  grafana:
+    image: grafana/grafana:latest
+    ports:
+      - "3011:3000"
+    volumes:
+      - ./misc/provisioning/datasources:/etc/grafana/provisioning/datasources
+      - ./misc/provisioning/dashboards:/etc/grafana/provisioning/dashboards
+    environment:
+      - GF_DASHBOARDS_JSON_ENABLED=true
+      - GF_SECURITY_ADMIN_PASSWORD=admin
+      - GF_PROVISIONING_PATHS=/etc/grafana/provisioning
+    networks:
+      - cdk
+
+  mintd-ldk-node:
+    # Use the ldk-node tagged image from the same repository
+    image: cashubtc/mintd:ldk-node-amd64
+    # Alternatively, build locally:
+    # build:
+    #   context: .
+    #   dockerfile: Dockerfile.ldk-node
+    container_name: mint-ldk-node
+    ports:
+      - "8085:8085"
+    environment:
+      - CDK_MINTD_URL=https://example.com
+      - CDK_MINTD_LN_BACKEND=ldk-node
+      - CDK_MINTD_LISTEN_HOST=0.0.0.0
+      - CDK_MINTD_LISTEN_PORT=8085
+      - CDK_MINTD_MNEMONIC=
+      # Database configuration - choose one:
+      # Option 1: SQLite (embedded, no additional setup needed)
+      - CDK_MINTD_DATABASE=sqlite
+      # Option 2: ReDB (embedded, no additional setup needed)
+      # - CDK_MINTD_DATABASE=redb
+      # Option 3: PostgreSQL (requires postgres service, enable with: docker-compose --profile postgres up)
+      # - CDK_MINTD_DATABASE=postgres
+      # - CDK_MINTD_DATABASE_URL=postgresql://cdk_user:cdk_password@postgres:5432/cdk_mint
+      # Cache configuration
+      - CDK_MINTD_CACHE_BACKEND=memory
+      - CDK_MINTD_PROMETHEUS_ENABLED=true
+      - CDK_MINTD_PROMETHEUS_ADDRESS=0.0.0.0
+      - CDK_MINTD_PROMETHEUS_PORT=9000
+      # LDK Node specific configuration
+      - CDK_MINTD_LDK_NODE_NETWORK=testnet
+      - CDK_MINTD_LDK_NODE_ESPLORA_URL=https://blockstream.info/testnet/api
+      - CDK_MINTD_LDK_NODE_LISTENING_ADDRESSES=0.0.0.0:9735
+    volumes:
+      # Persist LDK node data
+      - ldk_node_data:/usr/src/app/ldk_node_data
+    command: ["cdk-mintd"]
+    depends_on:
+      - prometheus
+      - grafana
+    networks:
+      - cdk
+    # Uncomment when using PostgreSQL:
+    # depends_on:
+    #   - postgres
+
+  # PostgreSQL database service
+  # Enable with: docker-compose --profile postgres up
+  postgres:
+    image: postgres:16-alpine
+    container_name: mint_postgres
+    restart: unless-stopped
+    profiles:
+      - postgres
+    environment:
+      - POSTGRES_USER=cdk_user
+      - POSTGRES_PASSWORD=cdk_password
+      - POSTGRES_DB=cdk_mint
+      - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
+    ports:
+      - "5432:5432"
+    volumes:
+      - postgres_data:/var/lib/postgresql/data
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U cdk_user -d cdk_mint"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+    networks:
+      - cdk
+
+volumes:
+  postgres_data:
+    driver: local
+  ldk_node_data:
+    driver: local
+
+networks:
+  cdk:
+    driver: bridge

+ 40 - 25
docker-compose.yaml

@@ -58,9 +58,6 @@ services:
       # - CDK_MINTD_DATABASE_URL=postgresql://cdk_user:cdk_password@postgres:5432/cdk_mint
       # Cache configuration
       - CDK_MINTD_CACHE_BACKEND=memory
-      # For Redis cache (requires redis service, enable with: docker-compose --profile redis up):
-      # - CDK_MINTD_CACHE_REDIS_URL=redis://redis:6379
-      # - CDK_MINTD_CACHE_REDIS_KEY_PREFIX=cdk-mintd
       - CDK_MINTD_PROMETHEUS_ENABLED=true
       - CDK_MINTD_PROMETHEUS_ADDRESS=0.0.0.0
       - CDK_MINTD_PROMETHEUS_PORT=9000
@@ -74,6 +71,43 @@ services:
     # depends_on:
     #   - postgres
 
+  # LDK Node mint service - enable with: docker-compose --profile ldk-node up
+  mintd-ldk-node:
+    build:
+      context: .
+      dockerfile: Dockerfile.ldk-node
+    container_name: mint-ldk-node
+    profiles:
+      - ldk-node
+    ports:
+      - "8086:8085"  # Different port to avoid conflict with main mint
+    environment:
+      - CDK_MINTD_URL=https://example.com
+      - CDK_MINTD_LN_BACKEND=ldk-node
+      - CDK_MINTD_LISTEN_HOST=0.0.0.0
+      - CDK_MINTD_LISTEN_PORT=8085
+      - CDK_MINTD_MNEMONIC=
+      # Database configuration
+      - CDK_MINTD_DATABASE=sqlite
+      # Cache configuration
+      - CDK_MINTD_CACHE_BACKEND=memory
+      - CDK_MINTD_PROMETHEUS_ENABLED=true
+      - CDK_MINTD_PROMETHEUS_ADDRESS=0.0.0.0
+      - CDK_MINTD_PROMETHEUS_PORT=9000
+      # LDK Node specific configuration
+      - CDK_MINTD_LDK_NODE_NETWORK=testnet
+      - CDK_MINTD_LDK_NODE_ESPLORA_URL=https://blockstream.info/testnet/api
+      - CDK_MINTD_LDK_NODE_LISTENING_ADDRESSES=0.0.0.0:9735
+    volumes:
+      # Persist LDK node data
+      - ldk_node_data:/usr/src/app/ldk_node_data
+    command: ["cdk-mintd"]
+    depends_on:
+      - prometheus
+      - grafana
+    networks:
+      - cdk
+
   # PostgreSQL database service
   # Enable with: docker-compose --profile postgres up
   # postgres:
@@ -97,32 +131,13 @@ services:
   #     timeout: 5s
   #     retries: 5
 
-  # Redis cache service (optional)
-  # Enable with: docker-compose --profile redis up
-#   redis:
-#     image: redis:7-alpine
-#     container_name: mint_redis
-#     restart: unless-stopped
-#     profiles:
-#       - redis
-#     ports:
-#       - "6379:6379"
-#     volumes:
-#       - redis_data:/data
-#     command: redis-server --save 60 1 --loglevel warning
-#     healthcheck:
-#       test: ["CMD", "redis-cli", "ping"]
-#       interval: 10s
-#       timeout: 3s
-#       retries: 5
+
 
 volumes:
   postgres_data:
     driver: local
-#   redis_data:
-#     driver: local
-
-
+  ldk_node_data:
+    driver: local
 
 networks:
   cdk:

+ 2 - 0
justfile

@@ -322,6 +322,7 @@ release m="":
 
   args=(
     "-p cashu"
+    "-p cdk-prometheus"
     "-p cdk-common"
     "-p cdk-sql-common"
     "-p cdk-sqlite"
@@ -329,6 +330,7 @@ release m="":
     "-p cdk-redb"
     "-p cdk-signatory"
     "-p cdk"
+    "-p cdk-ffi"
     "-p cdk-axum"
     "-p cdk-mint-rpc"
     "-p cdk-cln"