lib.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. use std::collections::{HashMap, HashSet};
  2. use std::sync::Arc;
  3. use std::time::Duration;
  4. use anyhow::{bail, Result};
  5. use axum::Router;
  6. use bip39::Mnemonic;
  7. use cdk::amount::{Amount, SplitTarget};
  8. use cdk::cdk_database::mint_memory::MintMemoryDatabase;
  9. use cdk::cdk_lightning::MintLightning;
  10. use cdk::dhke::construct_proofs;
  11. use cdk::mint::FeeReserve;
  12. use cdk::nuts::{
  13. CurrencyUnit, Id, KeySet, MintBolt11Request, MintInfo, MintQuoteBolt11Request, MintQuoteState,
  14. Nuts, PaymentMethod, PreMintSecrets, Proofs, State,
  15. };
  16. use cdk::types::{LnKey, QuoteTTL};
  17. use cdk::wallet::client::{HttpClient, HttpClientMethods};
  18. use cdk::{Mint, Wallet};
  19. use cdk_fake_wallet::FakeWallet;
  20. use init_regtest::{get_mint_addr, get_mint_port, get_mint_url};
  21. use tokio::sync::Notify;
  22. use tokio::time::sleep;
  23. use tower_http::cors::CorsLayer;
  24. pub mod init_fake_wallet;
  25. pub mod init_regtest;
  26. pub fn create_backends_fake_wallet(
  27. ) -> HashMap<LnKey, Arc<dyn MintLightning<Err = cdk::cdk_lightning::Error> + Sync + Send>> {
  28. let fee_reserve = FeeReserve {
  29. min_fee_reserve: 1.into(),
  30. percent_fee_reserve: 1.0,
  31. };
  32. let mut ln_backends: HashMap<
  33. LnKey,
  34. Arc<dyn MintLightning<Err = cdk::cdk_lightning::Error> + Sync + Send>,
  35. > = HashMap::new();
  36. let ln_key = LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11);
  37. let wallet = Arc::new(FakeWallet::new(
  38. fee_reserve.clone(),
  39. HashMap::default(),
  40. HashSet::default(),
  41. 0,
  42. ));
  43. ln_backends.insert(ln_key, wallet.clone());
  44. ln_backends
  45. }
  46. pub async fn start_mint(
  47. ln_backends: HashMap<
  48. LnKey,
  49. Arc<dyn MintLightning<Err = cdk::cdk_lightning::Error> + Sync + Send>,
  50. >,
  51. supported_units: HashMap<CurrencyUnit, (u64, u8)>,
  52. ) -> Result<()> {
  53. let nuts = Nuts::new()
  54. .nut07(true)
  55. .nut08(true)
  56. .nut09(true)
  57. .nut10(true)
  58. .nut11(true)
  59. .nut12(true)
  60. .nut14(true);
  61. let mint_info = MintInfo::new().nuts(nuts);
  62. let mnemonic = Mnemonic::generate(12)?;
  63. let quote_ttl = QuoteTTL::new(10000, 10000);
  64. let mint = Mint::new(
  65. &get_mint_url(),
  66. &mnemonic.to_seed_normalized(""),
  67. mint_info,
  68. quote_ttl,
  69. Arc::new(MintMemoryDatabase::default()),
  70. ln_backends.clone(),
  71. supported_units,
  72. HashMap::new(),
  73. )
  74. .await?;
  75. let cache_time_to_live = 3600;
  76. let cache_time_to_idle = 3600;
  77. let mint_arc = Arc::new(mint);
  78. let v1_service = cdk_axum::create_mint_router(
  79. Arc::clone(&mint_arc),
  80. cache_time_to_live,
  81. cache_time_to_idle,
  82. None,
  83. )
  84. .await?;
  85. let mint_service = Router::new()
  86. .merge(v1_service)
  87. .layer(CorsLayer::permissive());
  88. let mint = Arc::clone(&mint_arc);
  89. let shutdown = Arc::new(Notify::new());
  90. tokio::spawn({
  91. let shutdown = Arc::clone(&shutdown);
  92. async move { mint.wait_for_paid_invoices(shutdown).await }
  93. });
  94. axum::Server::bind(
  95. &format!("{}:{}", get_mint_addr(), get_mint_port())
  96. .as_str()
  97. .parse()?,
  98. )
  99. .serve(mint_service.into_make_service())
  100. .await?;
  101. Ok(())
  102. }
  103. pub async fn wallet_mint(
  104. wallet: Arc<Wallet>,
  105. amount: Amount,
  106. split_target: SplitTarget,
  107. description: Option<String>,
  108. ) -> Result<()> {
  109. let quote = wallet.mint_quote(amount, description).await?;
  110. loop {
  111. let status = wallet.mint_quote_state(&quote.id).await?;
  112. if status.state == MintQuoteState::Paid {
  113. break;
  114. }
  115. println!("{:?}", status);
  116. sleep(Duration::from_secs(2)).await;
  117. }
  118. let receive_amount = wallet.mint(&quote.id, split_target, None).await?;
  119. println!("Minted: {}", receive_amount);
  120. Ok(())
  121. }
  122. pub async fn mint_proofs(
  123. mint_url: &str,
  124. amount: Amount,
  125. keyset_id: Id,
  126. mint_keys: &KeySet,
  127. description: Option<String>,
  128. ) -> anyhow::Result<Proofs> {
  129. println!("Minting for ecash");
  130. println!();
  131. let wallet_client = HttpClient::new();
  132. let request = MintQuoteBolt11Request {
  133. amount,
  134. unit: CurrencyUnit::Sat,
  135. description,
  136. };
  137. let mint_quote = wallet_client
  138. .post_mint_quote(mint_url.parse()?, request)
  139. .await?;
  140. println!("Please pay: {}", mint_quote.request);
  141. loop {
  142. let status = wallet_client
  143. .get_mint_quote_status(mint_url.parse()?, &mint_quote.quote)
  144. .await?;
  145. if status.state == MintQuoteState::Paid {
  146. break;
  147. }
  148. println!("{:?}", status.state);
  149. sleep(Duration::from_secs(2)).await;
  150. }
  151. let premint_secrets = PreMintSecrets::random(keyset_id, amount, &SplitTarget::default())?;
  152. let request = MintBolt11Request {
  153. quote: mint_quote.quote,
  154. outputs: premint_secrets.blinded_messages(),
  155. };
  156. let mint_response = wallet_client.post_mint(mint_url.parse()?, request).await?;
  157. let pre_swap_proofs = construct_proofs(
  158. mint_response.signatures,
  159. premint_secrets.rs(),
  160. premint_secrets.secrets(),
  161. &mint_keys.clone().keys,
  162. )?;
  163. Ok(pre_swap_proofs)
  164. }
  165. // Get all pending from wallet and attempt to swap
  166. // Will panic if there are no pending
  167. // Will return Ok if swap fails as expected
  168. pub async fn attempt_to_swap_pending(wallet: &Wallet) -> Result<()> {
  169. let pending = wallet
  170. .localstore
  171. .get_proofs(None, None, Some(vec![State::Pending]), None)
  172. .await?;
  173. assert!(!pending.is_empty());
  174. let swap = wallet
  175. .swap(
  176. None,
  177. SplitTarget::None,
  178. pending.into_iter().map(|p| p.proof).collect(),
  179. None,
  180. false,
  181. )
  182. .await;
  183. match swap {
  184. Ok(_swap) => {
  185. bail!("These proofs should be pending")
  186. }
  187. Err(err) => match err {
  188. cdk::error::Error::TokenPending => (),
  189. _ => {
  190. println!("{:?}", err);
  191. bail!("Wrong error")
  192. }
  193. },
  194. }
  195. Ok(())
  196. }