Jelajahi Sumber

Mintd lib (#914)

* feat(cdk-integration-tests): refactor regtest setup and mintd integration

- Replace shell-based regtest setup with Rust binary (start_regtest_mints)
- Add cdk-mintd crate to workspace and integration tests
- Improve environment variable handling for test configurations
- Update integration tests to use proper temp directory management
- Remove deprecated start_regtest.rs binary
- Enhance CLN client connection with retry logic
- Simplify regtest shell script (itests.sh) to use new binary
- Fix tracing filters and improve error handling in setup
- Update dependencies and configurations for integration tests

fix: killing

chore: comment tests for ci debugging

chore: compile

Revert "chore: comment tests for ci debugging"

This reverts commit bfc594c11cf37caeaa6445cb854ae5567d2da6bd.

* chore: sql cipher

* fix: removal of sqlite cipher

* fix: auth password

* refactor(cdk-mintd): improve database password handling and function signatures

- Pass database password as parameter instead of parsing CLI args in setup_database
- Update function signatures for run_mintd and run_mintd_with_shutdown to accept db_password
- Remove direct CLI parsing from database setup logic
- Fix auth database initialization to use correct type when sqlcipher feature enabled
thesimplekid 3 bulan lalu
induk
melakukan
3a3cd88ee9
33 mengubah file dengan 2469 tambahan dan 1210 penghapusan
  1. 10 0
      CHANGELOG.md
  2. 1 0
      Cargo.toml
  3. 4 1
      crates/cdk-integration-tests/Cargo.toml
  4. 163 0
      crates/cdk-integration-tests/src/bin/start_fake_auth_mint.rs
  5. 178 0
      crates/cdk-integration-tests/src/bin/start_fake_mint.rs
  6. 41 21
      crates/cdk-integration-tests/src/bin/start_regtest.rs
  7. 305 0
      crates/cdk-integration-tests/src/bin/start_regtest_mints.rs
  8. 42 0
      crates/cdk-integration-tests/src/cli.rs
  9. 2 2
      crates/cdk-integration-tests/src/init_pure_tests.rs
  10. 69 29
      crates/cdk-integration-tests/src/init_regtest.rs
  11. 42 31
      crates/cdk-integration-tests/src/lib.rs
  12. 266 0
      crates/cdk-integration-tests/src/shared.rs
  13. 54 11
      crates/cdk-integration-tests/tests/bolt12.rs
  14. 16 34
      crates/cdk-integration-tests/tests/fake_wallet.rs
  15. 29 8
      crates/cdk-integration-tests/tests/happy_path_mint_wallet.rs
  16. 5 0
      crates/cdk-integration-tests/tests/integration_tests_pure.rs
  17. 8 1
      crates/cdk-integration-tests/tests/mint.rs
  18. 77 47
      crates/cdk-integration-tests/tests/regtest.rs
  19. 6 3
      crates/cdk-integration-tests/tests/test_fees.rs
  20. 1 1
      crates/cdk-mint-rpc/Cargo.toml
  21. 40 12
      crates/cdk-mint-rpc/src/bin/mint_rpc_cli.rs
  22. 8 0
      crates/cdk-mintd/src/cli.rs
  23. 803 2
      crates/cdk-mintd/src/lib.rs
  24. 15 780
      crates/cdk-mintd/src/main.rs
  25. 1 0
      crates/cdk-payment-processor/Cargo.toml
  26. 47 11
      crates/cdk-payment-processor/src/bin/payment_processor.rs
  27. 42 12
      crates/cdk-signatory/src/bin/cli/mod.rs
  28. 41 66
      misc/fake_auth_itests.sh
  29. 81 45
      misc/fake_itests.sh
  30. 1 1
      misc/interactive_regtest_mprocs.sh
  31. 67 91
      misc/itests.sh
  32. 1 1
      misc/mintd_payment_processor.sh
  33. 3 0
      misc/nutshell_wallet_itest.sh

+ 10 - 0
CHANGELOG.md

@@ -14,6 +14,10 @@
 - cashu: NUT-19 support in the wallet ([crodas]).
 - cdk: SIG_ALL support for swap and melt operations ([thesimplekid]).
 - cdk-sql-common: Add cache to SQL statements for better performance ([crodas]).
+- cdk-integration-tests: New binary `start_fake_auth_mint` for testing fake mint with authentication ([thesimplekid]).
+- cdk-integration-tests: New binary `start_fake_mint` for testing fake mint instances ([thesimplekid]).
+- cdk-integration-tests: New binary `start_regtest_mints` for testing regtest mints ([thesimplekid]).
+- cdk-integration-tests: Shared utilities module for common integration test functionality ([thesimplekid]).
 
 ### Changed
 - cdk: Refactored wallet keyset management methods for better clarity and separation of concerns ([thesimplekid]).
@@ -28,6 +32,12 @@
 - cdk-integration-tests: Updated test utilities to use new mint lifecycle management ([thesimplekid]).
 - cdk-sqlite: Introduce `cdk-sql-common` crate for shared SQL storage codebase ([crodas]).
 - cdk-sqlite: Rename `still_active` to `stale` for better clarity ([crodas]).
+- cdk-integration-tests: Refactored regtest setup to use Rust binaries instead of shell scripts ([thesimplekid]).
+- cdk-integration-tests: Improved environment variable handling for test configurations ([thesimplekid]).
+- cdk-integration-tests: Enhanced CLN client connection with retry logic ([thesimplekid]).
+- cdk-integration-tests: Updated integration tests to use proper temp directory management ([thesimplekid]).
+- cdk-integration-tests: Simplified regtest shell scripts to use new binaries ([thesimplekid]).
+- crates/cdk-mintd: Moved mintd library functions to separate module for better organization and testability ([thesimplekid]).
 
 ### Fixed
 - cashu: Fixed CurrencyUnit custom units preserving original case instead of being converted to uppercase ([thesimplekid]).

+ 1 - 0
Cargo.toml

@@ -57,6 +57,7 @@ cdk-redb = { path = "./crates/cdk-redb", default-features = true, version = "=0.
 cdk-sql-common = { path = "./crates/cdk-sql-common", default-features = true, version = "=0.11.0" }
 cdk-sqlite = { path = "./crates/cdk-sqlite", default-features = true, version = "=0.11.0" }
 cdk-signatory = { path = "./crates/cdk-signatory", version = "=0.11.0", default-features = false }
+cdk-mintd = { path = "./crates/cdk-mintd", version = "=0.11.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"

+ 4 - 1
crates/cdk-integration-tests/Cargo.toml

@@ -23,10 +23,12 @@ cashu = { workspace = true, features = ["mint", "wallet"] }
 cdk = { workspace = true, features = ["mint", "wallet", "auth"] }
 cdk-cln = { workspace = true }
 cdk-lnd = { workspace = true }
-cdk-axum = { workspace = true }
+cdk-axum = { workspace = true, features = ["auth"] }
 cdk-sqlite = { workspace = true }
 cdk-redb = { workspace = true }
 cdk-fake-wallet = { workspace = true }
+cdk-common = { workspace = true, features = ["mint", "wallet", "auth"] }
+cdk-mintd = { workspace = true, features = ["cln", "lnd", "fakewallet", "grpc-processor", "auth", "lnbits", "management-rpc"] }
 futures = { workspace = true, default-features = false, features = [
     "executor",
 ] }
@@ -44,6 +46,7 @@ tower-http = { workspace = true, features = ["cors"] }
 tower-service = "0.3.3"
 reqwest.workspace = true
 bitcoin = "0.32.0"
+clap = { workspace = true, features = ["derive"] }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 tokio.workspace = true

+ 163 - 0
crates/cdk-integration-tests/src/bin/start_fake_auth_mint.rs

@@ -0,0 +1,163 @@
+//! Binary for starting a fake mint with authentication for testing
+//!
+//! This binary provides a programmatic way to start a fake mint instance with authentication for testing purposes:
+//! 1. Sets up a fake mint instance with authentication using the cdk-mintd library
+//! 2. Configures OpenID Connect authentication settings
+//! 3. Waits for the mint to be ready and responsive
+//! 4. Keeps it running until interrupted (Ctrl+C)
+//! 5. Gracefully shuts down on receiving shutdown signal
+//!
+//! This approach offers better control and integration compared to external scripts,
+//! making it easier to run authentication integration tests with consistent configuration.
+
+use std::path::Path;
+use std::sync::Arc;
+
+use anyhow::Result;
+use bip39::Mnemonic;
+use cdk_integration_tests::cli::CommonArgs;
+use cdk_integration_tests::shared;
+use clap::Parser;
+use tokio::sync::Notify;
+
+#[derive(Parser)]
+#[command(name = "start-fake-auth-mint")]
+#[command(about = "Start a fake mint with authentication for testing", long_about = None)]
+struct Args {
+    #[command(flatten)]
+    common: CommonArgs,
+
+    /// Database type (sqlite)
+    database_type: String,
+
+    /// Working directory path
+    work_dir: String,
+
+    /// OpenID discovery URL
+    openid_discovery: String,
+
+    /// Port to listen on (default: 8087)
+    #[arg(default_value_t = 8087)]
+    port: u16,
+}
+
+/// Start a fake mint with authentication using the library
+async fn start_fake_auth_mint(
+    temp_dir: &Path,
+    port: u16,
+    openid_discovery: String,
+    shutdown: Arc<Notify>,
+) -> Result<tokio::task::JoinHandle<()>> {
+    println!("Starting fake auth mintd on port {port}");
+
+    // Create settings struct for fake mint with auth using shared function
+    let fake_wallet_config = cdk_mintd::config::FakeWallet {
+        supported_units: vec![cdk::nuts::CurrencyUnit::Sat, cdk::nuts::CurrencyUnit::Usd],
+        fee_percent: 0.0,
+        reserve_fee_min: cdk::Amount::from(1),
+        min_delay_time: 1,
+        max_delay_time: 3,
+    };
+
+    let mut settings = shared::create_fake_wallet_settings(
+        port,
+        Some(Mnemonic::generate(12)?.to_string()),
+        None,
+        Some(fake_wallet_config),
+    );
+
+    // Enable authentication
+    settings.auth = Some(cdk_mintd::config::Auth {
+        openid_discovery,
+        openid_client_id: "cashu-client".to_string(),
+        mint_max_bat: 50,
+        enabled_mint: true,
+        enabled_melt: true,
+        enabled_swap: true,
+        enabled_check_mint_quote: true,
+        enabled_check_melt_quote: true,
+        enabled_restore: true,
+        enabled_check_proof_state: true,
+    });
+
+    // Set description for the mint
+    settings.mint_info.description = "fake test mint with auth".to_string();
+
+    let temp_dir = temp_dir.to_path_buf();
+    let shutdown_clone = shutdown.clone();
+
+    // Run the mint in a separate task
+    let handle = tokio::spawn(async move {
+        // Create a future that resolves when the shutdown signal is received
+        let shutdown_future = async move {
+            shutdown_clone.notified().await;
+            println!("Fake auth mint shutdown signal received");
+        };
+
+        match cdk_mintd::run_mintd_with_shutdown(&temp_dir, &settings, shutdown_future, None).await
+        {
+            Ok(_) => println!("Fake auth mint exited normally"),
+            Err(e) => eprintln!("Fake auth mint exited with error: {e}"),
+        }
+    });
+
+    Ok(handle)
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = Args::parse();
+
+    // Initialize logging based on CLI arguments
+    shared::setup_logging(&args.common);
+
+    let temp_dir = shared::init_working_directory(&args.work_dir)?;
+
+    // Start fake auth mint
+    let shutdown = shared::create_shutdown_handler();
+    let shutdown_clone = shutdown.clone();
+
+    let handle = start_fake_auth_mint(
+        &temp_dir,
+        args.port,
+        args.openid_discovery.clone(),
+        shutdown_clone,
+    )
+    .await?;
+
+    // Wait for fake auth mint to be ready
+    if let Err(e) = shared::wait_for_mint_ready(args.port, 100).await {
+        eprintln!("Error waiting for fake auth mint: {e}");
+        return Err(e);
+    }
+
+    println!("Fake auth mint started successfully!");
+    println!("Fake auth mint: http://127.0.0.1:{}", args.port);
+    println!("Temp directory: {temp_dir:?}");
+    println!("Database type: {}", args.database_type);
+    println!("OpenID Discovery: {}", args.openid_discovery);
+    println!();
+    println!("Environment variables needed for tests:");
+    println!("  CDK_TEST_OIDC_USER=<username>");
+    println!("  CDK_TEST_OIDC_PASSWORD=<password>");
+    println!();
+    println!("You can now run auth integration tests with:");
+    println!("  cargo test -p cdk-integration-tests --test fake_auth");
+    println!();
+
+    println!("Press Ctrl+C to stop the mint...");
+
+    // Wait for Ctrl+C signal
+    shared::wait_for_shutdown_signal(shutdown).await;
+
+    println!("\nReceived Ctrl+C, shutting down mint...");
+
+    // Wait for mint to finish gracefully
+    if let Err(e) = handle.await {
+        eprintln!("Error waiting for mint to shut down: {e}");
+    }
+
+    println!("Mint shut down successfully");
+
+    Ok(())
+}

+ 178 - 0
crates/cdk-integration-tests/src/bin/start_fake_mint.rs

@@ -0,0 +1,178 @@
+//! Binary for starting a fake mint for testing
+//!
+//! This binary provides a programmatic way to start a fake mint instance for testing purposes:
+//! 1. Sets up a fake mint instance using the cdk-mintd library
+//! 2. Configures the mint with fake wallet backend for testing Lightning Network interactions
+//! 3. Waits for the mint to be ready and responsive
+//! 4. Keeps it running until interrupted (Ctrl+C)
+//! 5. Gracefully shuts down on receiving shutdown signal
+//!
+//! This approach offers better control and integration compared to external scripts,
+//! making it easier to run integration tests with consistent configuration.
+
+use std::path::Path;
+use std::sync::Arc;
+
+use anyhow::Result;
+use cdk::nuts::CurrencyUnit;
+use cdk_integration_tests::cli::CommonArgs;
+use cdk_integration_tests::shared;
+use clap::Parser;
+use tokio::sync::Notify;
+
+#[derive(Parser)]
+#[command(name = "start-fake-mint")]
+#[command(about = "Start a fake mint for testing", long_about = None)]
+struct Args {
+    #[command(flatten)]
+    common: CommonArgs,
+
+    /// Database type (sqlite)
+    database_type: String,
+
+    /// Working directory path
+    work_dir: String,
+
+    /// Port to listen on (default: 8086)
+    #[arg(default_value_t = 8086)]
+    port: u16,
+
+    /// Use external signatory
+    #[arg(long, default_value_t = false)]
+    external_signatory: bool,
+}
+
+/// Start a fake mint using the library
+async fn start_fake_mint(
+    temp_dir: &Path,
+    port: u16,
+    shutdown: Arc<Notify>,
+    external_signatory: bool,
+) -> Result<tokio::task::JoinHandle<()>> {
+    let signatory_config = if external_signatory {
+        println!("Configuring external signatory");
+        Some((
+            "https://127.0.0.1:15060".to_string(),  // Default signatory URL
+            temp_dir.to_string_lossy().to_string(), // Certs directory as string
+        ))
+    } else {
+        None
+    };
+
+    let mnemonic = if external_signatory {
+        None
+    } else {
+        Some(
+            "eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
+                .to_string(),
+        )
+    };
+
+    let fake_wallet_config = Some(cdk_mintd::config::FakeWallet {
+        supported_units: vec![CurrencyUnit::Sat, CurrencyUnit::Usd],
+        fee_percent: 0.0,
+        reserve_fee_min: 1.into(),
+        min_delay_time: 1,
+        max_delay_time: 3,
+    });
+
+    // Create settings struct for fake mint using shared function
+    let settings =
+        shared::create_fake_wallet_settings(port, mnemonic, signatory_config, fake_wallet_config);
+
+    println!("Starting fake mintd on port {port}");
+
+    let temp_dir = temp_dir.to_path_buf();
+    let shutdown_clone = shutdown.clone();
+
+    // Run the mint in a separate task
+    let handle = tokio::spawn(async move {
+        // Create a future that resolves when the shutdown signal is received
+        let shutdown_future = async move {
+            shutdown_clone.notified().await;
+            println!("Fake mint shutdown signal received");
+        };
+
+        match cdk_mintd::run_mintd_with_shutdown(&temp_dir, &settings, shutdown_future, None).await
+        {
+            Ok(_) => println!("Fake mint exited normally"),
+            Err(e) => eprintln!("Fake mint exited with error: {e}"),
+        }
+    });
+
+    Ok(handle)
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = Args::parse();
+
+    // Initialize logging based on CLI arguments
+    shared::setup_logging(&args.common);
+
+    let temp_dir = shared::init_working_directory(&args.work_dir)?;
+
+    // Write environment variables to a .env file in the temp_dir BEFORE starting the mint
+    let mint_url = format!("http://127.0.0.1:{}", args.port);
+    let itests_dir = temp_dir.display().to_string();
+    let env_vars: Vec<(&str, &str)> = vec![
+        ("CDK_TEST_MINT_URL", &mint_url),
+        ("CDK_ITESTS_DIR", &itests_dir),
+    ];
+
+    shared::write_env_file(&temp_dir, &env_vars)?;
+
+    // Start fake mint
+    let shutdown = shared::create_shutdown_handler();
+    let shutdown_clone = shutdown.clone();
+
+    let handle = start_fake_mint(
+        &temp_dir,
+        args.port,
+        shutdown_clone,
+        args.external_signatory,
+    )
+    .await?;
+
+    // Wait for fake mint to be ready
+    if let Err(e) = shared::wait_for_mint_ready(args.port, 100).await {
+        eprintln!("Error waiting for fake mint: {e}");
+        return Err(e);
+    }
+
+    shared::display_mint_info(args.port, &temp_dir, &args.database_type);
+
+    println!();
+    println!(
+        "Environment variables written to: {}/.env",
+        temp_dir.display()
+    );
+    println!("You can source these variables with:");
+    println!("  source {}/.env", temp_dir.display());
+    println!();
+    println!("Environment variables set:");
+    println!("  CDK_TEST_MINT_URL=http://127.0.0.1:{}", args.port);
+    println!("  CDK_ITESTS_DIR={}", temp_dir.display());
+    println!();
+    println!("You can now run integration tests with:");
+    println!("  cargo test -p cdk-integration-tests --test fake_wallet");
+    println!("  cargo test -p cdk-integration-tests --test happy_path_mint_wallet");
+    println!("  etc.");
+    println!();
+
+    println!("Press Ctrl+C to stop the mint...");
+
+    // Wait for Ctrl+C signal
+    shared::wait_for_shutdown_signal(shutdown).await;
+
+    println!("\nReceived Ctrl+C, shutting down mint...");
+
+    // Wait for mint to finish gracefully
+    if let Err(e) = handle.await {
+        eprintln!("Error waiting for mint to shut down: {e}");
+    }
+
+    println!("Mint shut down successfully");
+
+    Ok(())
+}

+ 41 - 21
crates/cdk-integration-tests/src/bin/start_regtest.rs

@@ -1,20 +1,33 @@
 use std::fs::OpenOptions;
 use std::io::Write;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;
 
-use anyhow::{bail, Result};
-use cdk_integration_tests::init_regtest::{get_temp_dir, start_regtest_end};
+use anyhow::Result;
+use cdk_integration_tests::cli::{init_logging, CommonArgs};
+use cdk_integration_tests::init_regtest::start_regtest_end;
+use clap::Parser;
 use tokio::signal;
 use tokio::sync::{oneshot, Notify};
 use tokio::time::timeout;
-use tracing_subscriber::EnvFilter;
 
-fn signal_progress() {
-    let temp_dir = get_temp_dir();
+#[derive(Parser)]
+#[command(name = "start-regtest")]
+#[command(about = "Start regtest environment", long_about = None)]
+struct Args {
+    #[command(flatten)]
+    common: CommonArgs,
+
+    /// Working directory path
+    work_dir: String,
+}
+
+fn signal_progress(work_dir: &Path) {
     let mut pipe = OpenOptions::new()
         .write(true)
-        .open(temp_dir.join("progress_pipe"))
+        .open(work_dir.join("progress_pipe"))
         .expect("Failed to open pipe");
 
     pipe.write_all(b"checkpoint1\n")
@@ -23,24 +36,21 @@ fn signal_progress() {
 
 #[tokio::main]
 async fn main() -> Result<()> {
-    let default_filter = "debug";
-
-    let sqlx_filter = "sqlx=warn";
-    let hyper_filter = "hyper=warn";
-    let h2_filter = "h2=warn";
-    let rustls_filter = "rustls=warn";
+    let args = Args::parse();
 
-    let env_filter = EnvFilter::new(format!(
-        "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter}"
-    ));
+    // Initialize logging based on CLI arguments
+    init_logging(args.common.enable_logging, args.common.log_level);
 
-    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+    let temp_dir = PathBuf::from_str(&args.work_dir)?;
+    let temp_dir_clone = temp_dir.clone();
 
     let shutdown_regtest = Arc::new(Notify::new());
-    let shutdown_clone = shutdown_regtest.clone();
+    let shutdown_clone = Arc::clone(&shutdown_regtest);
+    let shutdown_clone_two = Arc::clone(&shutdown_regtest);
+
     let (tx, rx) = oneshot::channel();
     tokio::spawn(async move {
-        start_regtest_end(tx, shutdown_clone)
+        start_regtest_end(&temp_dir_clone, tx, shutdown_clone)
             .await
             .expect("Error starting regtest");
     });
@@ -48,15 +58,25 @@ async fn main() -> Result<()> {
     match timeout(Duration::from_secs(300), rx).await {
         Ok(_) => {
             tracing::info!("Regtest set up");
-            signal_progress();
+            signal_progress(&temp_dir);
         }
         Err(_) => {
             tracing::error!("regtest setup timed out after 5 minutes");
-            bail!("Could not set up regtest");
+            anyhow::bail!("Could not set up regtest");
         }
     }
 
-    signal::ctrl_c().await?;
+    let shutdown_future = async {
+        // Wait for Ctrl+C signal
+        signal::ctrl_c()
+            .await
+            .expect("failed to install CTRL+C handler");
+        tracing::info!("Shutdown signal received");
+        println!("\nReceived Ctrl+C, shutting down mints...");
+        shutdown_clone_two.notify_waiters();
+    };
+
+    shutdown_future.await;
 
     Ok(())
 }

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

@@ -0,0 +1,305 @@
+//! Binary for starting regtest mints
+//!
+//! This binary provides a programmatic way to start regtest mints for testing purposes:
+//! 1. Sets up a regtest environment with CLN and LND nodes
+//! 2. Starts CLN and LND mint instances using the cdk-mintd library
+//! 3. Configures the mints to connect to the respective Lightning Network backends
+//! 4. Waits for both mints to be ready and responsive
+//! 5. Keeps them running until interrupted (Ctrl+C)
+//! 6. Gracefully shuts down all services on receiving shutdown signal
+//!
+//! This approach offers better control and integration compared to external scripts,
+//! making it easier to run integration tests with consistent configuration.
+
+use std::fs;
+use std::path::Path;
+use std::sync::Arc;
+use std::time::Duration;
+
+use anyhow::Result;
+use cdk_integration_tests::cli::CommonArgs;
+use cdk_integration_tests::init_regtest::start_regtest_end;
+use cdk_integration_tests::shared;
+use clap::Parser;
+use tokio::signal::unix::SignalKind;
+use tokio::signal::{self};
+use tokio::sync::{oneshot, Notify};
+use tokio::time::timeout;
+
+#[derive(Parser)]
+#[command(name = "start-regtest-mints")]
+#[command(about = "Start regtest mints", long_about = None)]
+struct Args {
+    #[command(flatten)]
+    common: CommonArgs,
+
+    /// Database type (sqlite)
+    database_type: String,
+
+    /// Working directory path
+    work_dir: String,
+
+    /// Mint address (default: 127.0.0.1)
+    #[arg(default_value = "127.0.0.1")]
+    mint_addr: String,
+
+    /// CLN port (default: 8085)
+    #[arg(default_value_t = 8085)]
+    cln_port: u16,
+
+    /// LND port (default: 8087)
+    #[arg(default_value_t = 8087)]
+    lnd_port: u16,
+}
+
+/// Start regtest CLN mint using the library
+async fn start_cln_mint(
+    temp_dir: &Path,
+    port: u16,
+    shutdown: Arc<Notify>,
+) -> Result<tokio::task::JoinHandle<()>> {
+    let cln_rpc_path = temp_dir
+        .join("cln")
+        .join("one")
+        .join("regtest")
+        .join("lightning-rpc");
+
+    let cln_config = cdk_mintd::config::Cln {
+        rpc_path: cln_rpc_path,
+        bolt12: false,
+        fee_percent: 0.0,
+        reserve_fee_min: 0.into(),
+    };
+
+    // Create settings struct for CLN mint using shared function
+    let settings = shared::create_cln_settings(
+        port,
+        temp_dir
+            .join("cln")
+            .join("one")
+            .join("regtest")
+            .join("lightning-rpc"),
+        "eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal".to_string(),
+        cln_config,
+    );
+
+    println!("Starting CLN mintd on port {port}");
+
+    let temp_dir = temp_dir.to_path_buf();
+    let shutdown_clone = shutdown.clone();
+
+    // Run the mint in a separate task
+    let handle = tokio::spawn(async move {
+        // Create a future that resolves when the shutdown signal is received
+        let shutdown_future = async move {
+            shutdown_clone.notified().await;
+            println!("CLN mint shutdown signal received");
+        };
+
+        match cdk_mintd::run_mintd_with_shutdown(&temp_dir, &settings, shutdown_future, None).await
+        {
+            Ok(_) => println!("CLN mint exited normally"),
+            Err(e) => eprintln!("CLN mint exited with error: {e}"),
+        }
+    });
+
+    Ok(handle)
+}
+
+/// Start regtest LND mint using the library
+async fn start_lnd_mint(
+    temp_dir: &Path,
+    port: u16,
+    shutdown: Arc<Notify>,
+) -> Result<tokio::task::JoinHandle<()>> {
+    let lnd_cert_file = temp_dir.join("lnd").join("two").join("tls.cert");
+    let lnd_macaroon_file = temp_dir
+        .join("lnd")
+        .join("two")
+        .join("data")
+        .join("chain")
+        .join("bitcoin")
+        .join("regtest")
+        .join("admin.macaroon");
+    let lnd_work_dir = temp_dir.join("lnd_mint");
+
+    // Create work directory for LND mint
+    fs::create_dir_all(&lnd_work_dir)?;
+
+    let lnd_config = cdk_mintd::config::Lnd {
+        address: "https://localhost:10010".to_string(),
+        cert_file: lnd_cert_file,
+        macaroon_file: lnd_macaroon_file,
+        fee_percent: 0.0,
+        reserve_fee_min: 0.into(),
+    };
+
+    // Create settings struct for LND mint using shared function
+    let settings = shared::create_lnd_settings(
+        port,
+        lnd_config,
+        "cattle gold bind busy sound reduce tone addict baby spend february strategy".to_string(),
+    );
+
+    println!("Starting LND mintd on port {port}");
+
+    let lnd_work_dir = lnd_work_dir.clone();
+    let shutdown_clone = shutdown.clone();
+
+    // Run the mint in a separate task
+    let handle = tokio::spawn(async move {
+        // Create a future that resolves when the shutdown signal is received
+        let shutdown_future = async move {
+            shutdown_clone.notified().await;
+            println!("LND mint shutdown signal received");
+        };
+
+        match cdk_mintd::run_mintd_with_shutdown(&lnd_work_dir, &settings, shutdown_future, None)
+            .await
+        {
+            Ok(_) => println!("LND mint exited normally"),
+            Err(e) => eprintln!("LND mint exited with error: {e}"),
+        }
+    });
+
+    Ok(handle)
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = Args::parse();
+
+    // Initialize logging based on CLI arguments
+    shared::setup_logging(&args.common);
+
+    let temp_dir = shared::init_working_directory(&args.work_dir)?;
+
+    // Write environment variables to a .env file in the temp_dir
+    let mint_url_1 = format!("http://{}:{}", args.mint_addr, args.cln_port);
+    let mint_url_2 = format!("http://{}:{}", args.mint_addr, args.lnd_port);
+    let env_vars: Vec<(&str, &str)> = vec![
+        ("CDK_TEST_MINT_URL", &mint_url_1),
+        ("CDK_TEST_MINT_URL_2", &mint_url_2),
+    ];
+
+    shared::write_env_file(&temp_dir, &env_vars)?;
+
+    // Start regtest
+    println!("Starting regtest...");
+
+    let shutdown_regtest = shared::create_shutdown_handler();
+    let shutdown_clone = shutdown_regtest.clone();
+    let (tx, rx) = oneshot::channel();
+
+    let shutdown_clone_one = Arc::clone(&shutdown_clone);
+
+    let temp_dir_clone = temp_dir.clone();
+    tokio::spawn(async move {
+        start_regtest_end(&temp_dir_clone, tx, shutdown_clone_one)
+            .await
+            .expect("Error starting regtest");
+    });
+
+    match timeout(Duration::from_secs(300), rx).await {
+        Ok(_) => {
+            tracing::info!("Regtest set up");
+        }
+        Err(_) => {
+            tracing::error!("regtest setup timed out after 5 minutes");
+            anyhow::bail!("Could not set up regtest");
+        }
+    }
+
+    // Start CLN mint
+    let cln_handle = start_cln_mint(&temp_dir, args.cln_port, shutdown_clone.clone()).await?;
+
+    // Wait for CLN mint to be ready
+    if let Err(e) = shared::wait_for_mint_ready(args.cln_port, 100).await {
+        eprintln!("Error waiting for CLN mint: {e}");
+        return Err(e);
+    }
+
+    // Start LND mint
+    let lnd_handle = start_lnd_mint(&temp_dir, args.lnd_port, shutdown_clone.clone()).await?;
+
+    // Wait for LND mint to be ready
+    if let Err(e) = shared::wait_for_mint_ready(args.lnd_port, 100).await {
+        eprintln!("Error waiting for LND mint: {e}");
+        return Err(e);
+    }
+
+    println!("All regtest mints started successfully!");
+    println!("CLN mint: http://{}:{}", args.mint_addr, args.cln_port);
+    println!("LND mint: http://{}:{}", args.mint_addr, args.lnd_port);
+    shared::display_mint_info(args.cln_port, &temp_dir, &args.database_type); // Using CLN port for display
+    println!();
+    println!("Environment variables set:");
+    println!(
+        "  CDK_TEST_MINT_URL=http://{}:{}",
+        args.mint_addr, args.cln_port
+    );
+    println!(
+        "  CDK_TEST_MINT_URL_2=http://{}:{}",
+        args.mint_addr, args.lnd_port
+    );
+    println!("  CDK_ITESTS_DIR={}", temp_dir.display());
+    println!();
+    println!("You can now run integration tests with:");
+    println!("  cargo test -p cdk-integration-tests --test regtest");
+    println!("  cargo test -p cdk-integration-tests --test happy_path_mint_wallet");
+    println!("  etc.");
+    println!();
+
+    println!("Press Ctrl+C to stop the mints...");
+
+    // Create a future to wait for either Ctrl+C signal or unexpected mint termination
+    let shutdown_future = async {
+        // Wait for either SIGINT (Ctrl+C) or SIGTERM
+        let mut sigterm = signal::unix::signal(SignalKind::terminate())
+            .expect("Failed to create SIGTERM signal handler");
+        tokio::select! {
+            _ = signal::ctrl_c() => {
+                tracing::info!("Received SIGINT (Ctrl+C), shutting down mints...");
+            }
+            _ = sigterm.recv() => {
+                tracing::info!("Received SIGTERM, shutting down mints...");
+            }
+        }
+        println!("\nShutdown signal received, shutting down mints...");
+        shutdown_clone.notify_waiters();
+    };
+
+    // Monitor mint handles for unexpected termination
+    let monitor_mints = async {
+        loop {
+            if cln_handle.is_finished() {
+                println!("CLN mint finished unexpectedly");
+                return;
+            }
+            if lnd_handle.is_finished() {
+                println!("LND mint finished unexpectedly");
+                return;
+            }
+            tokio::time::sleep(Duration::from_millis(100)).await;
+        }
+    };
+
+    // Wait for either shutdown signal or mint termination
+    tokio::select! {
+        _ = shutdown_future => {
+            println!("Shutdown signal received, waiting for mints to stop...");
+        }
+        _ = monitor_mints => {
+            println!("One or more mints terminated unexpectedly");
+        }
+    }
+
+    // Wait for mints to finish gracefully
+    if let Err(e) = tokio::try_join!(cln_handle, lnd_handle) {
+        eprintln!("Error waiting for mints to shut down: {e}");
+    }
+
+    println!("All services shut down successfully");
+
+    Ok(())
+}

+ 42 - 0
crates/cdk-integration-tests/src/cli.rs

@@ -0,0 +1,42 @@
+//! Common CLI and logging utilities for CDK integration test binaries
+//!
+//! This module provides standardized CLI argument parsing and logging setup
+//! for integration test binaries.
+
+use clap::Parser;
+use tracing_subscriber::EnvFilter;
+
+/// Common CLI arguments for CDK integration test binaries
+#[derive(Parser, Debug)]
+pub struct CommonArgs {
+    /// Enable logging (default is false)
+    #[arg(long, default_value_t = false)]
+    pub enable_logging: bool,
+
+    /// Logging level when enabled (default is debug)
+    #[arg(long, default_value = "debug")]
+    pub log_level: tracing::Level,
+}
+
+/// Initialize logging based on CLI arguments
+pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
+    if enable_logging {
+        let default_filter = log_level.to_string();
+
+        // Common filters to reduce noise
+        let sqlx_filter = "sqlx=warn";
+        let hyper_filter = "hyper=warn";
+        let h2_filter = "h2=warn";
+        let rustls_filter = "rustls=warn";
+        let reqwest_filter = "reqwest=warn";
+
+        let env_filter = EnvFilter::new(format!(
+            "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
+        ));
+
+        // Ok if successful, Err if already initialized
+        let _ = tracing_subscriber::fmt()
+            .with_env_filter(env_filter)
+            .try_init();
+    }
+}

+ 2 - 2
crates/cdk-integration-tests/src/init_pure_tests.rs

@@ -211,10 +211,10 @@ impl MintConnector for DirectMintConnection {
 pub fn setup_tracing() {
     let default_filter = "debug";
 
-    let sqlx_filter = "sqlx=warn";
+    let h2_filter = "h2=warn";
     let hyper_filter = "hyper=warn";
 
-    let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter},{hyper_filter}"));
+    let env_filter = EnvFilter::new(format!("{default_filter},{h2_filter},{hyper_filter}"));
 
     // Ok if successful, Err if already initialized
     // Allows us to setup tracing at the start of several parallel tests

+ 69 - 29
crates/cdk-integration-tests/src/init_regtest.rs

@@ -31,21 +31,41 @@ pub const LND_TWO_RPC_ADDR: &str = "localhost:10010";
 pub const CLN_ADDR: &str = "127.0.0.1:19846";
 pub const CLN_TWO_ADDR: &str = "127.0.0.1:19847";
 
-pub fn get_mint_addr() -> String {
-    env::var("CDK_ITESTS_MINT_ADDR").expect("Mint address not set")
+/// Configuration for regtest environment
+pub struct RegtestConfig {
+    pub mint_addr: String,
+    pub cln_port: u16,
+    pub lnd_port: u16,
+    pub temp_dir: PathBuf,
 }
 
-pub fn get_mint_port(which: &str) -> u16 {
-    let dir = env::var(format!("CDK_ITESTS_MINT_PORT_{which}")).expect("Mint port not set");
-    dir.parse().unwrap()
+impl Default for RegtestConfig {
+    fn default() -> Self {
+        Self {
+            mint_addr: "127.0.0.1".to_string(),
+            cln_port: 8085,
+            lnd_port: 8087,
+            temp_dir: std::env::temp_dir().join("cdk-itests-default"),
+        }
+    }
 }
 
-pub fn get_mint_url(which: &str) -> String {
-    format!("http://{}:{}", get_mint_addr(), get_mint_port(which))
+pub fn get_mint_url_with_config(config: &RegtestConfig, which: &str) -> String {
+    let port = match which {
+        "0" => config.cln_port,
+        "1" => config.lnd_port,
+        _ => panic!("Unknown mint identifier: {which}"),
+    };
+    format!("http://{}:{}", config.mint_addr, port)
 }
 
-pub fn get_mint_ws_url(which: &str) -> String {
-    format!("ws://{}:{}/v1/ws", get_mint_addr(), get_mint_port(which))
+pub fn get_mint_ws_url_with_config(config: &RegtestConfig, which: &str) -> String {
+    let port = match which {
+        "0" => config.cln_port,
+        "1" => config.lnd_port,
+        _ => panic!("Unknown mint identifier: {which}"),
+    };
+    format!("ws://{}:{}/v1/ws", config.mint_addr, port)
 }
 
 pub fn get_temp_dir() -> PathBuf {
@@ -54,15 +74,19 @@ pub fn get_temp_dir() -> PathBuf {
     dir.parse().expect("Valid path buf")
 }
 
-pub fn get_bitcoin_dir() -> PathBuf {
-    let dir = get_temp_dir().join(BITCOIN_DIR);
+pub fn get_temp_dir_with_config(config: &RegtestConfig) -> &PathBuf {
+    &config.temp_dir
+}
+
+pub fn get_bitcoin_dir(temp_dir: &Path) -> PathBuf {
+    let dir = temp_dir.join(BITCOIN_DIR);
     std::fs::create_dir_all(&dir).unwrap();
     dir
 }
 
-pub fn init_bitcoind() -> Bitcoind {
+pub fn init_bitcoind(work_dir: &Path) -> Bitcoind {
     Bitcoind::new(
-        get_bitcoin_dir(),
+        get_bitcoin_dir(work_dir),
         BITCOIND_ADDR.parse().unwrap(),
         BITCOIN_RPC_USER.to_string(),
         BITCOIN_RPC_PASS.to_string(),
@@ -81,14 +105,14 @@ pub fn init_bitcoin_client() -> Result<BitcoinClient> {
     )
 }
 
-pub fn get_cln_dir(name: &str) -> PathBuf {
-    let dir = get_temp_dir().join("cln").join(name);
+pub fn get_cln_dir(work_dir: &Path, name: &str) -> PathBuf {
+    let dir = work_dir.join("cln").join(name);
     std::fs::create_dir_all(&dir).unwrap();
     dir
 }
 
-pub fn get_lnd_dir(name: &str) -> PathBuf {
-    let dir = get_temp_dir().join("lnd").join(name);
+pub fn get_lnd_dir(work_dir: &Path, name: &str) -> PathBuf {
+    let dir = work_dir.join("lnd").join(name);
     std::fs::create_dir_all(&dir).unwrap();
     dir
 }
@@ -101,9 +125,14 @@ pub fn get_lnd_macaroon_path(lnd_dir: &Path) -> PathBuf {
     lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon")
 }
 
-pub async fn init_lnd(lnd_dir: PathBuf, lnd_addr: &str, lnd_rpc_addr: &str) -> Lnd {
+pub async fn init_lnd(
+    work_dir: &Path,
+    lnd_dir: PathBuf,
+    lnd_addr: &str,
+    lnd_rpc_addr: &str,
+) -> Lnd {
     Lnd::new(
-        get_bitcoin_dir(),
+        get_bitcoin_dir(work_dir),
         lnd_dir,
         lnd_addr.parse().unwrap(),
         lnd_rpc_addr.to_string(),
@@ -192,8 +221,12 @@ where
     Ok(())
 }
 
-pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyhow::Result<()> {
-    let mut bitcoind = init_bitcoind();
+pub async fn start_regtest_end(
+    work_dir: &Path,
+    sender: Sender<()>,
+    notify: Arc<Notify>,
+) -> anyhow::Result<()> {
+    let mut bitcoind = init_bitcoind(work_dir);
     bitcoind.start_bitcoind()?;
 
     let bitcoin_client = init_bitcoin_client()?;
@@ -203,9 +236,9 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
     let new_add = bitcoin_client.get_new_address()?;
     bitcoin_client.generate_blocks(&new_add, 200).unwrap();
 
-    let cln_one_dir = get_cln_dir("one");
+    let cln_one_dir = get_cln_dir(work_dir, "one");
     let mut clnd = Clnd::new(
-        get_bitcoin_dir(),
+        get_bitcoin_dir(work_dir),
         cln_one_dir.clone(),
         CLN_ADDR.into(),
         BITCOIN_RPC_USER.to_string(),
@@ -220,9 +253,9 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
     fund_ln(&bitcoin_client, &cln_client).await.unwrap();
 
     // Create second cln
-    let cln_two_dir = get_cln_dir("two");
+    let cln_two_dir = get_cln_dir(work_dir, "two");
     let mut clnd_two = Clnd::new(
-        get_bitcoin_dir(),
+        get_bitcoin_dir(work_dir),
         cln_two_dir.clone(),
         CLN_TWO_ADDR.into(),
         BITCOIN_RPC_USER.to_string(),
@@ -236,10 +269,10 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
 
     fund_ln(&bitcoin_client, &cln_two_client).await.unwrap();
 
-    let lnd_dir = get_lnd_dir("one");
+    let lnd_dir = get_lnd_dir(work_dir, "one");
     println!("{}", lnd_dir.display());
 
-    let mut lnd = init_lnd(lnd_dir.clone(), LND_ADDR, LND_RPC_ADDR).await;
+    let mut lnd = init_lnd(work_dir, lnd_dir.clone(), LND_ADDR, LND_RPC_ADDR).await;
     lnd.start_lnd().unwrap();
     tracing::info!("Started lnd node");
 
@@ -255,8 +288,15 @@ pub async fn start_regtest_end(sender: Sender<()>, notify: Arc<Notify>) -> anyho
     fund_ln(&bitcoin_client, &lnd_client).await.unwrap();
 
     // create second lnd node
-    let lnd_two_dir = get_lnd_dir("two");
-    let mut lnd_two = init_lnd(lnd_two_dir.clone(), LND_TWO_ADDR, LND_TWO_RPC_ADDR).await;
+    let work_dir = get_temp_dir();
+    let lnd_two_dir = get_lnd_dir(&work_dir, "two");
+    let mut lnd_two = init_lnd(
+        &work_dir,
+        lnd_two_dir.clone(),
+        LND_TWO_ADDR,
+        LND_TWO_RPC_ADDR,
+    )
+    .await;
     lnd_two.start_lnd().unwrap();
     tracing::info!("Started second lnd node");
 

+ 42 - 31
crates/cdk-integration-tests/src/lib.rs

@@ -1,4 +1,23 @@
+//! Integration Test Library
+//!
+//! This crate provides shared functionality for CDK integration tests.
+//! It includes utilities for setting up test environments, funding wallets,
+//! and common test operations across different test scenarios.
+//!
+//! Test Categories Supported:
+//! - Pure in-memory tests (no external dependencies)
+//! - Regtest environment tests (with actual Lightning nodes)
+//! - Authenticated mint tests
+//! - Multi-mint scenarios
+//!
+//! Key Components:
+//! - Test environment initialization
+//! - Wallet funding utilities
+//! - Lightning Network client helpers
+//! - Proof state management utilities
+
 use std::env;
+use std::path::Path;
 use std::sync::Arc;
 
 use anyhow::{anyhow, bail, Result};
@@ -8,13 +27,15 @@ use cdk::nuts::{MintQuoteState, NotificationPayload, State};
 use cdk::wallet::WalletSubscription;
 use cdk::Wallet;
 use cdk_fake_wallet::create_fake_invoice;
-use init_regtest::{get_lnd_dir, get_mint_url, LND_RPC_ADDR};
+use init_regtest::{get_lnd_dir, LND_RPC_ADDR};
 use ln_regtest_rs::ln_client::{LightningClient, LndClient};
 use tokio::time::{sleep, timeout, Duration};
 
+pub mod cli;
 pub mod init_auth_mint;
 pub mod init_pure_tests;
 pub mod init_regtest;
+pub mod shared;
 
 pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
     let quote = wallet
@@ -32,6 +53,20 @@ pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
         .expect("Could not mint");
 }
 
+pub fn get_mint_url_from_env() -> String {
+    match env::var("CDK_TEST_MINT_URL") {
+        Ok(url) => url,
+        Err(_) => panic!("Mint url not set"),
+    }
+}
+
+pub fn get_second_mint_url_from_env() -> String {
+    match env::var("CDK_TEST_MINT_URL_2") {
+        Ok(url) => url,
+        Err(_) => panic!("Mint url not set"),
+    }
+}
+
 // Get all pending from wallet and attempt to swap
 // Will panic if there are no pending
 // Will return Ok if swap fails as expected
@@ -151,33 +186,9 @@ pub async fn wait_for_mint_to_be_paid(
     }
 }
 
-/// Gets the mint URL from environment variable or falls back to default
-///
-/// Checks the CDK_TEST_MINT_URL environment variable:
-/// - If set, returns that URL
-/// - Otherwise falls back to the default URL from get_mint_url("0")
-pub fn get_mint_url_from_env() -> String {
-    match env::var("CDK_TEST_MINT_URL") {
-        Ok(url) => url,
-        Err(_) => get_mint_url("0"),
-    }
-}
-
-/// Gets the second mint URL from environment variable or falls back to default
-///
-/// Checks the CDK_TEST_MINT_URL_2 environment variable:
-/// - If set, returns that URL
-/// - Otherwise falls back to the default URL from get_mint_url("1")
-pub fn get_second_mint_url_from_env() -> String {
-    match env::var("CDK_TEST_MINT_URL_2") {
-        Ok(url) => url,
-        Err(_) => get_mint_url("1"),
-    }
-}
-
 // This is the ln wallet we use to send/receive ln payements as the wallet
-pub async fn init_lnd_client() -> LndClient {
-    let lnd_dir = get_lnd_dir("one");
+pub async fn init_lnd_client(work_dir: &Path) -> LndClient {
+    let lnd_dir = get_lnd_dir(work_dir, "one");
     let cert_file = lnd_dir.join("tls.cert");
     let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
     LndClient::new(format!("https://{LND_RPC_ADDR}"), cert_file, macaroon_file)
@@ -189,10 +200,10 @@ pub async fn init_lnd_client() -> LndClient {
 ///
 /// This is useful for tests that need to pay invoices in regtest mode but
 /// should be skipped in other environments.
-pub async fn pay_if_regtest(invoice: &Bolt11Invoice) -> Result<()> {
+pub async fn pay_if_regtest(work_dir: &Path, invoice: &Bolt11Invoice) -> Result<()> {
     // Check if the invoice is for the regtest network
     if invoice.network() == bitcoin::Network::Regtest {
-        let lnd_client = init_lnd_client().await;
+        let lnd_client = init_lnd_client(work_dir).await;
         lnd_client.pay_invoice(invoice.to_string()).await?;
         Ok(())
     } else {
@@ -220,10 +231,10 @@ pub fn is_regtest_env() -> bool {
 ///
 /// Uses the is_regtest_env() function to determine whether to
 /// create a real regtest invoice or a fake one for testing.
-pub async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
+pub async fn create_invoice_for_env(work_dir: &Path, amount_sat: Option<u64>) -> Result<String> {
     if is_regtest_env() {
         // In regtest mode, create a real invoice
-        let lnd_client = init_lnd_client().await;
+        let lnd_client = init_lnd_client(work_dir).await;
         lnd_client
             .create_invoice(amount_sat)
             .await

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

@@ -0,0 +1,266 @@
+//! Shared utilities for mint integration tests
+//!
+//! This module provides common functionality used across different
+//! integration test binaries to reduce code duplication.
+
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::sync::Arc;
+use std::time::Duration;
+
+use anyhow::Result;
+use cdk_axum::cache;
+use tokio::signal;
+use tokio::sync::Notify;
+
+use crate::cli::{init_logging, CommonArgs};
+
+/// Default minimum mint amount for test mints
+const DEFAULT_MIN_MINT: u64 = 1;
+/// Default maximum mint amount for test mints
+const DEFAULT_MAX_MINT: u64 = 500_000;
+/// Default minimum melt amount for test mints
+const DEFAULT_MIN_MELT: u64 = 1;
+/// Default maximum melt amount for test mints
+const DEFAULT_MAX_MELT: u64 = 500_000;
+
+/// Wait for mint to be ready by checking its info endpoint
+pub async fn wait_for_mint_ready(port: u16, timeout_secs: u64) -> Result<()> {
+    let url = format!("http://127.0.0.1:{port}/v1/info");
+    let start_time = std::time::Instant::now();
+
+    println!("Waiting for mint on port {port} to be ready...");
+
+    loop {
+        // Check if timeout has been reached
+        if start_time.elapsed().as_secs() > timeout_secs {
+            return Err(anyhow::anyhow!("Timeout waiting for mint on port {}", port));
+        }
+
+        // Try to make a request to the mint info endpoint
+        match reqwest::get(&url).await {
+            Ok(response) => {
+                if response.status().is_success() {
+                    println!("Mint on port {port} is ready");
+                    return Ok(());
+                } else {
+                    println!(
+                        "Mint on port {} returned status: {}",
+                        port,
+                        response.status()
+                    );
+                }
+            }
+            Err(e) => {
+                println!("Error connecting to mint on port {port}: {e}");
+            }
+        }
+
+        tokio::time::sleep(Duration::from_secs(2)).await;
+    }
+}
+
+/// Initialize working directory
+pub fn init_working_directory(work_dir: &str) -> Result<PathBuf> {
+    let temp_dir = PathBuf::from_str(work_dir)?;
+
+    // Create the temp directory if it doesn't exist
+    fs::create_dir_all(&temp_dir)?;
+
+    Ok(temp_dir)
+}
+
+/// Write environment variables to .env file
+pub fn write_env_file(temp_dir: &Path, env_vars: &[(&str, &str)]) -> Result<()> {
+    let mut env_content = String::new();
+    for (key, value) in env_vars {
+        env_content.push_str(&format!("{key}={value}\n"));
+    }
+
+    let env_file_path = temp_dir.join(".env");
+
+    fs::write(&env_file_path, &env_content)
+        .map(|_| {
+            println!(
+                "Environment variables written to: {}",
+                env_file_path.display()
+            );
+        })
+        .map_err(|e| anyhow::anyhow!("Could not write .env file: {}", e))
+}
+
+/// Wait for .env file to be created
+pub async fn wait_for_env_file(temp_dir: &Path, timeout_secs: u64) -> Result<()> {
+    let env_file_path = temp_dir.join(".env");
+    let start_time = std::time::Instant::now();
+
+    println!(
+        "Waiting for .env file to be created at: {}",
+        env_file_path.display()
+    );
+
+    loop {
+        // Check if timeout has been reached
+        if start_time.elapsed().as_secs() > timeout_secs {
+            return Err(anyhow::anyhow!(
+                "Timeout waiting for .env file at {}",
+                env_file_path.display()
+            ));
+        }
+
+        // Check if the file exists
+        if env_file_path.exists() {
+            println!(".env file found at: {}", env_file_path.display());
+            return Ok(());
+        }
+
+        tokio::time::sleep(Duration::from_secs(1)).await;
+    }
+}
+
+/// Setup common logging based on CLI arguments
+pub fn setup_logging(common_args: &CommonArgs) {
+    init_logging(common_args.enable_logging, common_args.log_level);
+}
+
+/// Create shutdown handler for graceful termination
+pub fn create_shutdown_handler() -> Arc<Notify> {
+    Arc::new(Notify::new())
+}
+
+/// Wait for Ctrl+C signal
+pub async fn wait_for_shutdown_signal(shutdown: Arc<Notify>) {
+    signal::ctrl_c()
+        .await
+        .expect("failed to install CTRL+C handler");
+
+    println!("\nReceived Ctrl+C, shutting down...");
+    shutdown.notify_waiters();
+}
+
+/// Common mint information display
+pub fn display_mint_info(port: u16, temp_dir: &Path, database_type: &str) {
+    println!("Mint started successfully!");
+    println!("Mint URL: http://127.0.0.1:{port}");
+    println!("Temp directory: {temp_dir:?}");
+    println!("Database type: {database_type}");
+}
+
+/// Create settings for a fake wallet mint
+pub fn create_fake_wallet_settings(
+    port: u16,
+    mnemonic: Option<String>,
+    signatory_config: Option<(String, String)>, // (url, certs_dir)
+    fake_wallet_config: Option<cdk_mintd::config::FakeWallet>,
+) -> cdk_mintd::config::Settings {
+    cdk_mintd::config::Settings {
+        info: cdk_mintd::config::Info {
+            url: format!("http://127.0.0.1:{port}"),
+            listen_host: "127.0.0.1".to_string(),
+            listen_port: port,
+            mnemonic,
+            signatory_url: signatory_config.as_ref().map(|(url, _)| url.clone()),
+            signatory_certs: signatory_config
+                .as_ref()
+                .map(|(_, certs_dir)| certs_dir.clone()),
+            input_fee_ppk: None,
+            http_cache: cache::Config::default(),
+            enable_swagger_ui: None,
+        },
+        mint_info: cdk_mintd::config::MintInfo::default(),
+        ln: cdk_mintd::config::Ln {
+            ln_backend: cdk_mintd::config::LnBackend::FakeWallet,
+            invoice_description: None,
+            min_mint: DEFAULT_MIN_MINT.into(),
+            max_mint: DEFAULT_MAX_MINT.into(),
+            min_melt: DEFAULT_MIN_MELT.into(),
+            max_melt: DEFAULT_MAX_MELT.into(),
+        },
+        cln: None,
+        lnbits: None,
+        lnd: None,
+        fake_wallet: fake_wallet_config,
+        grpc_processor: None,
+        database: cdk_mintd::config::Database::default(),
+        mint_management_rpc: None,
+        auth: None,
+    }
+}
+
+/// Create settings for a CLN mint
+pub fn create_cln_settings(
+    port: u16,
+    _cln_rpc_path: PathBuf,
+    mnemonic: String,
+    cln_config: cdk_mintd::config::Cln,
+) -> cdk_mintd::config::Settings {
+    cdk_mintd::config::Settings {
+        info: cdk_mintd::config::Info {
+            url: format!("http://127.0.0.1:{port}"),
+            listen_host: "127.0.0.1".to_string(),
+            listen_port: port,
+            mnemonic: Some(mnemonic),
+            signatory_url: None,
+            signatory_certs: None,
+            input_fee_ppk: None,
+            http_cache: cache::Config::default(),
+            enable_swagger_ui: None,
+        },
+        mint_info: cdk_mintd::config::MintInfo::default(),
+        ln: cdk_mintd::config::Ln {
+            ln_backend: cdk_mintd::config::LnBackend::Cln,
+            invoice_description: None,
+            min_mint: DEFAULT_MIN_MINT.into(),
+            max_mint: DEFAULT_MAX_MINT.into(),
+            min_melt: DEFAULT_MIN_MELT.into(),
+            max_melt: DEFAULT_MAX_MELT.into(),
+        },
+        cln: Some(cln_config),
+        lnbits: None,
+        lnd: None,
+        fake_wallet: None,
+        grpc_processor: None,
+        database: cdk_mintd::config::Database::default(),
+        mint_management_rpc: None,
+        auth: None,
+    }
+}
+
+/// Create settings for an LND mint
+pub fn create_lnd_settings(
+    port: u16,
+    lnd_config: cdk_mintd::config::Lnd,
+    mnemonic: String,
+) -> cdk_mintd::config::Settings {
+    cdk_mintd::config::Settings {
+        info: cdk_mintd::config::Info {
+            url: format!("http://127.0.0.1:{port}"),
+            listen_host: "127.0.0.1".to_string(),
+            listen_port: port,
+            mnemonic: Some(mnemonic),
+            signatory_url: None,
+            signatory_certs: None,
+            input_fee_ppk: None,
+            http_cache: cache::Config::default(),
+            enable_swagger_ui: None,
+        },
+        mint_info: cdk_mintd::config::MintInfo::default(),
+        ln: cdk_mintd::config::Ln {
+            ln_backend: cdk_mintd::config::LnBackend::Lnd,
+            invoice_description: None,
+            min_mint: DEFAULT_MIN_MINT.into(),
+            max_mint: DEFAULT_MAX_MINT.into(),
+            min_melt: DEFAULT_MIN_MELT.into(),
+            max_melt: DEFAULT_MAX_MELT.into(),
+        },
+        cln: None,
+        lnbits: None,
+        lnd: Some(lnd_config),
+        fake_wallet: None,
+        grpc_processor: None,
+        database: cdk_mintd::config::Database::default(),
+        mint_management_rpc: None,
+        auth: None,
+    }
+}

+ 54 - 11
crates/cdk-integration-tests/tests/bolt12.rs

@@ -1,3 +1,5 @@
+use std::env;
+use std::path::PathBuf;
 use std::sync::Arc;
 
 use anyhow::{bail, Result};
@@ -6,11 +8,45 @@ use cashu::amount::SplitTarget;
 use cashu::nut23::Amountless;
 use cashu::{Amount, CurrencyUnit, MintRequest, PreMintSecrets, ProofsMethods};
 use cdk::wallet::{HttpClient, MintConnector, Wallet};
-use cdk_integration_tests::init_regtest::get_cln_dir;
+use cdk_integration_tests::init_regtest::{get_cln_dir, get_temp_dir};
 use cdk_integration_tests::{get_mint_url_from_env, wait_for_mint_to_be_paid};
 use cdk_sqlite::wallet::memory;
 use ln_regtest_rs::ln_client::ClnClient;
 
+// Helper function to get temp directory from environment or fallback
+fn get_test_temp_dir() -> PathBuf {
+    match env::var("CDK_ITESTS_DIR") {
+        Ok(dir) => PathBuf::from(dir),
+        Err(_) => get_temp_dir(), // fallback to default
+    }
+}
+
+// Helper function to create CLN client with retries
+async fn create_cln_client_with_retry(cln_dir: PathBuf) -> Result<ClnClient> {
+    let mut retries = 0;
+    let max_retries = 10;
+    loop {
+        match ClnClient::new(cln_dir.clone(), None).await {
+            Ok(client) => return Ok(client),
+            Err(e) => {
+                retries += 1;
+                if retries >= max_retries {
+                    bail!(
+                        "Could not connect to CLN client after {} retries: {}",
+                        max_retries,
+                        e
+                    );
+                }
+                println!(
+                    "Failed to connect to CLN (attempt {}/{}): {}. Retrying in 7 seconds...",
+                    retries, max_retries, e
+                );
+                tokio::time::sleep(tokio::time::Duration::from_secs(7)).await;
+            }
+        }
+    }
+}
+
 /// Tests basic BOLT12 minting functionality:
 /// - Creates a wallet
 /// - Gets a BOLT12 quote for a specific amount (100 sats)
@@ -36,8 +72,11 @@ async fn test_regtest_bolt12_mint() {
 
     assert_eq!(mint_quote.amount, Some(mint_amount));
 
-    let cln_one_dir = get_cln_dir("one");
-    let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
+    let work_dir = get_test_temp_dir();
+    let cln_one_dir = get_cln_dir(&work_dir, "one");
+    let cln_client = create_cln_client_with_retry(cln_one_dir.clone())
+        .await
+        .unwrap();
     cln_client
         .pay_bolt12_offer(None, mint_quote.request)
         .await
@@ -68,8 +107,9 @@ async fn test_regtest_bolt12_mint_multiple() -> Result<()> {
 
     let mint_quote = wallet.mint_bolt12_quote(None, None).await?;
 
-    let cln_one_dir = get_cln_dir("one");
-    let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
+    let work_dir = get_test_temp_dir();
+    let cln_one_dir = get_cln_dir(&work_dir, "one");
+    let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
     cln_client
         .pay_bolt12_offer(Some(10000), mint_quote.request.clone())
         .await
@@ -131,8 +171,9 @@ async fn test_regtest_bolt12_multiple_wallets() -> Result<()> {
     )?;
 
     // Create a BOLT12 offer that both wallets will use
-    let cln_one_dir = get_cln_dir("one");
-    let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
+    let work_dir = get_test_temp_dir();
+    let cln_one_dir = get_cln_dir(&work_dir, "one");
+    let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
     // First wallet payment
     let quote_one = wallet_one
         .mint_bolt12_quote(Some(10_000.into()), None)
@@ -222,8 +263,9 @@ async fn test_regtest_bolt12_melt() -> Result<()> {
 
     assert_eq!(mint_quote.amount, Some(mint_amount));
     // Pay the quote
-    let cln_one_dir = get_cln_dir("one");
-    let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
+    let work_dir = get_test_temp_dir();
+    let cln_one_dir = get_cln_dir(&work_dir, "one");
+    let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
     cln_client
         .pay_bolt12_offer(None, mint_quote.request.clone())
         .await?;
@@ -281,8 +323,9 @@ async fn test_regtest_bolt12_mint_extra() -> Result<()> {
 
     let pay_amount_msats = 10_000;
 
-    let cln_one_dir = get_cln_dir("one");
-    let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?;
+    let work_dir = get_test_temp_dir();
+    let cln_one_dir = get_cln_dir(&work_dir, "one");
+    let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
     cln_client
         .pay_bolt12_offer(Some(pay_amount_msats), mint_quote.request.clone())
         .await?;

+ 16 - 34
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -1,3 +1,19 @@
+//! Fake Wallet Integration Tests
+//!
+//! This file contains tests for the fake wallet backend functionality.
+//! The fake wallet simulates Lightning Network behavior for testing purposes,
+//! allowing verification of mint behavior in various payment scenarios without
+//! requiring a real Lightning node.
+//!
+//! Test Scenarios:
+//! - Pending payment states and proof handling
+//! - Payment failure cases and proof state management
+//! - Change output verification in melt operations
+//! - Witness signature validation
+//! - Cross-unit transaction validation
+//! - Overflow and balance validation
+//! - Duplicate proof detection
+
 use std::sync::Arc;
 
 use bip39::Mnemonic;
@@ -408,40 +424,6 @@ async fn test_fake_melt_change_in_quote() {
     assert_eq!(melt_change, check);
 }
 
-/// Tests that the correct database type is used based on environment variables
-#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
-async fn test_database_type() {
-    // Get the database type and work dir from environment
-    let db_type = std::env::var("CDK_MINTD_DATABASE").expect("MINT_DATABASE env var should be set");
-    let work_dir =
-        std::env::var("CDK_MINTD_WORK_DIR").expect("CDK_MINTD_WORK_DIR env var should be set");
-
-    // Check that the correct database file exists
-    match db_type.as_str() {
-        "REDB" => {
-            let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.redb");
-            assert!(
-                db_path.exists(),
-                "Expected redb database file to exist at {:?}",
-                db_path
-            );
-        }
-        "SQLITE" => {
-            let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.sqlite");
-            assert!(
-                db_path.exists(),
-                "Expected sqlite database file to exist at {:?}",
-                db_path
-            );
-        }
-        "MEMORY" => {
-            // Memory database has no file to check
-            println!("Memory database in use - no file to check");
-        }
-        _ => panic!("Unknown database type: {}", db_type),
-    }
-}
-
 /// Tests minting tokens with a valid witness signature
 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
 async fn test_fake_mint_with_witness() {

+ 29 - 8
crates/cdk-integration-tests/tests/happy_path_mint_wallet.rs

@@ -11,6 +11,7 @@
 use core::panic;
 use std::env;
 use std::fmt::Debug;
+use std::path::PathBuf;
 use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;
@@ -32,6 +33,14 @@ use tokio::time::timeout;
 use tokio_tungstenite::connect_async;
 use tokio_tungstenite::tungstenite::protocol::Message;
 
+// Helper function to get temp directory from environment or fallback
+fn get_test_temp_dir() -> PathBuf {
+    match env::var("CDK_ITESTS_DIR") {
+        Ok(dir) => PathBuf::from(dir),
+        Err(_) => panic!("Unknown test dir"),
+    }
+}
+
 async fn get_notification<T: StreamExt<Item = Result<Message, E>> + Unpin, E: Debug>(
     reader: &mut T,
     timeout_to_wait: Duration,
@@ -98,7 +107,9 @@ async fn test_happy_mint_melt_round_trip() {
     let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
-    pay_if_regtest(&invoice).await.unwrap();
+    pay_if_regtest(&get_test_temp_dir(), &invoice)
+        .await
+        .unwrap();
 
     wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 10)
         .await
@@ -113,7 +124,9 @@ async fn test_happy_mint_melt_round_trip() {
 
     assert!(mint_amount == 100.into());
 
-    let invoice = create_invoice_for_env(Some(50)).await.unwrap();
+    let invoice = create_invoice_for_env(&get_test_temp_dir(), Some(50))
+        .await
+        .unwrap();
 
     let melt = wallet.melt_quote(invoice, None).await.unwrap();
 
@@ -217,7 +230,9 @@ async fn test_happy_mint() {
     assert_eq!(mint_quote.amount, Some(mint_amount));
 
     let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
-    pay_if_regtest(&invoice).await.unwrap();
+    pay_if_regtest(&get_test_temp_dir(), &invoice)
+        .await
+        .unwrap();
 
     wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
         .await
@@ -262,7 +277,9 @@ async fn test_restore() {
     let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
-    pay_if_regtest(&invoice).await.unwrap();
+    pay_if_regtest(&get_test_temp_dir(), &invoice)
+        .await
+        .unwrap();
 
     wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
         .await
@@ -341,7 +358,7 @@ async fn test_fake_melt_change_in_quote() {
 
     let bolt11 = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
 
-    pay_if_regtest(&bolt11).await.unwrap();
+    pay_if_regtest(&get_test_temp_dir(), &bolt11).await.unwrap();
 
     wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
         .await
@@ -352,7 +369,9 @@ async fn test_fake_melt_change_in_quote() {
         .await
         .unwrap();
 
-    let invoice = create_invoice_for_env(Some(9)).await.unwrap();
+    let invoice = create_invoice_for_env(&get_test_temp_dir(), Some(9))
+        .await
+        .unwrap();
 
     let proofs = wallet.get_unspent_proofs().await.unwrap();
 
@@ -408,7 +427,7 @@ async fn test_pay_invoice_twice() {
 
     let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
-    pay_if_regtest(&mint_quote.request.parse().unwrap())
+    pay_if_regtest(&get_test_temp_dir(), &mint_quote.request.parse().unwrap())
         .await
         .unwrap();
 
@@ -425,7 +444,9 @@ async fn test_pay_invoice_twice() {
 
     assert_eq!(mint_amount, 100.into());
 
-    let invoice = create_invoice_for_env(Some(25)).await.unwrap();
+    let invoice = create_invoice_for_env(&get_test_temp_dir(), Some(25))
+        .await
+        .unwrap();
 
     let melt_quote = wallet.melt_quote(invoice.clone(), None).await.unwrap();
 

+ 5 - 0
crates/cdk-integration-tests/tests/integration_tests_pure.rs

@@ -3,6 +3,11 @@
 //! These tests verify the interaction between mint and wallet components, simulating real-world usage scenarios.
 //! They test the complete flow of operations including wallet funding, token swapping, sending tokens between wallets,
 //! and other operations that require client-mint interaction.
+//!
+//! Test Environment:
+//! - Uses pure in-memory mint instances for fast execution
+//! - Tests run concurrently with multi-threaded tokio runtime
+//! - No external dependencies (Lightning nodes, databases) required
 
 use std::assert_eq;
 use std::collections::{HashMap, HashSet};

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

@@ -1,8 +1,15 @@
-//! Mint tests
+//! Mint Tests
 //!
 //! This file contains tests that focus on the mint's internal functionality without client interaction.
 //! These tests verify the mint's behavior in isolation, such as keyset management, database operations,
 //! and other mint-specific functionality that doesn't require wallet clients.
+//!
+//! Test Categories:
+//! - Keyset rotation and management
+//! - Database transaction handling
+//! - Internal state transitions
+//! - Fee calculation and enforcement
+//! - Proof validation and state management
 
 use std::collections::{HashMap, HashSet};
 use std::sync::Arc;

+ 77 - 47
crates/cdk-integration-tests/tests/regtest.rs

@@ -1,4 +1,20 @@
-use std::str::FromStr;
+//! Regtest Integration Tests
+//!
+//! This file contains tests that run against actual Lightning Network nodes in regtest mode.
+//! These tests require a local development environment with LND nodes configured for regtest.
+//!
+//! Test Environment Setup:
+//! - Uses actual LND nodes connected to a regtest Bitcoin network
+//! - Tests real Lightning payment flows including invoice creation and payment
+//! - Verifies mint behavior with actual Lightning Network interactions
+//!
+//! Running Tests:
+//! - Requires CDK_TEST_REGTEST=1 environment variable to be set
+//! - Requires properly configured LND nodes with TLS certificates and macaroons
+//! - Uses real Bitcoin transactions in regtest mode
+
+use std::env;
+use std::path::PathBuf;
 use std::sync::Arc;
 use std::time::Duration;
 
@@ -10,32 +26,46 @@ use cdk::nuts::{
     NotificationPayload, PreMintSecrets,
 };
 use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletSubscription};
-use cdk_integration_tests::init_regtest::{
-    get_cln_dir, get_lnd_cert_file_path, get_lnd_dir, get_lnd_macaroon_path, get_mint_port,
-    LND_RPC_ADDR, LND_TWO_RPC_ADDR,
-};
+use cdk_integration_tests::init_regtest::{get_lnd_dir, LND_RPC_ADDR};
 use cdk_integration_tests::{
     get_mint_url_from_env, get_second_mint_url_from_env, wait_for_mint_to_be_paid,
 };
 use cdk_sqlite::wallet::{self, memory};
 use futures::join;
-use lightning_invoice::Bolt11Invoice;
-use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
-use ln_regtest_rs::InvoiceStatus;
+use ln_regtest_rs::ln_client::{LightningClient, LndClient};
 use tokio::time::timeout;
 
 // This is the ln wallet we use to send/receive ln payements as the wallet
 async fn init_lnd_client() -> LndClient {
-    let lnd_dir = get_lnd_dir("one");
+    // Try to get the temp directory from environment variable first (from .env file)
+    let temp_dir = match env::var("CDK_ITESTS_DIR") {
+        Ok(dir) => {
+            let path = PathBuf::from(dir);
+            println!("Using temp directory from CDK_ITESTS_DIR: {:?}", path);
+            path
+        }
+        Err(_) => {
+            panic!("Unknown temp dir");
+        }
+    };
+
+    // The LND mint uses the second LND node (LND_TWO_RPC_ADDR = localhost:10010)
+    let lnd_dir = get_lnd_dir(&temp_dir, "one");
     let cert_file = lnd_dir.join("tls.cert");
     let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
+
+    println!("Looking for LND cert file: {:?}", cert_file);
+    println!("Looking for LND macaroon file: {:?}", macaroon_file);
+    println!("Connecting to LND at: https://{}", LND_RPC_ADDR);
+
+    // Connect to LND
     LndClient::new(
         format!("https://{}", LND_RPC_ADDR),
-        cert_file,
-        macaroon_file,
+        cert_file.clone(),
+        macaroon_file.clone(),
     )
     .await
-    .unwrap()
+    .expect("Could not connect to lnd rpc")
 }
 
 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
@@ -98,41 +128,41 @@ async fn test_internal_payment() {
         .await
         .unwrap();
 
-    let check_paid = match get_mint_port("0") {
-        8085 => {
-            let cln_one_dir = get_cln_dir("one");
-            let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
-
-            let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
-            cln_client
-                .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
-                .await
-                .expect("Could not check invoice")
-        }
-        8087 => {
-            let lnd_two_dir = get_lnd_dir("two");
-            let lnd_client = LndClient::new(
-                format!("https://{}", LND_TWO_RPC_ADDR),
-                get_lnd_cert_file_path(&lnd_two_dir),
-                get_lnd_macaroon_path(&lnd_two_dir),
-            )
-            .await
-            .unwrap();
-            let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
-            lnd_client
-                .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
-                .await
-                .expect("Could not check invoice")
-        }
-        _ => panic!("Unknown mint port"),
-    };
-
-    match check_paid {
-        InvoiceStatus::Unpaid => (),
-        _ => {
-            panic!("Invoice has incorrect status: {:?}", check_paid);
-        }
-    }
+    // let check_paid = match get_mint_port("0") {
+    //     8085 => {
+    //         let cln_one_dir = get_cln_dir(&get_temp_dir(), "one");
+    //         let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
+
+    //         let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
+    //         cln_client
+    //             .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
+    //             .await
+    //             .expect("Could not check invoice")
+    //     }
+    //     8087 => {
+    //         let lnd_two_dir = get_lnd_dir(&get_temp_dir(), "two");
+    //         let lnd_client = LndClient::new(
+    //             format!("https://{}", LND_TWO_RPC_ADDR),
+    //             get_lnd_cert_file_path(&lnd_two_dir),
+    //             get_lnd_macaroon_path(&lnd_two_dir),
+    //         )
+    //         .await
+    //         .unwrap();
+    //         let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
+    //         lnd_client
+    //             .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
+    //             .await
+    //             .expect("Could not check invoice")
+    //     }
+    //     _ => panic!("Unknown mint port"),
+    // };
+
+    // match check_paid {
+    //     InvoiceStatus::Unpaid => (),
+    //     _ => {
+    //         panic!("Invoice has incorrect status: {:?}", check_paid);
+    //     }
+    // }
 
     let wallet_2_balance = wallet_2.total_balance().await.unwrap();
 

+ 6 - 3
crates/cdk-integration-tests/tests/test_fees.rs

@@ -6,6 +6,7 @@ use cashu::{Bolt11Invoice, ProofsMethods};
 use cdk::amount::{Amount, SplitTarget};
 use cdk::nuts::CurrencyUnit;
 use cdk::wallet::{ReceiveOptions, SendKind, SendOptions, Wallet};
+use cdk_integration_tests::init_regtest::get_temp_dir;
 use cdk_integration_tests::{
     create_invoice_for_env, get_mint_url_from_env, pay_if_regtest, wait_for_mint_to_be_paid,
 };
@@ -26,7 +27,7 @@ async fn test_swap() {
     let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
-    pay_if_regtest(&invoice).await.unwrap();
+    pay_if_regtest(&get_temp_dir(), &invoice).await.unwrap();
 
     wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 10)
         .await
@@ -93,7 +94,7 @@ async fn test_fake_melt_change_in_quote() {
 
     let bolt11 = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
 
-    pay_if_regtest(&bolt11).await.unwrap();
+    pay_if_regtest(&get_temp_dir(), &bolt11).await.unwrap();
 
     wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60)
         .await
@@ -106,7 +107,9 @@ async fn test_fake_melt_change_in_quote() {
 
     let invoice_amount = 9;
 
-    let invoice = create_invoice_for_env(Some(invoice_amount)).await.unwrap();
+    let invoice = create_invoice_for_env(&get_temp_dir(), Some(invoice_amount))
+        .await
+        .unwrap();
 
     let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
 

+ 1 - 1
crates/cdk-mint-rpc/Cargo.toml

@@ -23,7 +23,7 @@ anyhow.workspace = true
 cdk = { workspace = true, features = [
     "mint",
 ] }
-cdk-common = { workspace = true }
+cdk-common.workspace = true
 clap.workspace = true
 tonic = { workspace = true, features = ["transport"] }
 tracing.workspace = true

+ 40 - 12
crates/cdk-mint-rpc/src/bin/mint_rpc_cli.rs

@@ -7,22 +7,55 @@ use cdk_mint_rpc::GetInfoRequest;
 use clap::{Parser, Subcommand};
 use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
 use tonic::Request;
-use tracing::Level;
 use tracing_subscriber::EnvFilter;
 
+/// Common CLI arguments for CDK binaries
+#[derive(Parser, Debug)]
+pub struct CommonArgs {
+    /// Enable logging (default is false)
+    #[arg(long, default_value_t = false)]
+    pub enable_logging: bool,
+
+    /// Logging level when enabled (default is debug)
+    #[arg(long, default_value = "debug")]
+    pub log_level: tracing::Level,
+}
+
+/// Initialize logging based on CLI arguments
+pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
+    if enable_logging {
+        let default_filter = log_level.to_string();
+
+        // Common filters to reduce noise
+        let sqlx_filter = "sqlx=warn";
+        let hyper_filter = "hyper=warn";
+        let h2_filter = "h2=warn";
+        let rustls_filter = "rustls=warn";
+        let reqwest_filter = "reqwest=warn";
+
+        let env_filter = EnvFilter::new(format!(
+            "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
+        ));
+
+        // Ok if successful, Err if already initialized
+        let _ = tracing_subscriber::fmt()
+            .with_env_filter(env_filter)
+            .try_init();
+    }
+}
+
 const DEFAULT_WORK_DIR: &str = ".cdk-mint-rpc-cli";
 
 #[derive(Parser)]
 #[command(version, about, long_about = None)]
 struct Cli {
+    #[command(flatten)]
+    common: CommonArgs,
+
     /// Address of RPC server
     #[arg(short, long, default_value = "https://127.0.0.1:8086")]
     addr: String,
 
-    /// Logging level
-    #[arg(short, long, default_value = "debug")]
-    log_level: Level,
-
     /// Path to working dir
     #[arg(short, long)]
     work_dir: Option<PathBuf>,
@@ -70,14 +103,9 @@ enum Commands {
 #[tokio::main]
 async fn main() -> Result<()> {
     let args: Cli = Cli::parse();
-    let default_filter = args.log_level;
-
-    let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
-
-    let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter}"));
 
-    // Parse input
-    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+    // Initialize logging based on CLI arguments
+    init_logging(args.common.enable_logging, args.common.log_level);
 
     let cli = Cli::parse();
 

+ 8 - 0
crates/cdk-mintd/src/cli.rs

@@ -24,4 +24,12 @@ pub struct CLIArgs {
     pub config: Option<PathBuf>,
     #[arg(short, long, help = "Recover Greenlight from seed", required = false)]
     pub recover: Option<String>,
+    #[arg(
+        long,
+        help = "Enable logging output",
+        required = false,
+        action = clap::ArgAction::SetTrue,
+        default_value = "true"
+    )]
+    pub enable_logging: bool,
 }

+ 803 - 2
crates/cdk-mintd/src/lib.rs

@@ -1,13 +1,66 @@
 //! Cdk mintd lib
 
-#[cfg(feature = "cln")]
-use std::path::PathBuf;
+// std
+#[cfg(feature = "auth")]
+use std::collections::HashMap;
+use std::env;
+use std::net::SocketAddr;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::sync::Arc;
+
+// external crates
+use anyhow::{anyhow, bail, Result};
+use axum::Router;
+use bip39::Mnemonic;
+// internal crate modules
+use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
+use cdk::cdk_payment;
+use cdk::cdk_payment::MintPayment;
+use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
+#[cfg(any(
+    feature = "cln",
+    feature = "lnbits",
+    feature = "lnd",
+    feature = "fakewallet",
+    feature = "grpc-processor"
+))]
+use cdk::nuts::nut17::SupportedMethods;
+use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
+#[cfg(any(
+    feature = "cln",
+    feature = "lnbits",
+    feature = "lnd",
+    feature = "fakewallet"
+))]
+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;
+#[cfg(feature = "auth")]
+use cdk_sqlite::mint::MintSqliteAuthDatabase;
+use cdk_sqlite::MintSqliteDatabase;
+use cli::CLIArgs;
+use config::{DatabaseEngine, LnBackend};
+use env_vars::ENV_WORK_DIR;
+use setup::LnBackendSetup;
+use tower::ServiceBuilder;
+use tower_http::compression::CompressionLayer;
+use tower_http::decompression::RequestDecompressionLayer;
+use tower_http::trace::TraceLayer;
+use tracing_subscriber::EnvFilter;
+#[cfg(feature = "swagger")]
+use utoipa::OpenApi;
 
 pub mod cli;
 pub mod config;
 pub mod env_vars;
 pub mod setup;
 
+const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
+
 #[cfg(feature = "cln")]
 fn expand_path(path: &str) -> Option<PathBuf> {
     if path.starts_with('~') {
@@ -23,3 +76,751 @@ fn expand_path(path: &str) -> Option<PathBuf> {
         Some(PathBuf::from(path))
     }
 }
+
+/// Performs the initial setup for the application, including configuring tracing,
+/// parsing CLI arguments, setting up the working directory, loading settings,
+/// and initializing the database connection.
+async fn initial_setup(
+    work_dir: &Path,
+    settings: &config::Settings,
+    db_password: Option<String>,
+) -> Result<(
+    Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
+    Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
+)> {
+    let (localstore, keystore) = setup_database(settings, work_dir, db_password).await?;
+    Ok((localstore, keystore))
+}
+
+/// Sets up and initializes a tracing subscriber with custom log filtering.
+pub fn setup_tracing() {
+    let default_filter = "debug";
+    let hyper_filter = "hyper=warn";
+    let h2_filter = "h2=warn";
+    let tower_http = "tower_http=warn";
+
+    let env_filter = EnvFilter::new(format!(
+        "{default_filter},{hyper_filter},{h2_filter},{tower_http}"
+    ));
+
+    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+}
+
+/// Retrieves the work directory based on command-line arguments, environment variables, or system defaults.
+pub async fn get_work_directory(args: &CLIArgs) -> Result<PathBuf> {
+    let work_dir = if let Some(work_dir) = &args.work_dir {
+        tracing::info!("Using work dir from cmd arg");
+        work_dir.clone()
+    } else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
+        tracing::info!("Using work dir from env var");
+        env_work_dir.into()
+    } else {
+        work_dir()?
+    };
+    tracing::info!("Using work dir: {}", work_dir.display());
+    Ok(work_dir)
+}
+
+/// Loads the application settings based on a configuration file and environment variables.
+pub fn load_settings(work_dir: &Path, config_path: Option<PathBuf>) -> Result<config::Settings> {
+    // get config file name from args
+    let config_file_arg = match config_path {
+        Some(c) => c,
+        None => work_dir.join("config.toml"),
+    };
+
+    let mut settings = if config_file_arg.exists() {
+        config::Settings::new(Some(config_file_arg))
+    } else {
+        tracing::info!("Config file does not exist. Attempting to read env vars");
+        config::Settings::default()
+    };
+
+    // This check for any settings defined in ENV VARs
+    // ENV VARS will take **priority** over those in the config
+    settings.from_env()
+}
+
+async fn setup_database(
+    settings: &config::Settings,
+    work_dir: &Path,
+    db_password: Option<String>,
+) -> Result<(
+    Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
+    Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
+)> {
+    match settings.database.engine {
+        DatabaseEngine::Sqlite => {
+            let db = setup_sqlite_database(work_dir, db_password).await?;
+            let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> = db.clone();
+            let keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync> = db;
+            Ok((localstore, keystore))
+        }
+    }
+}
+
+async fn setup_sqlite_database(
+    work_dir: &Path,
+    _password: Option<String>,
+) -> Result<Arc<MintSqliteDatabase>> {
+    let sql_db_path = work_dir.join("cdk-mintd.sqlite");
+
+    #[cfg(not(feature = "sqlcipher"))]
+    let db = MintSqliteDatabase::new(&sql_db_path).await?;
+    #[cfg(feature = "sqlcipher")]
+    let db = {
+        // Get password from command line arguments for sqlcipher
+        MintSqliteDatabase::new((sql_db_path, _password.unwrap())).await?
+    };
+
+    Ok(Arc::new(db))
+}
+
+/**
+ * Configures a `MintBuilder` instance with provided settings and initializes
+ * routers for Lightning Network backends.
+ */
+async fn configure_mint_builder(
+    settings: &config::Settings,
+    mint_builder: MintBuilder,
+) -> Result<(MintBuilder, Vec<Router>)> {
+    let mut ln_routers = vec![];
+
+    // Configure basic mint information
+    let mint_builder = configure_basic_info(settings, mint_builder);
+
+    // Configure lightning backend
+    let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
+
+    // Configure caching
+    let mint_builder = configure_cache(settings, mint_builder);
+
+    Ok((mint_builder, ln_routers))
+}
+
+/// Configures basic mint information (name, contact info, descriptions, etc.)
+fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
+    // Add contact information
+    let mut contacts = Vec::new();
+    if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
+        contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
+    }
+    if let Some(email) = &settings.mint_info.contact_email {
+        contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
+    }
+
+    // Add version information
+    let mint_version = MintVersion::new(
+        "cdk-mintd".to_string(),
+        CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
+    );
+
+    // Configure mint builder with basic info
+    let mut builder = mint_builder
+        .with_name(settings.mint_info.name.clone())
+        .with_version(mint_version)
+        .with_description(settings.mint_info.description.clone());
+
+    // Add optional information
+    if let Some(long_description) = &settings.mint_info.description_long {
+        builder = builder.with_long_description(long_description.to_string());
+    }
+
+    for contact in contacts {
+        builder = builder.with_contact_info(contact);
+    }
+
+    if let Some(pubkey) = settings.mint_info.pubkey {
+        builder = builder.with_pubkey(pubkey);
+    }
+
+    if let Some(icon_url) = &settings.mint_info.icon_url {
+        builder = builder.with_icon_url(icon_url.to_string());
+    }
+
+    if let Some(motd) = &settings.mint_info.motd {
+        builder = builder.with_motd(motd.to_string());
+    }
+
+    if let Some(tos_url) = &settings.mint_info.tos_url {
+        builder = builder.with_tos_url(tos_url.to_string());
+    }
+
+    builder
+}
+/// Configures Lightning Network backend based on the specified backend type
+async fn configure_lightning_backend(
+    settings: &config::Settings,
+    mut mint_builder: MintBuilder,
+    ln_routers: &mut Vec<Router>,
+) -> Result<MintBuilder> {
+    let mint_melt_limits = MintMeltLimits {
+        mint_min: settings.ln.min_mint,
+        mint_max: settings.ln.max_mint,
+        melt_min: settings.ln.min_melt,
+        melt_max: settings.ln.max_melt,
+    };
+
+    tracing::debug!("Ln backend: {:?}", settings.ln.ln_backend);
+
+    match settings.ln.ln_backend {
+        #[cfg(feature = "cln")]
+        LnBackend::Cln => {
+            let cln_settings = settings
+                .cln
+                .clone()
+                .expect("Config checked at load that cln is some");
+            let cln = cln_settings
+                .setup(ln_routers, settings, CurrencyUnit::Msat)
+                .await?;
+
+            mint_builder = configure_backend_for_unit(
+                settings,
+                mint_builder,
+                CurrencyUnit::Sat,
+                mint_melt_limits,
+                Arc::new(cln),
+            )
+            .await?;
+        }
+        #[cfg(feature = "lnbits")]
+        LnBackend::LNbits => {
+            let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
+            let lnbits = lnbits_settings
+                .setup(ln_routers, settings, CurrencyUnit::Sat)
+                .await?;
+
+            mint_builder = configure_backend_for_unit(
+                settings,
+                mint_builder,
+                CurrencyUnit::Sat,
+                mint_melt_limits,
+                Arc::new(lnbits),
+            )
+            .await?;
+        }
+        #[cfg(feature = "lnd")]
+        LnBackend::Lnd => {
+            let lnd_settings = settings.clone().lnd.expect("Checked at config load");
+            let lnd = lnd_settings
+                .setup(ln_routers, settings, CurrencyUnit::Msat)
+                .await?;
+
+            mint_builder = configure_backend_for_unit(
+                settings,
+                mint_builder,
+                CurrencyUnit::Sat,
+                mint_melt_limits,
+                Arc::new(lnd),
+            )
+            .await?;
+        }
+        #[cfg(feature = "fakewallet")]
+        LnBackend::FakeWallet => {
+            let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
+            tracing::info!("Using fake wallet: {:?}", fake_wallet);
+
+            for unit in fake_wallet.clone().supported_units {
+                let fake = fake_wallet
+                    .setup(ln_routers, settings, CurrencyUnit::Sat)
+                    .await?;
+
+                mint_builder = configure_backend_for_unit(
+                    settings,
+                    mint_builder,
+                    unit.clone(),
+                    mint_melt_limits,
+                    Arc::new(fake),
+                )
+                .await?;
+            }
+        }
+        #[cfg(feature = "grpc-processor")]
+        LnBackend::GrpcProcessor => {
+            let grpc_processor = settings
+                .clone()
+                .grpc_processor
+                .expect("grpc processor config defined");
+
+            tracing::info!(
+                "Attempting to start with gRPC payment processor at {}:{}.",
+                grpc_processor.addr,
+                grpc_processor.port
+            );
+
+            for unit in grpc_processor.clone().supported_units {
+                tracing::debug!("Adding unit: {:?}", unit);
+                let processor = grpc_processor
+                    .setup(ln_routers, settings, unit.clone())
+                    .await?;
+
+                mint_builder = configure_backend_for_unit(
+                    settings,
+                    mint_builder,
+                    unit.clone(),
+                    mint_melt_limits,
+                    Arc::new(processor),
+                )
+                .await?;
+            }
+        }
+        LnBackend::None => {
+            tracing::error!(
+                "Payment backend was not set or feature disabled. {:?}",
+                settings.ln.ln_backend
+            );
+            bail!("Lightning backend must be configured");
+        }
+    };
+
+    Ok(mint_builder)
+}
+
+/// Helper function to configure a mint builder with a lightning backend for a specific currency unit
+async fn configure_backend_for_unit(
+    settings: &config::Settings,
+    mut mint_builder: MintBuilder,
+    unit: cdk::nuts::CurrencyUnit,
+    mint_melt_limits: MintMeltLimits,
+    backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
+) -> Result<MintBuilder> {
+    let payment_settings = backend.get_settings().await?;
+
+    if let Some(bolt12) = payment_settings.get("bolt12") {
+        if bolt12.as_bool().unwrap_or_default() {
+            mint_builder
+                .add_payment_processor(
+                    unit.clone(),
+                    PaymentMethod::Bolt12,
+                    mint_melt_limits,
+                    Arc::clone(&backend),
+                )
+                .await?;
+        }
+    }
+
+    mint_builder
+        .add_payment_processor(
+            unit.clone(),
+            PaymentMethod::Bolt11,
+            mint_melt_limits,
+            backend,
+        )
+        .await?;
+
+    if let Some(input_fee) = settings.info.input_fee_ppk {
+        mint_builder.set_unit_fee(&unit, input_fee)?;
+    }
+
+    let nut17_supported = SupportedMethods::default_bolt11(unit);
+    mint_builder = mint_builder.with_supported_websockets(nut17_supported);
+
+    Ok(mint_builder)
+}
+
+/// Configures cache settings
+fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
+    let cached_endpoints = vec![
+        CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
+        CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
+        CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
+    ];
+
+    let cache: HttpCache = settings.info.http_cache.clone().into();
+    mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
+}
+
+#[cfg(feature = "auth")]
+async fn setup_authentication(
+    settings: &config::Settings,
+    work_dir: &Path,
+    mut mint_builder: MintBuilder,
+    _password: Option<String>,
+) -> Result<MintBuilder> {
+    if let Some(auth_settings) = settings.auth.clone() {
+        tracing::info!("Auth settings are defined. {:?}", auth_settings);
+        let auth_localstore: Arc<
+            dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
+        > = match settings.database.engine {
+            DatabaseEngine::Sqlite => {
+                let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
+                #[cfg(not(feature = "sqlcipher"))]
+                let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
+                #[cfg(feature = "sqlcipher")]
+                let sqlite_db = {
+                    // Get password from command line arguments for sqlcipher
+                    MintSqliteAuthDatabase::new((sql_db_path, _password.unwrap())).await?
+                };
+
+                Arc::new(sqlite_db)
+            }
+        };
+
+        let mint_blind_auth_endpoint =
+            ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
+
+        let mut protected_endpoints = HashMap::new();
+
+        protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
+
+        let mut blind_auth_endpoints = vec![];
+        let mut unprotected_endpoints = vec![];
+
+        {
+            let mint_quote_protected_endpoint =
+                ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11);
+            let mint_protected_endpoint =
+                ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt11);
+            if auth_settings.enabled_mint {
+                protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
+
+                protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
+
+                blind_auth_endpoints.push(mint_quote_protected_endpoint);
+                blind_auth_endpoints.push(mint_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(mint_protected_endpoint);
+                unprotected_endpoints.push(mint_quote_protected_endpoint);
+            }
+        }
+
+        {
+            let melt_quote_protected_endpoint =
+                ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt11);
+            let melt_protected_endpoint =
+                ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
+
+            if auth_settings.enabled_melt {
+                protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
+                protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
+
+                blind_auth_endpoints.push(melt_quote_protected_endpoint);
+                blind_auth_endpoints.push(melt_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(melt_quote_protected_endpoint);
+                unprotected_endpoints.push(melt_protected_endpoint);
+            }
+        }
+
+        {
+            let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
+
+            if auth_settings.enabled_swap {
+                protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
+                blind_auth_endpoints.push(swap_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(swap_protected_endpoint);
+            }
+        }
+
+        {
+            let check_mint_protected_endpoint =
+                ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
+
+            if auth_settings.enabled_check_mint_quote {
+                protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
+                blind_auth_endpoints.push(check_mint_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(check_mint_protected_endpoint);
+            }
+        }
+
+        {
+            let check_melt_protected_endpoint =
+                ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
+
+            if auth_settings.enabled_check_melt_quote {
+                protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
+                blind_auth_endpoints.push(check_melt_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(check_melt_protected_endpoint);
+            }
+        }
+
+        {
+            let restore_protected_endpoint =
+                ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
+
+            if auth_settings.enabled_restore {
+                protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
+                blind_auth_endpoints.push(restore_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(restore_protected_endpoint);
+            }
+        }
+
+        {
+            let state_protected_endpoint =
+                ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
+
+            if auth_settings.enabled_check_proof_state {
+                protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
+                blind_auth_endpoints.push(state_protected_endpoint);
+            } else {
+                unprotected_endpoints.push(state_protected_endpoint);
+            }
+        }
+
+        mint_builder = mint_builder.with_auth(
+            auth_localstore.clone(),
+            auth_settings.openid_discovery,
+            auth_settings.openid_client_id,
+            vec![mint_blind_auth_endpoint],
+        );
+        mint_builder =
+            mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
+
+        let mut tx = auth_localstore.begin_transaction().await?;
+
+        tx.remove_protected_endpoints(unprotected_endpoints).await?;
+        tx.add_protected_endpoints(protected_endpoints).await?;
+        tx.commit().await?;
+    }
+    Ok(mint_builder)
+}
+
+/// Build mints with the configured the signing method (remote signatory or local seed)
+async fn build_mint(
+    settings: &config::Settings,
+    keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
+    mint_builder: MintBuilder,
+) -> Result<Mint> {
+    if let Some(signatory_url) = settings.info.signatory_url.clone() {
+        tracing::info!(
+            "Connecting to remote signatory to {} with certs {:?}",
+            signatory_url,
+            settings.info.signatory_certs.clone()
+        );
+
+        Ok(mint_builder
+            .build_with_signatory(Arc::new(
+                cdk_signatory::SignatoryRpcClient::new(
+                    signatory_url,
+                    settings.info.signatory_certs.clone(),
+                )
+                .await?,
+            ))
+            .await?)
+    } else if let Some(mnemonic) = settings
+        .info
+        .mnemonic
+        .clone()
+        .map(|s| Mnemonic::from_str(&s))
+        .transpose()?
+    {
+        Ok(mint_builder
+            .build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
+            .await?)
+    } else {
+        bail!("No seed nor remote signatory set");
+    }
+}
+
+async fn start_services_with_shutdown(
+    mint: Arc<cdk::mint::Mint>,
+    settings: &config::Settings,
+    ln_routers: Vec<Router>,
+    work_dir: &Path,
+    mint_builder_info: cdk::nuts::MintInfo,
+    shutdown_signal: impl std::future::Future<Output = ()> + Send + 'static,
+) -> Result<()> {
+    let listen_addr = settings.info.listen_host.clone();
+    let listen_port = settings.info.listen_port;
+    let cache: HttpCache = settings.info.http_cache.clone().into();
+
+    #[cfg(feature = "management-rpc")]
+    let mut rpc_enabled = false;
+    #[cfg(not(feature = "management-rpc"))]
+    let rpc_enabled = false;
+
+    #[cfg(feature = "management-rpc")]
+    let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
+
+    #[cfg(feature = "management-rpc")]
+    {
+        if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
+            if rpc_settings.enabled {
+                let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
+                let port = rpc_settings.port.unwrap_or(8086);
+                let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
+
+                let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
+
+                if !tls_dir.exists() {
+                    tracing::error!("TLS directory does not exist: {}", tls_dir.display());
+                    bail!("Cannot start RPC server: TLS directory does not exist");
+                }
+
+                mint_rpc.start(Some(tls_dir)).await?;
+
+                rpc_server = Some(mint_rpc);
+
+                rpc_enabled = true;
+            }
+        }
+    }
+
+    if rpc_enabled {
+        if mint.mint_info().await.is_err() {
+            tracing::info!("Mint info not set on mint, setting.");
+            mint.set_mint_info(mint_builder_info).await?;
+            mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
+        } else {
+            if mint.localstore().get_quote_ttl().await.is_err() {
+                mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
+            }
+            // Add version information
+            let mint_version = MintVersion::new(
+                "cdk-mintd".to_string(),
+                CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
+            );
+            let mut stored_mint_info = mint.mint_info().await?;
+            stored_mint_info.version = Some(mint_version);
+            mint.set_mint_info(stored_mint_info).await?;
+
+            tracing::info!("Mint info already set, not using config file settings.");
+        }
+    } else {
+        tracing::warn!("RPC not enabled, using mint info from config.");
+        mint.set_mint_info(mint_builder_info).await?;
+        mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
+    }
+
+    let mint_info = mint.mint_info().await?;
+    let nut04_methods = mint_info.nuts.nut04.supported_methods();
+    let nut05_methods = mint_info.nuts.nut05.supported_methods();
+
+    let bolt12_supported = nut04_methods.contains(&&PaymentMethod::Bolt12)
+        || nut05_methods.contains(&&PaymentMethod::Bolt12);
+
+    let v1_service =
+        cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache, bolt12_supported)
+            .await?;
+
+    let mut mint_service = Router::new()
+        .merge(v1_service)
+        .layer(
+            ServiceBuilder::new()
+                .layer(RequestDecompressionLayer::new())
+                .layer(CompressionLayer::new()),
+        )
+        .layer(TraceLayer::new_for_http());
+
+    #[cfg(feature = "swagger")]
+    {
+        if settings.info.enable_swagger_ui.unwrap_or(false) {
+            mint_service = mint_service.merge(
+                utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
+                    .url("/api-docs/openapi.json", cdk_axum::ApiDoc::openapi()),
+            );
+        }
+    }
+
+    for router in ln_routers {
+        mint_service = mint_service.merge(router);
+    }
+
+    mint.start().await?;
+
+    let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
+
+    let listener = tokio::net::TcpListener::bind(socket_addr).await?;
+
+    tracing::debug!("listening on {}", listener.local_addr().unwrap());
+
+    // Wait for axum server to complete with custom shutdown signal
+    let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal);
+
+    match axum_result.await {
+        Ok(_) => {
+            tracing::info!("Axum server stopped with okay status");
+        }
+        Err(err) => {
+            tracing::warn!("Axum server stopped with error");
+            tracing::error!("{}", err);
+            bail!("Axum exited with error")
+        }
+    }
+
+    mint.stop().await?;
+
+    #[cfg(feature = "management-rpc")]
+    {
+        if let Some(rpc_server) = rpc_server {
+            rpc_server.stop().await?;
+        }
+    }
+
+    Ok(())
+}
+
+async fn shutdown_signal() {
+    tokio::signal::ctrl_c()
+        .await
+        .expect("failed to install CTRL+C handler");
+    tracing::info!("Shutdown signal received");
+}
+
+fn work_dir() -> Result<PathBuf> {
+    let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
+    let dir = home_dir.join(".cdk-mintd");
+
+    std::fs::create_dir_all(&dir)?;
+
+    Ok(dir)
+}
+
+/// The main entry point for the application when used as a library.
+///
+/// This asynchronous function performs the following steps:
+/// 1. Executes the initial setup, including loading configurations and initializing the database.
+/// 2. Configures a `MintBuilder` instance with the local store and keystore based on the database.
+/// 3. Applies additional custom configurations and authentication setup for the `MintBuilder`.
+/// 4. Constructs a `Mint` instance from the configured `MintBuilder`.
+/// 5. Checks and resolves the status of any pending mint and melt quotes.
+pub async fn run_mintd(
+    work_dir: &Path,
+    settings: &config::Settings,
+    db_password: Option<String>,
+) -> Result<()> {
+    run_mintd_with_shutdown(work_dir, settings, shutdown_signal(), db_password).await
+}
+
+/// Run mintd with a custom shutdown signal
+pub async fn run_mintd_with_shutdown(
+    work_dir: &Path,
+    settings: &config::Settings,
+    shutdown_signal: impl std::future::Future<Output = ()> + Send + 'static,
+    db_password: Option<String>,
+) -> Result<()> {
+    let (localstore, keystore) = initial_setup(work_dir, settings, db_password.clone()).await?;
+
+    let mint_builder = MintBuilder::new(localstore);
+
+    let (mint_builder, ln_routers) = configure_mint_builder(settings, mint_builder).await?;
+    #[cfg(feature = "auth")]
+    let mint_builder = setup_authentication(settings, work_dir, mint_builder, db_password).await?;
+
+    let mint = build_mint(settings, keystore, mint_builder).await?;
+
+    tracing::debug!("Mint built from builder.");
+
+    let mint = Arc::new(mint);
+
+    // Checks the status of all pending melt quotes
+    // Pending melt quotes where the payment has gone through inputs are burnt
+    // Pending melt quotes where the payment has **failed** inputs are reset to unspent
+    mint.check_pending_melt_quotes().await?;
+
+    start_services_with_shutdown(
+        mint.clone(),
+        settings,
+        ln_routers,
+        work_dir,
+        mint.mint_info().await?,
+        shutdown_signal,
+    )
+    .await?;
+
+    Ok(())
+}

+ 15 - 780
crates/cdk-mintd/src/main.rs

@@ -2,63 +2,6 @@
 #![warn(missing_docs)]
 #![warn(rustdoc::bare_urls)]
 
-// std
-#[cfg(feature = "auth")]
-use std::collections::HashMap;
-use std::env;
-use std::net::SocketAddr;
-use std::path::{Path, PathBuf};
-use std::str::FromStr;
-use std::sync::Arc;
-
-// external crates
-use anyhow::{anyhow, bail, Result};
-use axum::Router;
-use bip39::Mnemonic;
-// internal crate modules
-use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
-use cdk::cdk_payment;
-use cdk::cdk_payment::MintPayment;
-use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
-#[cfg(any(
-    feature = "cln",
-    feature = "lnbits",
-    feature = "lnd",
-    feature = "fakewallet",
-    feature = "grpc-processor"
-))]
-use cdk::nuts::nut17::SupportedMethods;
-use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
-#[cfg(any(
-    feature = "cln",
-    feature = "lnbits",
-    feature = "lnd",
-    feature = "fakewallet"
-))]
-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_mintd::cli::CLIArgs;
-use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
-use cdk_mintd::env_vars::ENV_WORK_DIR;
-use cdk_mintd::setup::LnBackendSetup;
-#[cfg(feature = "auth")]
-use cdk_sqlite::mint::MintSqliteAuthDatabase;
-use cdk_sqlite::MintSqliteDatabase;
-use clap::Parser;
-use tower::ServiceBuilder;
-use tower_http::compression::CompressionLayer;
-use tower_http::decompression::RequestDecompressionLayer;
-use tower_http::trace::TraceLayer;
-use tracing_subscriber::EnvFilter;
-#[cfg(feature = "swagger")]
-use utoipa::OpenApi;
-
-const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
-
 // Ensure at least one lightning backend is enabled at compile time
 #[cfg(not(any(
     feature = "cln",
@@ -71,737 +14,29 @@ compile_error!(
     "At least one lightning backend feature must be enabled: cln, lnbits, lnd, fakewallet, or grpc-processor"
 );
 
-/// The main entry point for the application.
-///
-/// This asynchronous function performs the following steps:
-/// 1. Executes the initial setup, including loading configurations and initializing the database.
-/// 2. Configures a `MintBuilder` instance with the local store and keystore based on the database.
-/// 3. Applies additional custom configurations and authentication setup for the `MintBuilder`.
-/// 4. Constructs a `Mint` instance from the configured `MintBuilder`.
-/// 5. Checks and resolves the status of any pending mint and melt quotes.
-#[tokio::main]
-async fn main() -> Result<()> {
-    let (work_dir, settings, localstore, keystore) = initial_setup().await?;
-
-    let mint_builder = MintBuilder::new(localstore);
-
-    let (mint_builder, ln_routers) = configure_mint_builder(&settings, mint_builder).await?;
-    #[cfg(feature = "auth")]
-    let mint_builder = setup_authentication(&settings, &work_dir, mint_builder).await?;
-
-    let mint = build_mint(&settings, keystore, mint_builder).await?;
-
-    tracing::debug!("Mint built from builder.");
-
-    let mint = Arc::new(mint);
-
-    // Checks the status of all pending melt quotes
-    // Pending melt quotes where the payment has gone through inputs are burnt
-    // Pending melt quotes where the payment has **failed** inputs are reset to unspent
-    mint.check_pending_melt_quotes().await?;
+use anyhow::Result;
+use cdk_mintd::cli::CLIArgs;
+use cdk_mintd::{get_work_directory, load_settings, setup_tracing};
+use clap::Parser;
+use tokio::main;
 
-    start_services(
-        mint.clone(),
-        &settings,
-        ln_routers,
-        &work_dir,
-        mint.mint_info().await?,
-    )
-    .await?;
+#[main]
+async fn main() -> Result<()> {
+    let args = CLIArgs::parse();
 
-    Ok(())
-}
+    if args.enable_logging {
+        setup_tracing();
+    }
 
-/// Performs the initial setup for the application, including configuring tracing,
-/// parsing CLI arguments, setting up the working directory, loading settings,
-/// and initializing the database connection.
-async fn initial_setup() -> Result<(
-    PathBuf,
-    config::Settings,
-    Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
-    Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
-)> {
-    setup_tracing();
-    let args = CLIArgs::parse();
     let work_dir = get_work_directory(&args).await?;
 
     let settings = load_settings(&work_dir, args.config)?;
-    let (localstore, keystore) = setup_database(&settings, &work_dir).await?;
-    Ok((work_dir, settings, localstore, keystore))
-}
-
-/// Sets up and initializes a tracing subscriber with custom log filtering.
-fn setup_tracing() {
-    let default_filter = "debug";
-    let sqlx_filter = "sqlx=warn";
-    let hyper_filter = "hyper=warn";
-    let h2_filter = "h2=warn";
-    let tower_http = "tower_http=warn";
-
-    let env_filter = EnvFilter::new(format!(
-        "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{tower_http}"
-    ));
-
-    tracing_subscriber::fmt().with_env_filter(env_filter).init();
-}
-
-/// Retrieves the work directory based on command-line arguments, environment variables, or system defaults.
-async fn get_work_directory(args: &CLIArgs) -> Result<PathBuf> {
-    let work_dir = if let Some(work_dir) = &args.work_dir {
-        tracing::info!("Using work dir from cmd arg");
-        work_dir.clone()
-    } else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
-        tracing::info!("Using work dir from env var");
-        env_work_dir.into()
-    } else {
-        work_dir()?
-    };
-    tracing::info!("Using work dir: {}", work_dir.display());
-    Ok(work_dir)
-}
-
-/// Loads the application settings based on a configuration file and environment variables.
-fn load_settings(work_dir: &Path, config_path: Option<PathBuf>) -> Result<config::Settings> {
-    // get config file name from args
-    let config_file_arg = match config_path {
-        Some(c) => c,
-        None => work_dir.join("config.toml"),
-    };
-
-    let mut settings = if config_file_arg.exists() {
-        config::Settings::new(Some(config_file_arg))
-    } else {
-        tracing::info!("Config file does not exist. Attempting to read env vars");
-        config::Settings::default()
-    };
-
-    // This check for any settings defined in ENV VARs
-    // ENV VARS will take **priority** over those in the config
-    settings.from_env()
-}
-
-async fn setup_database(
-    settings: &config::Settings,
-    work_dir: &Path,
-) -> Result<(
-    Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
-    Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
-)> {
-    match settings.database.engine {
-        DatabaseEngine::Sqlite => {
-            #[cfg(feature = "sqlcipher")]
-            let password = CLIArgs::parse().password;
-            #[cfg(not(feature = "sqlcipher"))]
-            let password = String::new();
-            let db = setup_sqlite_database(work_dir, Some(password)).await?;
-            let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> = db.clone();
-            let keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync> = db;
-            Ok((localstore, keystore))
-        }
-    }
-}
 
-async fn setup_sqlite_database(
-    work_dir: &Path,
-    _password: Option<String>,
-) -> Result<Arc<MintSqliteDatabase>> {
-    let sql_db_path = work_dir.join("cdk-mintd.sqlite");
-    #[cfg(not(feature = "sqlcipher"))]
-    let db = MintSqliteDatabase::new(&sql_db_path).await?;
     #[cfg(feature = "sqlcipher")]
-    let db = {
-        // Get password from command line arguments for sqlcipher
-        MintSqliteDatabase::new((sql_db_path, _password.unwrap())).await?
-    };
-    Ok(Arc::new(db))
-}
-
-/**
- * Configures a `MintBuilder` instance with provided settings and initializes
- * routers for Lightning Network backends.
- */
-async fn configure_mint_builder(
-    settings: &config::Settings,
-    mint_builder: MintBuilder,
-) -> Result<(MintBuilder, Vec<Router>)> {
-    let mut ln_routers = vec![];
-
-    // Configure basic mint information
-    let mint_builder = configure_basic_info(settings, mint_builder);
-
-    // Configure lightning backend
-    let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
-
-    // Configure caching
-    let mint_builder = configure_cache(settings, mint_builder);
-
-    Ok((mint_builder, ln_routers))
-}
-
-/// Configures basic mint information (name, contact info, descriptions, etc.)
-fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
-    // Add contact information
-    let mut contacts = Vec::new();
-    if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
-        contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
-    }
-    if let Some(email) = &settings.mint_info.contact_email {
-        contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
-    }
-
-    // Add version information
-    let mint_version = MintVersion::new(
-        "cdk-mintd".to_string(),
-        CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
-    );
-
-    // Configure mint builder with basic info
-    let mut builder = mint_builder
-        .with_name(settings.mint_info.name.clone())
-        .with_version(mint_version)
-        .with_description(settings.mint_info.description.clone());
-
-    // Add optional information
-    if let Some(long_description) = &settings.mint_info.description_long {
-        builder = builder.with_long_description(long_description.to_string());
-    }
-
-    for contact in contacts {
-        builder = builder.with_contact_info(contact);
-    }
-
-    if let Some(pubkey) = settings.mint_info.pubkey {
-        builder = builder.with_pubkey(pubkey);
-    }
-
-    if let Some(icon_url) = &settings.mint_info.icon_url {
-        builder = builder.with_icon_url(icon_url.to_string());
-    }
-
-    if let Some(motd) = &settings.mint_info.motd {
-        builder = builder.with_motd(motd.to_string());
-    }
-
-    if let Some(tos_url) = &settings.mint_info.tos_url {
-        builder = builder.with_tos_url(tos_url.to_string());
-    }
-
-    builder
-}
-/// Configures Lightning Network backend based on the specified backend type
-async fn configure_lightning_backend(
-    settings: &config::Settings,
-    mut mint_builder: MintBuilder,
-    ln_routers: &mut Vec<Router>,
-) -> Result<MintBuilder> {
-    let mint_melt_limits = MintMeltLimits {
-        mint_min: settings.ln.min_mint,
-        mint_max: settings.ln.max_mint,
-        melt_min: settings.ln.min_melt,
-        melt_max: settings.ln.max_melt,
-    };
-
-    tracing::debug!("Ln backend: {:?}", settings.ln.ln_backend);
-
-    match settings.ln.ln_backend {
-        #[cfg(feature = "cln")]
-        LnBackend::Cln => {
-            let cln_settings = settings
-                .cln
-                .clone()
-                .expect("Config checked at load that cln is some");
-            let cln = cln_settings
-                .setup(ln_routers, settings, CurrencyUnit::Msat)
-                .await?;
-
-            mint_builder = configure_backend_for_unit(
-                settings,
-                mint_builder,
-                CurrencyUnit::Sat,
-                mint_melt_limits,
-                Arc::new(cln),
-            )
-            .await?;
-        }
-        #[cfg(feature = "lnbits")]
-        LnBackend::LNbits => {
-            let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
-            let lnbits = lnbits_settings
-                .setup(ln_routers, settings, CurrencyUnit::Sat)
-                .await?;
-
-            mint_builder = configure_backend_for_unit(
-                settings,
-                mint_builder,
-                CurrencyUnit::Sat,
-                mint_melt_limits,
-                Arc::new(lnbits),
-            )
-            .await?;
-        }
-        #[cfg(feature = "lnd")]
-        LnBackend::Lnd => {
-            let lnd_settings = settings.clone().lnd.expect("Checked at config load");
-            let lnd = lnd_settings
-                .setup(ln_routers, settings, CurrencyUnit::Msat)
-                .await?;
-
-            mint_builder = configure_backend_for_unit(
-                settings,
-                mint_builder,
-                CurrencyUnit::Sat,
-                mint_melt_limits,
-                Arc::new(lnd),
-            )
-            .await?;
-        }
-        #[cfg(feature = "fakewallet")]
-        LnBackend::FakeWallet => {
-            let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
-            tracing::info!("Using fake wallet: {:?}", fake_wallet);
-
-            for unit in fake_wallet.clone().supported_units {
-                let fake = fake_wallet
-                    .setup(ln_routers, settings, CurrencyUnit::Sat)
-                    .await?;
-
-                mint_builder = configure_backend_for_unit(
-                    settings,
-                    mint_builder,
-                    unit.clone(),
-                    mint_melt_limits,
-                    Arc::new(fake),
-                )
-                .await?;
-            }
-        }
-        #[cfg(feature = "grpc-processor")]
-        LnBackend::GrpcProcessor => {
-            let grpc_processor = settings
-                .clone()
-                .grpc_processor
-                .expect("grpc processor config defined");
-
-            tracing::info!(
-                "Attempting to start with gRPC payment processor at {}:{}.",
-                grpc_processor.addr,
-                grpc_processor.port
-            );
-
-            for unit in grpc_processor.clone().supported_units {
-                tracing::debug!("Adding unit: {:?}", unit);
-                let processor = grpc_processor
-                    .setup(ln_routers, settings, unit.clone())
-                    .await?;
-
-                mint_builder = configure_backend_for_unit(
-                    settings,
-                    mint_builder,
-                    unit.clone(),
-                    mint_melt_limits,
-                    Arc::new(processor),
-                )
-                .await?;
-            }
-        }
-        LnBackend::None => {
-            tracing::error!(
-                "Payment backend was not set or feature disabled. {:?}",
-                settings.ln.ln_backend
-            );
-            bail!("Lightning backend must be configured");
-        }
-    };
-
-    Ok(mint_builder)
-}
+    let password = Some(CLIArgs::parse().password);
 
-/// Helper function to configure a mint builder with a lightning backend for a specific currency unit
-async fn configure_backend_for_unit(
-    settings: &config::Settings,
-    mut mint_builder: MintBuilder,
-    unit: cdk::nuts::CurrencyUnit,
-    mint_melt_limits: MintMeltLimits,
-    backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
-) -> Result<MintBuilder> {
-    let payment_settings = backend.get_settings().await?;
-
-    if let Some(bolt12) = payment_settings.get("bolt12") {
-        if bolt12.as_bool().unwrap_or_default() {
-            mint_builder
-                .add_payment_processor(
-                    unit.clone(),
-                    PaymentMethod::Bolt12,
-                    mint_melt_limits,
-                    Arc::clone(&backend),
-                )
-                .await?;
-        }
-    }
-
-    mint_builder
-        .add_payment_processor(
-            unit.clone(),
-            PaymentMethod::Bolt11,
-            mint_melt_limits,
-            backend,
-        )
-        .await?;
-
-    if let Some(input_fee) = settings.info.input_fee_ppk {
-        mint_builder.set_unit_fee(&unit, input_fee)?;
-    }
-
-    let nut17_supported = SupportedMethods::default_bolt11(unit);
-    mint_builder = mint_builder.with_supported_websockets(nut17_supported);
-
-    Ok(mint_builder)
-}
-
-/// Configures cache settings
-fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
-    let cached_endpoints = vec![
-        CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
-        CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
-        CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
-    ];
-
-    let cache: HttpCache = settings.info.http_cache.clone().into();
-    mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
-}
-
-#[cfg(feature = "auth")]
-async fn setup_authentication(
-    settings: &config::Settings,
-    work_dir: &Path,
-    mut mint_builder: MintBuilder,
-) -> Result<MintBuilder> {
-    if let Some(auth_settings) = settings.auth.clone() {
-        tracing::info!("Auth settings are defined. {:?}", auth_settings);
-        let auth_localstore: Arc<
-            dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
-        > = match settings.database.engine {
-            DatabaseEngine::Sqlite => {
-                let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
-                #[cfg(feature = "sqlcipher")]
-                let password = CLIArgs::parse().password;
-                #[cfg(feature = "sqlcipher")]
-                let sqlite_db = MintSqliteAuthDatabase::new((sql_db_path, password)).await?;
-                #[cfg(not(feature = "sqlcipher"))]
-                let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
-                Arc::new(sqlite_db)
-            }
-        };
-
-        let mint_blind_auth_endpoint =
-            ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
-
-        let mut protected_endpoints = HashMap::new();
-
-        protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
-
-        let mut blind_auth_endpoints = vec![];
-        let mut unprotected_endpoints = vec![];
-
-        {
-            let mint_quote_protected_endpoint =
-                ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11);
-            let mint_protected_endpoint =
-                ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt11);
-            if auth_settings.enabled_mint {
-                protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
-
-                protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
-
-                blind_auth_endpoints.push(mint_quote_protected_endpoint);
-                blind_auth_endpoints.push(mint_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(mint_protected_endpoint);
-                unprotected_endpoints.push(mint_quote_protected_endpoint);
-            }
-        }
-
-        {
-            let melt_quote_protected_endpoint =
-                ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt11);
-            let melt_protected_endpoint =
-                ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
-
-            if auth_settings.enabled_melt {
-                protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
-                protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
-
-                blind_auth_endpoints.push(melt_quote_protected_endpoint);
-                blind_auth_endpoints.push(melt_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(melt_quote_protected_endpoint);
-                unprotected_endpoints.push(melt_protected_endpoint);
-            }
-        }
-
-        {
-            let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
-
-            if auth_settings.enabled_swap {
-                protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
-                blind_auth_endpoints.push(swap_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(swap_protected_endpoint);
-            }
-        }
-
-        {
-            let check_mint_protected_endpoint =
-                ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
-
-            if auth_settings.enabled_check_mint_quote {
-                protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
-                blind_auth_endpoints.push(check_mint_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(check_mint_protected_endpoint);
-            }
-        }
-
-        {
-            let check_melt_protected_endpoint =
-                ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
-
-            if auth_settings.enabled_check_melt_quote {
-                protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
-                blind_auth_endpoints.push(check_melt_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(check_melt_protected_endpoint);
-            }
-        }
-
-        {
-            let restore_protected_endpoint =
-                ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
-
-            if auth_settings.enabled_restore {
-                protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
-                blind_auth_endpoints.push(restore_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(restore_protected_endpoint);
-            }
-        }
-
-        {
-            let state_protected_endpoint =
-                ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
-
-            if auth_settings.enabled_check_proof_state {
-                protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
-                blind_auth_endpoints.push(state_protected_endpoint);
-            } else {
-                unprotected_endpoints.push(state_protected_endpoint);
-            }
-        }
-
-        mint_builder = mint_builder.with_auth(
-            auth_localstore.clone(),
-            auth_settings.openid_discovery,
-            auth_settings.openid_client_id,
-            vec![mint_blind_auth_endpoint],
-        );
-        mint_builder =
-            mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
-
-        let mut tx = auth_localstore.begin_transaction().await?;
-
-        tx.remove_protected_endpoints(unprotected_endpoints).await?;
-        tx.add_protected_endpoints(protected_endpoints).await?;
-        tx.commit().await?;
-    }
-    Ok(mint_builder)
-}
-
-/// Build mints with the configured the signing method (remote signatory or local seed)
-async fn build_mint(
-    settings: &config::Settings,
-    keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
-    mint_builder: MintBuilder,
-) -> Result<Mint> {
-    if let Some(signatory_url) = settings.info.signatory_url.clone() {
-        tracing::info!(
-            "Connecting to remote signatory to {} with certs {:?}",
-            signatory_url,
-            settings.info.signatory_certs.clone()
-        );
-
-        Ok(mint_builder
-            .build_with_signatory(Arc::new(
-                cdk_signatory::SignatoryRpcClient::new(
-                    signatory_url,
-                    settings.info.signatory_certs.clone(),
-                )
-                .await?,
-            ))
-            .await?)
-    } else if let Some(mnemonic) = settings
-        .info
-        .mnemonic
-        .clone()
-        .map(|s| Mnemonic::from_str(&s))
-        .transpose()?
-    {
-        Ok(mint_builder
-            .build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
-            .await?)
-    } else {
-        bail!("No seed nor remote signatory set");
-    }
-}
-
-async fn start_services(
-    mint: Arc<cdk::mint::Mint>,
-    settings: &config::Settings,
-    ln_routers: Vec<Router>,
-    _work_dir: &Path,
-    mint_builder_info: cdk::nuts::MintInfo,
-) -> Result<()> {
-    let listen_addr = settings.info.listen_host.clone();
-    let listen_port = settings.info.listen_port;
-    let cache: HttpCache = settings.info.http_cache.clone().into();
-
-    #[cfg(feature = "management-rpc")]
-    let mut rpc_enabled = false;
-    #[cfg(not(feature = "management-rpc"))]
-    let rpc_enabled = false;
-
-    #[cfg(feature = "management-rpc")]
-    let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
-
-    #[cfg(feature = "management-rpc")]
-    {
-        if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
-            if rpc_settings.enabled {
-                let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
-                let port = rpc_settings.port.unwrap_or(8086);
-                let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
-
-                let tls_dir = rpc_settings.tls_dir_path.unwrap_or(_work_dir.join("tls"));
-
-                if !tls_dir.exists() {
-                    tracing::error!("TLS directory does not exist: {}", tls_dir.display());
-                    bail!("Cannot start RPC server: TLS directory does not exist");
-                }
-
-                mint_rpc.start(Some(tls_dir)).await?;
-
-                rpc_server = Some(mint_rpc);
-
-                rpc_enabled = true;
-            }
-        }
-    }
-
-    if rpc_enabled {
-        if mint.mint_info().await.is_err() {
-            tracing::info!("Mint info not set on mint, setting.");
-            mint.set_mint_info(mint_builder_info).await?;
-            mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
-        } else {
-            if mint.localstore().get_quote_ttl().await.is_err() {
-                mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
-            }
-            // Add version information
-            let mint_version = MintVersion::new(
-                "cdk-mintd".to_string(),
-                CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
-            );
-            let mut stored_mint_info = mint.mint_info().await?;
-            stored_mint_info.version = Some(mint_version);
-            mint.set_mint_info(stored_mint_info).await?;
-
-            tracing::info!("Mint info already set, not using config file settings.");
-        }
-    } else {
-        tracing::warn!("RPC not enabled, using mint info from config.");
-        mint.set_mint_info(mint_builder_info).await?;
-        mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
-    }
-
-    let mint_info = mint.mint_info().await?;
-    let nut04_methods = mint_info.nuts.nut04.supported_methods();
-    let nut05_methods = mint_info.nuts.nut05.supported_methods();
-
-    let bolt12_supported = nut04_methods.contains(&&PaymentMethod::Bolt12)
-        || nut05_methods.contains(&&PaymentMethod::Bolt12);
-
-    let v1_service =
-        cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache, bolt12_supported)
-            .await?;
-
-    let mut mint_service = Router::new()
-        .merge(v1_service)
-        .layer(
-            ServiceBuilder::new()
-                .layer(RequestDecompressionLayer::new())
-                .layer(CompressionLayer::new()),
-        )
-        .layer(TraceLayer::new_for_http());
-
-    #[cfg(feature = "swagger")]
-    {
-        if settings.info.enable_swagger_ui.unwrap_or(false) {
-            mint_service = mint_service.merge(
-                utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
-                    .url("/api-docs/openapi.json", cdk_axum::ApiDoc::openapi()),
-            );
-        }
-    }
-
-    for router in ln_routers {
-        mint_service = mint_service.merge(router);
-    }
-
-    mint.start().await?;
-
-    let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
-
-    let listener = tokio::net::TcpListener::bind(socket_addr).await?;
-
-    tracing::debug!("listening on {}", listener.local_addr().unwrap());
-
-    // Wait for axum server to complete
-    let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
-
-    match axum_result.await {
-        Ok(_) => {
-            tracing::info!("Axum server stopped with okay status");
-        }
-        Err(err) => {
-            tracing::warn!("Axum server stopped with error");
-            tracing::error!("{}", err);
-            bail!("Axum exited with error")
-        }
-    }
-
-    mint.stop().await?;
-
-    #[cfg(feature = "management-rpc")]
-    {
-        if let Some(rpc_server) = rpc_server {
-            rpc_server.stop().await?;
-        }
-    }
-
-    Ok(())
-}
-
-async fn shutdown_signal() {
-    tokio::signal::ctrl_c()
-        .await
-        .expect("failed to install CTRL+C handler");
-    tracing::info!("Shutdown signal received");
-}
-
-fn work_dir() -> Result<PathBuf> {
-    let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
-    let dir = home_dir.join(".cdk-mintd");
-
-    std::fs::create_dir_all(&dir)?;
+    #[cfg(not(feature = "sqlcipher"))]
+    let password = None;
 
-    Ok(dir)
+    cdk_mintd::run_mintd(&work_dir, &settings, password).await
 }

+ 1 - 0
crates/cdk-payment-processor/Cargo.toml

@@ -30,6 +30,7 @@ cdk-common = { workspace = true, features = ["mint"] }
 cdk-cln = { workspace = true, optional = true }
 cdk-lnd = { workspace = true, optional = true }
 cdk-fake-wallet = { workspace = true, optional = true }
+clap = { workspace = true, features = ["derive"] }
 serde.workspace = true
 thiserror.workspace = true
 tracing.workspace = true

+ 47 - 11
crates/cdk-payment-processor/src/bin/payment_processor.rs

@@ -14,11 +14,47 @@ use cdk_common::payment::{self, MintPayment};
 use cdk_common::Amount;
 #[cfg(feature = "fake")]
 use cdk_fake_wallet::FakeWallet;
+use clap::Parser;
 use serde::{Deserialize, Serialize};
 #[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
 use tokio::signal;
 use tracing_subscriber::EnvFilter;
 
+/// Common CLI arguments for CDK binaries
+#[derive(Parser, Debug)]
+pub struct CommonArgs {
+    /// Enable logging (default is false)
+    #[arg(long, default_value_t = false)]
+    pub enable_logging: bool,
+
+    /// Logging level when enabled (default is debug)
+    #[arg(long, default_value = "debug")]
+    pub log_level: tracing::Level,
+}
+
+/// Initialize logging based on CLI arguments
+pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
+    if enable_logging {
+        let default_filter = log_level.to_string();
+
+        // Common filters to reduce noise
+        let sqlx_filter = "sqlx=warn";
+        let hyper_filter = "hyper=warn";
+        let h2_filter = "h2=warn";
+        let rustls_filter = "rustls=warn";
+        let reqwest_filter = "reqwest=warn";
+
+        let env_filter = EnvFilter::new(format!(
+            "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
+        ));
+
+        // Ok if successful, Err if already initialized
+        let _ = tracing_subscriber::fmt()
+            .with_env_filter(env_filter)
+            .try_init();
+    }
+}
+
 pub const ENV_LN_BACKEND: &str = "CDK_PAYMENT_PROCESSOR_LN_BACKEND";
 pub const ENV_LISTEN_HOST: &str = "CDK_PAYMENT_PROCESSOR_LISTEN_HOST";
 pub const ENV_LISTEN_PORT: &str = "CDK_PAYMENT_PROCESSOR_LISTEN_PORT";
@@ -36,20 +72,20 @@ pub const ENV_LND_ADDRESS: &str = "CDK_PAYMENT_PROCESSOR_LND_ADDRESS";
 pub const ENV_LND_CERT_FILE: &str = "CDK_PAYMENT_PROCESSOR_LND_CERT_FILE";
 pub const ENV_LND_MACAROON_FILE: &str = "CDK_PAYMENT_PROCESSOR_LND_MACAROON_FILE";
 
+#[derive(Parser)]
+#[command(name = "payment-processor")]
+#[command(about = "CDK Payment Processor", long_about = None)]
+struct Args {
+    #[command(flatten)]
+    common: CommonArgs,
+}
+
 #[tokio::main]
 async fn main() -> anyhow::Result<()> {
-    let default_filter = "debug";
-
-    let sqlx_filter = "sqlx=warn";
-    let hyper_filter = "hyper=warn";
-    let h2_filter = "h2=warn";
-    let rustls_filter = "rustls=warn";
-
-    let env_filter = EnvFilter::new(format!(
-        "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter}"
-    ));
+    let args = Args::parse();
 
-    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+    // Initialize logging based on CLI arguments
+    init_logging(args.common.enable_logging, args.common.log_level);
 
     #[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
     {

+ 42 - 12
crates/cdk-signatory/src/bin/cli/mod.rs

@@ -17,9 +17,43 @@ use cdk_signatory::{db_signatory, start_grpc_server};
 #[cfg(feature = "sqlite")]
 use cdk_sqlite::MintSqliteDatabase;
 use clap::Parser;
-use tracing::Level;
 use tracing_subscriber::EnvFilter;
 
+/// Common CLI arguments for CDK binaries
+#[derive(Parser, Debug)]
+pub struct CommonArgs {
+    /// Enable logging (default is false)
+    #[arg(long, default_value_t = false)]
+    pub enable_logging: bool,
+
+    /// Logging level when enabled (default is debug)
+    #[arg(long, default_value = "debug")]
+    pub log_level: tracing::Level,
+}
+
+/// Initialize logging based on CLI arguments
+pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
+    if enable_logging {
+        let default_filter = log_level.to_string();
+
+        // Common filters to reduce noise
+        let sqlx_filter = "sqlx=warn";
+        let hyper_filter = "hyper=warn";
+        let h2_filter = "h2=warn";
+        let rustls_filter = "rustls=warn";
+        let reqwest_filter = "reqwest=warn";
+
+        let env_filter = EnvFilter::new(format!(
+            "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
+        ));
+
+        // Ok if successful, Err if already initialized
+        let _ = tracing_subscriber::fmt()
+            .with_env_filter(env_filter)
+            .try_init();
+    }
+}
+
 const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
 const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
 
@@ -30,6 +64,9 @@ const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
 #[command(version = "0.1.0")]
 #[command(author, version, about, long_about = None)]
 struct Cli {
+    #[command(flatten)]
+    common: CommonArgs,
+
     /// Database engine to use (sqlite/redb)
     #[arg(short, long, default_value = "sqlite")]
     engine: String,
@@ -39,9 +76,6 @@ struct Cli {
     /// Path to working dir
     #[arg(short, long)]
     work_dir: Option<PathBuf>,
-    /// Logging level
-    #[arg(short, long, default_value = "debug")]
-    log_level: Level,
     #[arg(long, default_value = "127.0.0.1")]
     listen_addr: String,
     #[arg(long, default_value = "15060")]
@@ -56,7 +90,10 @@ struct Cli {
 /// Main function for the signatory standalone binary
 pub async fn cli_main() -> Result<()> {
     let args: Cli = Cli::parse();
-    let default_filter = args.log_level;
+
+    // Initialize logging based on CLI arguments
+    init_logging(args.common.enable_logging, args.common.log_level);
+
     let supported_units = args
         .units
         .into_iter()
@@ -74,13 +111,6 @@ pub async fn cli_main() -> Result<()> {
         })
         .collect::<Result<HashMap<_, _>, _>>()?;
 
-    let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
-
-    let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter}"));
-
-    // Parse input
-    tracing_subscriber::fmt().with_env_filter(env_filter).init();
-
     let work_dir = match &args.work_dir {
         Some(work_dir) => work_dir.clone(),
         None => {

+ 41 - 66
misc/fake_auth_itests.sh

@@ -1,31 +1,37 @@
-
 #!/usr/bin/env bash
 
 # Function to perform cleanup
 cleanup() {
     echo "Cleaning up..."
 
-    echo "Killing the cdk mintd"
-    kill -2 $cdk_mintd_pid
-    wait $cdk_mintd_pid
+    if [ -n "$FAKE_AUTH_MINT_PID" ]; then
+        echo "Killing the fake auth mint process"
+        kill -2 $FAKE_AUTH_MINT_PID 2>/dev/null || true
+        wait $FAKE_AUTH_MINT_PID 2>/dev/null || true
+    fi
 
     echo "Mint binary terminated"
     
     # Remove the temporary directory
-    rm -rf "$CDK_ITESTS_DIR"
-    echo "Temp directory removed: $CDK_ITESTS_DIR"
+    if [ -n "$CDK_ITESTS_DIR" ] && [ -d "$CDK_ITESTS_DIR" ]; then
+        rm -rf "$CDK_ITESTS_DIR"
+        echo "Temp directory removed: $CDK_ITESTS_DIR"
+    fi
+
+    # Unset all environment variables
     unset CDK_ITESTS_DIR
     unset CDK_ITESTS_MINT_ADDR
     unset CDK_ITESTS_MINT_PORT
+    unset FAKE_AUTH_MINT_PID
 }
 
 # Set up trap to call cleanup on script exit
-trap cleanup EXIT
+trap cleanup EXIT INT TERM
 
 # Create a temporary directory
 export CDK_ITESTS_DIR=$(mktemp -d)
-export CDK_ITESTS_MINT_ADDR="127.0.0.1";
-export CDK_ITESTS_MINT_PORT=8087;
+export CDK_ITESTS_MINT_ADDR="127.0.0.1"
+export CDK_ITESTS_MINT_PORT=8087
 
 # Check if the temporary directory was created successfully
 if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
@@ -34,72 +40,41 @@ if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
 fi
 
 echo "Temp directory created: $CDK_ITESTS_DIR"
-export MINT_DATABASE="$1";
-export OPENID_DISCOVERY="$2";
 
-cargo build -p cdk-integration-tests 
+# Check if a database type was provided as first argument, default to sqlite
+export MINT_DATABASE="${1:-sqlite}"
+
+# Check if OPENID_DISCOVERY was provided as second argument, default to a test value
+export OPENID_DISCOVERY="${2:-http://127.0.0.1:8080/realms/cdk-test-realm/.well-known/openid-configuration}"
 
-export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT";
-export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR";
-export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR;
-export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT;
-export CDK_MINTD_LN_BACKEND="fakewallet";
-export CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS="sat";
-export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal";
-export CDK_MINTD_FAKE_WALLET_FEE_PERCENT="0";
-export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1";
-export CDK_MINTD_DATABASE=$MINT_DATABASE;
+# Build the project
+cargo build -p cdk-integration-tests 
 
 # Auth configuration
-export CDK_TEST_OIDC_USER="cdk-test";
-export CDK_TEST_OIDC_PASSWORD="cdkpassword";
-
-export CDK_MINTD_AUTH_OPENID_DISCOVERY=$OPENID_DISCOVERY;
-export CDK_MINTD_AUTH_OPENID_CLIENT_ID="cashu-client";
-export CDK_MINTD_AUTH_MINT_MAX_BAT="50";
-export CDK_MINTD_AUTH_ENABLED_MINT="true";
-export CDK_MINTD_AUTH_ENABLED_MELT="true";
-export CDK_MINTD_AUTH_ENABLED_SWAP="true";
-export CDK_MINTD_AUTH_ENABLED_CHECK_MINT_QUOTE="true";
-export CDK_MINTD_AUTH_ENABLED_CHECK_MELT_QUOTE="true";
-export CDK_MINTD_AUTH_ENABLED_RESTORE="true";
-export CDK_MINTD_AUTH_ENABLED_CHECK_PROOF_STATE="true";
-
-echo "Starting auth mintd";
-cargo run --bin cdk-mintd --features redb &
-cdk_mintd_pid=$!
-
-URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT/v1/info"
-TIMEOUT=100
-START_TIME=$(date +%s)
-# Loop until the endpoint returns a 200 OK status or timeout is reached
-while true; do
-    # Get the current time
-    CURRENT_TIME=$(date +%s)
-    
-    # Calculate the elapsed time
-    ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
+export CDK_TEST_OIDC_USER="cdk-test"
+export CDK_TEST_OIDC_PASSWORD="cdkpassword"
 
-    # Check if the elapsed time exceeds the timeout
-    if [ $ELAPSED_TIME -ge $TIMEOUT ]; then
-        echo "Timeout of $TIMEOUT seconds reached. Exiting..."
-        exit 1
-    fi
+# Start the fake auth mint in the background
+echo "Starting fake auth mint with discovery URL: $OPENID_DISCOVERY"
+echo "Using temp directory: $CDK_ITESTS_DIR"
+cargo run -p cdk-integration-tests --bin start_fake_auth_mint -- --enable-logging "$MINT_DATABASE" "$CDK_ITESTS_DIR" "$OPENID_DISCOVERY" "$CDK_ITESTS_MINT_PORT" &
 
-    # Make a request to the endpoint and capture the HTTP status code
-    HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}" $URL)
+# Store the PID of the mint process
+FAKE_AUTH_MINT_PID=$!
 
-    # Check if the HTTP status is 200 OK
-    if [ "$HTTP_STATUS" -eq 200 ]; then
-        echo "Received 200 OK from $URL"
-        break
-    else
-        echo "Waiting for 200 OK response, current status: $HTTP_STATUS"
-        sleep 2  # Wait for 2 seconds before retrying
-    fi
-done
+# Wait a moment for the mint to start
+sleep 5
+
+# Check if the mint is running
+if ! kill -0 $FAKE_AUTH_MINT_PID 2>/dev/null; then
+    echo "Failed to start fake auth mint"
+    exit 1
+fi
+
+echo "Fake auth mint started with PID: $FAKE_AUTH_MINT_PID"
 
 # Run cargo test
+echo "Running fake auth integration tests..."
 cargo test -p cdk-integration-tests --test fake_auth
 
 # Capture the exit status of cargo test

+ 81 - 45
misc/fake_itests.sh

@@ -1,46 +1,45 @@
 #!/usr/bin/env bash
 
+# Script to run fake mint tests with proper handling of race conditions
+# This script ensures the .env file is properly created and available 
+# before running tests
+
 # Function to perform cleanup
 cleanup() {
     echo "Cleaning up..."
 
-    echo "Killing the cdk mintd"
-    kill -2 $CDK_MINTD_PID
-    wait $CDK_MINTD_PID
-    kill -9 $CDK_SIGNATORY_PID
-    wait $CDK_SIGNATORY_PID
+    if [ -n "$FAKE_MINT_PID" ]; then
+        echo "Killing the fake mint process"
+        kill -2 $FAKE_MINT_PID 2>/dev/null || true
+        wait $FAKE_MINT_PID 2>/dev/null || true
+    fi
+
+    if [ -n "$CDK_SIGNATORY_PID" ]; then
+        echo "Killing the signatory process"
+        kill -9 $CDK_SIGNATORY_PID 2>/dev/null || true
+        wait $CDK_SIGNATORY_PID 2>/dev/null || true
+    fi
 
     echo "Mint binary terminated"
 
     # Remove the temporary directory
-    rm -rf "$CDK_ITESTS_DIR"
-    echo "Temp directory removed: $CDK_ITESTS_DIR"
+    if [ -n "$CDK_ITESTS_DIR" ] && [ -d "$CDK_ITESTS_DIR" ]; then
+        rm -rf "$CDK_ITESTS_DIR"
+        echo "Temp directory removed: $CDK_ITESTS_DIR"
+    fi
 
     # Unset all environment variables
     unset CDK_ITESTS_DIR
-    unset CDK_ITESTS_MINT_ADDR
-    unset CDK_ITESTS_MINT_PORT
-    unset CDK_MINTD_DATABASE
     unset CDK_TEST_MINT_URL
-    unset CDK_MINTD_URL
-    unset CDK_MINTD_WORK_DIR
-    unset CDK_MINTD_LISTEN_HOST
-    unset CDK_MINTD_LISTEN_PORT
-    unset CDK_MINTD_LN_BACKEND
-    unset CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS
-    unset CDK_MINTD_MNEMONIC
-    unset CDK_MINTD_FAKE_WALLET_FEE_PERCENT
-    unset CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN
-    unset CDK_MINTD_PID
+    unset FAKE_MINT_PID
+    unset CDK_SIGNATORY_PID
 }
 
 # Set up trap to call cleanup on script exit
-trap cleanup EXIT
+trap cleanup EXIT INT TERM
 
 # Create a temporary directory
 export CDK_ITESTS_DIR=$(mktemp -d)
-export CDK_ITESTS_MINT_ADDR="127.0.0.1"
-export CDK_ITESTS_MINT_PORT=8086
 
 # Check if the temporary directory was created successfully
 if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
@@ -49,36 +48,74 @@ if [[ ! -d "$CDK_ITESTS_DIR" ]]; then
 fi
 
 echo "Temp directory created: $CDK_ITESTS_DIR"
-export CDK_MINTD_DATABASE="$1"
-
-cargo build -p cdk-integration-tests
 
+# Check if a database type was provided as first argument, default to sqlite
+export CDK_MINTD_DATABASE="${1:-sqlite}"
 
-export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT"
-export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR"
-export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR
-export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT
-export CDK_MINTD_LN_BACKEND="fakewallet"
-export CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS="sat,usd"
-export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
-export CDK_MINTD_FAKE_WALLET_FEE_PERCENT="0"
-export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1"
+cargo build -p cdk-integration-tests
 
+# Start the fake mint binary with the new Rust-based approach
+echo "Starting fake mint using Rust binary..."
 if [ "$2" = "external_signatory" ]; then
-    export CDK_MINTD_SIGNATORY_URL="https://127.0.0.1:15060"
-    export CDK_MINTD_SIGNATORY_CERTS="$CDK_ITESTS_DIR"
+    echo "Starting with external signatory support"
+
     bash -x `dirname $0`/../crates/cdk-signatory/generate_certs.sh $CDK_ITESTS_DIR
+    cargo build --bin signatory
     cargo run --bin signatory -- -w $CDK_ITESTS_DIR -u "sat" -u "usd"  &
     export CDK_SIGNATORY_PID=$!
     sleep 5
+    
+    cargo run --bin start_fake_mint -- --enable-logging --external-signatory "$CDK_MINTD_DATABASE" "$CDK_ITESTS_DIR" &
+else
+    cargo run --bin start_fake_mint -- --enable-logging "$CDK_MINTD_DATABASE" "$CDK_ITESTS_DIR" &
+fi
+export FAKE_MINT_PID=$!
+
+# Give the mint a moment to start
+sleep 3
+
+# Look for the .env file in the temp directory
+ENV_FILE_PATH="$CDK_ITESTS_DIR/.env"
+
+# Wait for the .env file to be created (with longer timeout)
+max_wait=200
+wait_count=0
+while [ $wait_count -lt $max_wait ]; do
+    if [ -f "$ENV_FILE_PATH" ]; then
+        echo ".env file found at: $ENV_FILE_PATH"
+        break
+    fi
+    echo "Waiting for .env file to be created... ($wait_count/$max_wait)"
+    wait_count=$((wait_count + 1))
+    sleep 1
+done
+
+# Check if we found the .env file
+if [ ! -f "$ENV_FILE_PATH" ]; then
+    echo "ERROR: Could not find .env file at $ENV_FILE_PATH after $max_wait seconds"
+    exit 1
 fi
 
-echo "Starting fake mintd"
-cargo run --bin cdk-mintd &
-export CDK_MINTD_PID=$!
+# Source the environment variables from the .env file
+echo "Sourcing environment variables from $ENV_FILE_PATH"
+source "$ENV_FILE_PATH"
+
+echo "Sourced environment variables:"
+echo "CDK_TEST_MINT_URL=$CDK_TEST_MINT_URL"
+echo "CDK_ITESTS_DIR=$CDK_ITESTS_DIR"
 
-URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT/v1/info"
-TIMEOUT=100
+# Validate that we sourced the variables
+if [ -z "$CDK_TEST_MINT_URL" ] || [ -z "$CDK_ITESTS_DIR" ]; then
+    echo "ERROR: Failed to source environment variables from the .env file"
+    exit 1
+fi
+
+# Export all variables so they're available to the tests
+export CDK_TEST_MINT_URL
+
+URL="$CDK_TEST_MINT_URL/v1/info"
+
+TIMEOUT=120
 START_TIME=$(date +%s)
 # Loop until the endpoint returns a 200 OK status or timeout is reached
 while true; do
@@ -107,10 +144,8 @@ while true; do
     fi
 done
 
-
-export CDK_TEST_MINT_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT"
-
 # Run first test
+echo "Running fake_wallet test"
 cargo test -p cdk-integration-tests --test fake_wallet
 status1=$?
 
@@ -121,6 +156,7 @@ if [ $status1 -ne 0 ]; then
 fi
 
 # Run second test only if the first one succeeded
+echo "Running happy_path_mint_wallet test"
 cargo test -p cdk-integration-tests --test happy_path_mint_wallet
 status2=$?
 

+ 1 - 1
misc/interactive_regtest_mprocs.sh

@@ -123,7 +123,7 @@ cargo build -p cdk-integration-tests --bin start_regtest
 cargo build --bin cdk-mintd
 
 echo "Starting regtest network (Bitcoin + Lightning nodes)..."
-cargo run --bin start_regtest &
+cargo run --bin start_regtest -- --enable-logging "$CDK_ITESTS_DIR" &
 export CDK_REGTEST_PID=$!
 
 # Create named pipe for progress tracking

+ 67 - 91
misc/itests.sh

@@ -4,25 +4,29 @@
 cleanup() {
     echo "Cleaning up..."
 
-    echo "Killing the cdk mintd"
-    kill -2 $CDK_MINTD_PID
-    wait $CDK_MINTD_PID
-
-    
-    echo "Killing the cdk lnd mintd"
-    kill -2 $CDK_MINTD_LND_PID
-    wait $CDK_MINTD_LND_PID
-
-    echo "Killing the cdk regtest"
-    kill -2 $CDK_REGTEST_PID
-    wait $CDK_REGTEST_PID
-
+    echo "Killing the cdk regtest and mints"
+    if [ ! -z "$CDK_REGTEST_PID" ]; then
+        # First try graceful shutdown with SIGTERM
+        kill -15 $CDK_REGTEST_PID 2>/dev/null
+        sleep 2
+        
+        # Check if process is still running, if so force kill with SIGKILL
+        if ps -p $CDK_REGTEST_PID > /dev/null 2>&1; then
+            echo "Process still running, force killing..."
+            kill -9 $CDK_REGTEST_PID 2>/dev/null
+        fi
+        
+        # Wait for process to terminate
+        wait $CDK_REGTEST_PID 2>/dev/null || true
+    fi
 
     echo "Mint binary terminated"
 
     # Remove the temporary directory
-    rm -rf "$CDK_ITESTS_DIR"
-    echo "Temp directory removed: $CDK_ITESTS_DIR"
+    if [ ! -z "$CDK_ITESTS_DIR" ] && [ -d "$CDK_ITESTS_DIR" ]; then
+        rm -rf "$CDK_ITESTS_DIR"
+        echo "Temp directory removed: $CDK_ITESTS_DIR"
+    fi
     
     # Unset all environment variables
     unset CDK_ITESTS_DIR
@@ -32,18 +36,6 @@ cleanup() {
     unset CDK_MINTD_DATABASE
     unset CDK_TEST_MINT_URL
     unset CDK_TEST_MINT_URL_2
-    unset CDK_MINTD_URL
-    unset CDK_MINTD_WORK_DIR
-    unset CDK_MINTD_LISTEN_HOST
-    unset CDK_MINTD_LISTEN_PORT
-    unset CDK_MINTD_LN_BACKEND
-    unset CDK_MINTD_MNEMONIC
-    unset CDK_MINTD_CLN_RPC_PATH
-    unset CDK_MINTD_LND_ADDRESS
-    unset CDK_MINTD_LND_CERT_FILE
-    unset CDK_MINTD_LND_MACAROON_FILE
-    unset CDK_MINTD_PID
-    unset CDK_MINTD_LND_PID
     unset CDK_REGTEST_PID
     unset RUST_BACKTRACE
     unset CDK_TEST_REGTEST
@@ -69,53 +61,59 @@ fi
 echo "Temp directory created: $CDK_ITESTS_DIR"
 export CDK_MINTD_DATABASE="$1"
 
-cargo build -p cdk-integration-tests 
-
-cargo run --bin start_regtest &
+cargo build -p cdk-integration-tests
+cargo build --bin start_regtest_mints 
 
+echo "Starting regtest and mints"
+# Run the binary in background
+cargo r --bin start_regtest_mints -- --enable-logging "$CDK_MINTD_DATABASE" "$CDK_ITESTS_DIR" "$CDK_ITESTS_MINT_ADDR" "$CDK_ITESTS_MINT_PORT_0" "$CDK_ITESTS_MINT_PORT_1" &
 export CDK_REGTEST_PID=$!
-mkfifo "$CDK_ITESTS_DIR/progress_pipe"
-rm -f "$CDK_ITESTS_DIR/signal_received"  # Ensure clean state
-# Start reading from pipe in background
-(while read line; do
-    case "$line" in
-        "checkpoint1")
-            echo "Reached first checkpoint"
-            touch "$CDK_ITESTS_DIR/signal_received"
-            exit 0
-            ;;
-    esac
-done < "$CDK_ITESTS_DIR/progress_pipe") &
-# Wait for up to 120 seconds
-for ((i=0; i<120; i++)); do
-    if [ -f "$CDK_ITESTS_DIR/signal_received" ]; then
-        echo "break signal received"
+
+# Give it a moment to start - reduced from 5 to 2 seconds since we have better waiting mechanisms now
+sleep 2
+
+# Look for the .env file in the current directory
+ENV_FILE_PATH="$CDK_ITESTS_DIR/.env"
+
+# Wait for the .env file to be created in the current directory
+max_wait=120
+wait_count=0
+while [ $wait_count -lt $max_wait ]; do
+    if [ -f "$ENV_FILE_PATH" ]; then
+        echo ".env file found at: $ENV_FILE_PATH"
         break
     fi
+    wait_count=$((wait_count + 1))
     sleep 1
 done
-echo "Regtest set up continuing"
 
-echo "Starting regtest mint"
-# cargo run --bin regtest_mint &
+# Check if we found the .env file
+if [ ! -f "$ENV_FILE_PATH" ]; then
+    echo "ERROR: Could not find .env file at $ENV_FILE_PATH"
+    exit 1
+fi
 
-export CDK_MINTD_CLN_RPC_PATH="$CDK_ITESTS_DIR/cln/one/regtest/lightning-rpc"
-export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0"
-export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR"
-export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR
-export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT_0
-export CDK_MINTD_LN_BACKEND="cln"
-export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
-export RUST_BACKTRACE=1
+# Source the environment variables from the .env file
+echo "Sourcing environment variables from $ENV_FILE_PATH"
+source "$ENV_FILE_PATH"
 
-echo "Starting cln mintd"
-cargo run --bin cdk-mintd &
-export CDK_MINTD_PID=$!
+echo "Sourced environment variables:"
+echo "CDK_TEST_MINT_URL=$CDK_TEST_MINT_URL"
+echo "CDK_TEST_MINT_URL_2=$CDK_TEST_MINT_URL_2"
+echo "CDK_ITESTS_DIR=$CDK_ITESTS_DIR"
 
+# Validate that we sourced the variables
+if [ -z "$CDK_TEST_MINT_URL" ] || [ -z "$CDK_TEST_MINT_URL_2" ] || [ -z "$CDK_ITESTS_DIR" ]; then
+    echo "ERROR: Failed to source environment variables from the .env file"
+    exit 1
+fi
+
+# Export all variables so they're available to the tests
+export CDK_TEST_MINT_URL
+export CDK_TEST_MINT_URL_2
 
-echo $CDK_ITESTS_DIR
+URL="$CDK_TEST_MINT_URL/v1/info"
 
-URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0/v1/info"
 
 TIMEOUT=100
 START_TIME=$(date +%s)
@@ -146,24 +144,8 @@ while true; do
     fi
 done
 
+URL="$CDK_TEST_MINT_URL_2/v1/info"
 
-export CDK_MINTD_LND_ADDRESS="https://localhost:10010"
-export CDK_MINTD_LND_CERT_FILE="$CDK_ITESTS_DIR/lnd/two/tls.cert"
-export CDK_MINTD_LND_MACAROON_FILE="$CDK_ITESTS_DIR/lnd/two/data/chain/bitcoin/regtest/admin.macaroon"
-
-export CDK_MINTD_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1"
-mkdir -p "$CDK_ITESTS_DIR/lnd_mint"
-export CDK_MINTD_WORK_DIR="$CDK_ITESTS_DIR/lnd_mint"
-export CDK_MINTD_LISTEN_HOST=$CDK_ITESTS_MINT_ADDR
-export CDK_MINTD_LISTEN_PORT=$CDK_ITESTS_MINT_PORT_1
-export CDK_MINTD_LN_BACKEND="lnd"
-export CDK_MINTD_MNEMONIC="cattle gold bind busy sound reduce tone addict baby spend february strategy"
-
-echo "Starting lnd mintd"
-cargo run --bin cdk-mintd &
-export CDK_MINTD_LND_PID=$!
-
-URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1/v1/info"
 
 TIMEOUT=100
 START_TIME=$(date +%s)
@@ -194,13 +176,6 @@ while true; do
     fi
 done
 
-
-
-export CDK_TEST_MINT_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0"
-export CDK_TEST_MINT_URL_2="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1"
-
-# Run tests and exit immediately on failure
-
 # Run cargo test
 echo "Running regtest test with CLN mint"
 cargo test -p cdk-integration-tests --test regtest
@@ -216,7 +191,7 @@ if [ $? -ne 0 ]; then
     exit 1
 fi
 
-# # Run cargo test with the http_subscription feature
+# Run cargo test with the http_subscription feature
 echo "Running regtest test with http_subscription feature"
 cargo test -p cdk-integration-tests --test regtest --features http_subscription
 if [ $? -ne 0 ]; then
@@ -233,12 +208,13 @@ fi
 
 # Switch Mints: Run tests with LND mint
 echo "Switching to LND mint for tests"
-export CDK_ITESTS_MINT_PORT_0=8087
-export CDK_ITESTS_MINT_PORT_1=8085
-export CDK_TEST_MINT_URL="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_0"
-export CDK_TEST_MINT_URL_2="http://$CDK_ITESTS_MINT_ADDR:$CDK_ITESTS_MINT_PORT_1"
 
 echo "Running regtest test with LND mint"
+CDK_TEST_MINT_URL_SWITCHED=$CDK_TEST_MINT_URL_2
+CDK_TEST_MINT_URL_2_SWITCHED=$CDK_TEST_MINT_URL
+export CDK_TEST_MINT_URL=$CDK_TEST_MINT_URL_SWITCHED
+export CDK_TEST_MINT_URL_2=$CDK_TEST_MINT_URL_2_SWITCHED
+
 cargo test -p cdk-integration-tests --test regtest
 if [ $? -ne 0 ]; then
     echo "regtest test with LND mint failed, exiting"

+ 1 - 1
misc/mintd_payment_processor.sh

@@ -80,7 +80,7 @@ cargo build -p cdk-integration-tests
 export CDK_TEST_REGTEST=0
 if [ "$LN_BACKEND" != "FAKEWALLET" ]; then
     export CDK_TEST_REGTEST=1
-    cargo run --bin start_regtest &
+    cargo run --bin start_regtest "$CDK_ITESTS_DIR" &
     CDK_REGTEST_PID=$!
     mkfifo "$CDK_ITESTS_DIR/progress_pipe"
     rm -f "$CDK_ITESTS_DIR/signal_received"  # Ensure clean state

+ 3 - 0
misc/nutshell_wallet_itest.sh

@@ -58,6 +58,9 @@ export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1"
 export CDK_MINTD_INPUT_FEE_PPK="100"
 
 
+export CDK_ITESTS_DIR="$CDK_ITESTS"
+
+
 echo "Starting fake mintd"
 cargo run --bin cdk-mintd &
 CDK_MINTD_PID=$!