| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 | 
							- use std::collections::{HashMap, HashSet};
 
- use std::fmt::{Debug, Formatter};
 
- use std::path::PathBuf;
 
- use std::str::FromStr;
 
- use std::sync::Arc;
 
- use std::{env, fs};
 
- use anyhow::{anyhow, bail, Result};
 
- use async_trait::async_trait;
 
- use bip39::Mnemonic;
 
- use cashu::quote_id::QuoteId;
 
- use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
 
- use cdk::amount::SplitTarget;
 
- use cdk::cdk_database::{self, MintDatabase, WalletDatabase};
 
- use cdk::mint::{MintBuilder, MintMeltLimits};
 
- use cdk::nuts::nut00::ProofsMethods;
 
- use cdk::nuts::{
 
-     CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse,
 
-     MeltQuoteBolt11Request, MeltQuoteBolt11Response, MeltRequest, MintInfo, MintQuoteBolt11Request,
 
-     MintQuoteBolt11Response, MintRequest, MintResponse, PaymentMethod, RestoreRequest,
 
-     RestoreResponse, SwapRequest, SwapResponse,
 
- };
 
- use cdk::types::{FeeReserve, QuoteTTL};
 
- use cdk::util::unix_time;
 
- use cdk::wallet::{AuthWallet, MintConnector, Wallet, WalletBuilder};
 
- use cdk::{Amount, Error, Mint, StreamExt};
 
- use cdk_fake_wallet::FakeWallet;
 
- use tokio::sync::RwLock;
 
- use tracing_subscriber::EnvFilter;
 
- use uuid::Uuid;
 
- pub struct DirectMintConnection {
 
-     pub mint: Mint,
 
-     auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
 
- }
 
- impl DirectMintConnection {
 
-     pub fn new(mint: Mint) -> Self {
 
-         Self {
 
-             mint,
 
-             auth_wallet: Arc::new(RwLock::new(None)),
 
-         }
 
-     }
 
- }
 
- impl Debug for DirectMintConnection {
 
-     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 
-         write!(f, "DirectMintConnection",)
 
-     }
 
- }
 
- /// Implements the generic [MintConnector] (i.e. use the interface that expects to communicate
 
- /// to a generic mint, where we don't know that quote ID's are [Uuid]s) for [DirectMintConnection],
 
- /// where we know we're dealing with a mint that uses [Uuid]s for quotes.
 
- /// Convert the requests and responses between the [String] and [Uuid] variants as necessary.
 
- #[async_trait]
 
- impl MintConnector for DirectMintConnection {
 
-     async fn resolve_dns_txt(&self, _domain: &str) -> Result<Vec<String>, Error> {
 
-         panic!("Not implemented");
 
-     }
 
-     async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
 
-         Ok(self.mint.pubkeys().keysets)
 
-     }
 
-     async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
 
-         self.mint.keyset(&keyset_id).ok_or(Error::UnknownKeySet)
 
-     }
 
-     async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
 
-         Ok(self.mint.keysets())
 
-     }
 
-     async fn post_mint_quote(
 
-         &self,
 
-         request: MintQuoteBolt11Request,
 
-     ) -> Result<MintQuoteBolt11Response<String>, Error> {
 
-         self.mint
 
-             .get_mint_quote(request.into())
 
-             .await
 
-             .map(Into::into)
 
-     }
 
-     async fn get_mint_quote_status(
 
-         &self,
 
-         quote_id: &str,
 
-     ) -> Result<MintQuoteBolt11Response<String>, Error> {
 
-         self.mint
 
-             .check_mint_quote(&QuoteId::from_str(quote_id)?)
 
-             .await
 
-             .map(Into::into)
 
-     }
 
-     async fn post_mint(&self, request: MintRequest<String>) -> Result<MintResponse, Error> {
 
-         let request_id: MintRequest<QuoteId> = request.try_into().unwrap();
 
-         self.mint.process_mint_request(request_id).await
 
-     }
 
-     async fn post_melt_quote(
 
-         &self,
 
-         request: MeltQuoteBolt11Request,
 
-     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
 
-         self.mint
 
-             .get_melt_quote(request.into())
 
-             .await
 
-             .map(Into::into)
 
-     }
 
-     async fn get_melt_quote_status(
 
-         &self,
 
-         quote_id: &str,
 
-     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
 
-         self.mint
 
-             .check_melt_quote(&QuoteId::from_str(quote_id)?)
 
-             .await
 
-             .map(Into::into)
 
-     }
 
