Cesar Rodas 2 달 전
부모
커밋
6d6bde31be
3개의 변경된 파일152개의 추가작업 그리고 2개의 파일을 삭제
  1. 0 1
      crates/cdk-cli/Cargo.toml
  2. 8 1
      crates/cdk-signatory/Cargo.toml
  3. 144 0
      crates/cdk-signatory/src/main.rs

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

@@ -36,4 +36,3 @@ nostr-sdk = { version = "0.35.0", default-features = false, features = [
 reqwest.workspace = true
 url.workspace = true
 serde_with.workspace = true
-

+ 8 - 1
crates/cdk-signatory/Cargo.toml

@@ -16,9 +16,16 @@ cdk-common = { workspace = true, default-features=false, features = [
     "mint", "auth",
 ] }
 tracing.workspace = true
-tokio.workspace = true
+tokio = { workspace = true, features = ["full"] }
 tonic = { workspace = true, optional = true }
 prost = { workspace = true, optional = true }
 
+# main.rs dependencies
+cdk-sqlite = { workspace = true }
+cdk-redb = { workspace = true }
+clap = { workspace = true }
+bip39.workspace = true
+home.workspace = true
+
 [build-dependencies]
 tonic-build = { workspace = true, features = ["prost"], optional = true }

+ 144 - 0
crates/cdk-signatory/src/main.rs

@@ -0,0 +1,144 @@
+use std::fs;
+use std::path::PathBuf;
+use std::sync::Arc;
+
+use bip39::rand::{thread_rng, Rng};
+use bip39::Mnemonic;
+use cdk_sqlite::WalletSqliteDatabase;
+use clap::Parser;
+use tracing::Level;
+
+const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
+
+/// Simple CLI application to interact with cashu
+#[derive(Parser)]
+#[command(name = "cashu-signatory")]
+#[command(author = "thesimplekid <tsk@thesimplekid.com>")]
+#[command(version = "0.1.0")]
+#[command(author, version, about, long_about = None)]
+struct Cli {
+    /// Database engine to use (sqlite/redb)
+    #[arg(short, long, default_value = "sqlite")]
+    engine: String,
+    /// Database password for sqlcipher
+    #[arg(long)]
+    password: Option<String>,
+    /// Path to working dir
+    #[arg(short, long)]
+    work_dir: Option<PathBuf>,
+    /// Logging level
+    #[arg(short, long, default_value = "error")]
+    log_level: Level,
+    /// NWS Proxy
+    #[arg(short, long)]
+    proxy: Option<Url>,
+}
+
+#[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();
+
+    let work_dir = match &args.work_dir {
+        Some(work_dir) => work_dir.clone(),
+        None => {
+            let home_dir = home::home_dir().unwrap();
+            home_dir.join(DEFAULT_WORK_DIR)
+        }
+    };
+
+    fs::create_dir_all(&work_dir)?;
+
+    let localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync> =
+        match args.engine.as_str() {
+            "sqlite" => {
+                let sql_path = work_dir.join("cdk-cli.sqlite");
+                #[cfg(not(feature = "sqlcipher"))]
+                let sql = WalletSqliteDatabase::new(&sql_path).await?;
+                #[cfg(feature = "sqlcipher")]
+                let sql = {
+                    match args.password {
+                        Some(pass) => WalletSqliteDatabase::new(&sql_path, pass).await?,
+                        None => bail!("Missing database password"),
+                    }
+                };
+
+                Arc::new(sql)
+            }
+            "redb" => {
+                #[cfg(feature = "redb")]
+                {
+                    let redb_path = work_dir.join("cdk-cli.redb");
+                    Arc::new(WalletRedbDatabase::new(&redb_path)?)
+                }
+                #[cfg(not(feature = "redb"))]
+                {
+                    bail!("redb feature not enabled");
+                }
+            }
+            _ => bail!("Unknown DB engine"),
+        };
+
+    let seed_path = work_dir.join("seed");
+
+    let mnemonic = match fs::metadata(seed_path.clone()) {
+        Ok(_) => {
+            let contents = fs::read_to_string(seed_path.clone())?;
+            Mnemonic::from_str(&contents)?
+        }
+        Err(_e) => {
+            let mut rng = thread_rng();
+            let random_bytes: [u8; 32] = rng.gen();
+
+            let mnemonic = Mnemonic::from_entropy(&random_bytes)?;
+            tracing::info!("Creating new seed");
+
+            fs::write(seed_path, mnemonic.to_string())?;
+
+            mnemonic
+        }
+    };
+    let seed = mnemonic.to_seed_normalized("");
+
+    let mut wallets: Vec<Wallet> = Vec::new();
+
+    let mints = localstore.get_mints().await?;
+
+    for (mint_url, _) in mints {
+        let mut builder = WalletBuilder::new()
+            .mint_url(mint_url.clone())
+            .unit(cdk::nuts::CurrencyUnit::Sat)
+            .localstore(localstore.clone())
+            .seed(&mnemonic.to_seed_normalized(""));
+
+        if let Some(proxy_url) = args.proxy.as_ref() {
+            let http_client = HttpClient::with_proxy(mint_url, proxy_url.clone(), None, true)?;
+            builder = builder.client(http_client);
+        }
+
+        let wallet = builder.build()?;
+
+        let wallet_clone = wallet.clone();
+
+        tokio::spawn(async move {
+            if let Err(err) = wallet_clone.get_mint_info().await {
+                tracing::error!(
+                    "Could not get mint quote for {}, {}",
+                    wallet_clone.mint_url,
+                    err
+                );
+            }
+        });
+
+        wallets.push(wallet);
+    }
+
+    let multi_mint_wallet = MultiMintWallet::new(localstore, Arc::new(seed), wallets);
+}