Browse Source

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 11 tháng trước cách đây
mục cha
commit
3a3cd88ee9
33 tập tin đã thay đổi với 2469 bổ sung1210 xóa
  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=$!