-     async fn post_melt(
 
-         &self,
 
-         request: MeltRequest<String>,
 
-     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
 
-         let request_uuid = request.try_into().unwrap();
 
-         self.mint.melt(&request_uuid).await.map(Into::into)
 
-     }
 
-     async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
 
-         self.mint.process_swap_request(swap_request).await
 
-     }
 
-     async fn get_mint_info(&self) -> Result<MintInfo, Error> {
 
-         Ok(self.mint.mint_info().await?.clone().time(unix_time()))
 
-     }
 
-     async fn post_check_state(
 
-         &self,
 
-         request: CheckStateRequest,
 
-     ) -> Result<CheckStateResponse, Error> {
 
-         self.mint.check_state(&request).await
 
-     }
 
-     async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
 
-         self.mint.restore(request).await
 
-     }
 
-     /// Get the auth wallet for the client
 
-     async fn get_auth_wallet(&self) -> Option<AuthWallet> {
 
-         self.auth_wallet.read().await.clone()
 
-     }
 
-     /// Set auth wallet on client
 
-     async fn set_auth_wallet(&self, wallet: Option<AuthWallet>) {
 
-         let mut auth_wallet = self.auth_wallet.write().await;
 
-         *auth_wallet = wallet;
 
-     }
 
-     async fn post_mint_bolt12_quote(
 
-         &self,
 
-         request: MintQuoteBolt12Request,
 
-     ) -> Result<MintQuoteBolt12Response<String>, Error> {
 
-         let res: MintQuoteBolt12Response<QuoteId> =
 
-             self.mint.get_mint_quote(request.into()).await?.try_into()?;
 
-         Ok(res.into())
 
-     }
 
-     async fn get_mint_quote_bolt12_status(
 
-         &self,
 
-         quote_id: &str,
 
-     ) -> Result<MintQuoteBolt12Response<String>, Error> {
 
-         let quote: MintQuoteBolt12Response<QuoteId> = self
 
-             .mint
 
-             .check_mint_quote(&QuoteId::from_str(quote_id)?)
 
-             .await?
 
-             .try_into()?;
 
-         Ok(quote.into())
 
-     }
 
-     /// Melt Quote [NUT-23]
 
-     async fn post_melt_bolt12_quote(
 
-         &self,
 
-         request: MeltQuoteBolt12Request,
 
-     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
 
-         self.mint
 
-             .get_melt_quote(request.into())
 
-             .await
 
-             .map(Into::into)
 
-     }
 
-     /// Melt Quote Status [NUT-23]
 
-     async fn get_melt_bolt12_quote_status(
 
-         &self,
 
-         quote_id: &str,
 
-     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
 
-         self.mint
 
-             .check_melt_quote(&QuoteId::from_str(quote_id)?)
 
-             .await
 
-             .map(Into::into)
 
-     }
 
-     /// Melt [NUT-23]
 
-     async fn post_melt_bolt12(
 
-         &self,
 
-         _request: MeltRequest<String>,
 
-     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
 
-         // Implementation to be added later
 
-         Err(Error::UnsupportedPaymentMethod)
 
-     }
 
- }
 
- pub fn setup_tracing() {
 
-     let default_filter = "debug";
 
-     let h2_filter = "h2=warn";
 
-     let hyper_filter = "hyper=warn";
 
-     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
 
-     let _ = tracing_subscriber::fmt()
 
-         .with_env_filter(env_filter)
 
-         .try_init();
 
- }
 
- pub async fn create_and_start_test_mint() -> Result<Mint> {
 
-     // Read environment variable to determine database type
 
-     let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set");
 
-     let localstore = match db_type.to_lowercase().as_str() {
 
-         "memory" => Arc::new(cdk_sqlite::mint::memory::empty().await?),
 
-         _ => {
 
-             // Create a temporary directory for SQLite database
 
-             let temp_dir = create_temp_dir("cdk-test-sqlite-mint")?;
 
-             let path = temp_dir.join("mint.db").to_str().unwrap().to_string();
 
-             Arc::new(
 
-                 cdk_sqlite::MintSqliteDatabase::new(path.as_str())
 
-                     .await
 
-                     .expect("Could not create sqlite db"),
 
-             )
 
-         }
 
-     };
 
-     let mut mint_builder = MintBuilder::new(localstore.clone());
 
-     let fee_reserve = FeeReserve {
 
-         min_fee_reserve: 1.into(),
 
-         percent_fee_reserve: 1.0,
 
-     };
 
-     let ln_fake_backend = FakeWallet::new(
 
-         fee_reserve.clone(),
 
-         HashMap::default(),
 
-         HashSet::default(),
 
-         2,
 
-         CurrencyUnit::Sat,
 
-     );
 
-     mint_builder
 
-         .add_payment_processor(
 
-             CurrencyUnit::Sat,
 
-             PaymentMethod::Bolt11,
 
-             MintMeltLimits::new(1, 10_000),
 
-             Arc::new(ln_fake_backend),
 
-         )
 
-         .await?;
 
-     let mnemonic = Mnemonic::generate(12)?;
 
-     mint_builder = mint_builder
 
-         .with_name("pure test mint".to_string())
 
-         .with_description("pure test mint".to_string())
 
-         .with_urls(vec!["https://aaa".to_string()]);
 
-     let tx_localstore = localstore.clone();
 
-     let mut tx = tx_localstore.begin_transaction().await?;
 
-     let quote_ttl = QuoteTTL::new(10000, 10000);
 
-     tx.set_quote_ttl(quote_ttl).await?;
 
-     tx.commit().await?;
 
-     let mint = mint_builder
 
-         .build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
 
-         .await?;
 
-     mint.start().await?;
 
-     Ok(mint)
 
- }
 
- pub async fn create_test_wallet_for_mint(mint: Mint) -> Result<Wallet> {
 
-     let connector = DirectMintConnection::new(mint.clone());
 
-     let mint_info = mint.mint_info().await?;
 
-     let mint_url = mint_info
 
-         .urls
 
-         .as_ref()
 
-         .ok_or(anyhow!("Test mint URLs list is unset"))?
 
-         .first()
 
-         .ok_or(anyhow!("Test mint has empty URLs list"))?;
 
-     let seed = Mnemonic::generate(12)?.to_seed_normalized("");
 
-     let unit = CurrencyUnit::Sat;
 
-     // Read environment variable to determine database type
 
-     let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set");
 
-     let localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync> =
 
-         match db_type.to_lowercase().as_str() {
 
-             "sqlite" => {
 
-                 // Create a temporary directory for SQLite database
 
-                 let temp_dir = create_temp_dir("cdk-test-sqlite-wallet")?;
 
-                 let path = temp_dir.join("wallet.db").to_str().unwrap().to_string();
 
-                 let database = cdk_sqlite::WalletSqliteDatabase::new(path.as_str())
 
-                     .await
 
-                     .expect("Could not create sqlite db");
 
-                 Arc::new(database)
 
-             }
 
-             "redb" => {
 
-                 // Create a temporary directory for ReDB database
 
-                 let temp_dir = create_temp_dir("cdk-test-redb-wallet")?;
 
-                 let path = temp_dir.join("wallet.redb");
 
-                 let database = cdk_redb::WalletRedbDatabase::new(&path)
 
-                     .expect("Could not create redb mint database");
 
-                 Arc::new(database)
 
-             }
 
-             "memory" => {
 
-                 let database = cdk_sqlite::wallet::memory::empty().await?;
 
-                 Arc::new(database)
 
-             }
 
-             _ => {
 
-                 bail!("Db type not set")
 
-             }
 
-         };
 
-     let wallet = WalletBuilder::new()
 
-         .mint_url(mint_url.parse().unwrap())
 
-         .unit(unit)
 
-         .localstore(localstore)
 
-         .seed(seed)
 
-         .client(connector)
 
-         .build()?;
 
-     Ok(wallet)
 
- }
 
- /// Creates a mint quote for the given amount and checks its state in a loop. Returns when
 
- /// amount is minted.
 
- /// Creates a temporary directory with a unique name based on the prefix
 
- fn create_temp_dir(prefix: &str) -> Result<PathBuf> {
 
-     let temp_dir = env::temp_dir();
 
-     let unique_dir = temp_dir.join(format!("{}-{}", prefix, Uuid::new_v4()));
 
-     fs::create_dir_all(&unique_dir)?;
 
-     Ok(unique_dir)
 
- }
 
- pub async fn fund_wallet(
 
-     wallet: Wallet,
 
-     amount: u64,
 
-     split_target: Option<SplitTarget>,
 
- ) -> Result<Amount> {
 
-     let desired_amount = Amount::from(amount);
 
-     let quote = wallet.mint_quote(desired_amount, None).await?;
 
-     Ok(wallet
 
-         .proof_stream(quote, split_target.unwrap_or_default(), None)
 
-         .next()
 
-         .await
 
-         .expect("proofs")?
 
-         .total_amount()?)
 
- }
 
 
  |