mint_nut04.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. use cdk_common::payment::Bolt11Settings;
  2. use tracing::instrument;
  3. use uuid::Uuid;
  4. use super::verification::Verification;
  5. use super::{
  6. nut04, CurrencyUnit, Mint, MintQuote, MintQuoteBolt11Request, MintQuoteBolt11Response,
  7. NotificationPayload, PaymentMethod, PublicKey,
  8. };
  9. use crate::nuts::MintQuoteState;
  10. use crate::types::PaymentProcessorKey;
  11. use crate::util::unix_time;
  12. use crate::{ensure_cdk, Amount, Error};
  13. impl Mint {
  14. /// Checks that minting is enabled, request is supported unit and within range
  15. async fn check_mint_request_acceptable(
  16. &self,
  17. amount: Amount,
  18. unit: &CurrencyUnit,
  19. ) -> Result<(), Error> {
  20. let mint_info = self.localstore.get_mint_info().await?;
  21. let nut04 = &mint_info.nuts.nut04;
  22. ensure_cdk!(!nut04.disabled, Error::MintingDisabled);
  23. let settings = nut04
  24. .get_settings(unit, &PaymentMethod::Bolt11)
  25. .ok_or(Error::UnsupportedUnit)?;
  26. let is_above_max = settings
  27. .max_amount
  28. .is_some_and(|max_amount| amount > max_amount);
  29. let is_below_min = settings
  30. .min_amount
  31. .is_some_and(|min_amount| amount < min_amount);
  32. let is_out_of_range = is_above_max || is_below_min;
  33. ensure_cdk!(
  34. !is_out_of_range,
  35. Error::AmountOutofLimitRange(
  36. settings.min_amount.unwrap_or_default(),
  37. settings.max_amount.unwrap_or_default(),
  38. amount,
  39. )
  40. );
  41. Ok(())
  42. }
  43. /// Create new mint bolt11 quote
  44. #[instrument(skip_all)]
  45. pub async fn get_mint_bolt11_quote(
  46. &self,
  47. mint_quote_request: MintQuoteBolt11Request,
  48. ) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
  49. let MintQuoteBolt11Request {
  50. amount,
  51. unit,
  52. description,
  53. pubkey,
  54. } = mint_quote_request;
  55. self.check_mint_request_acceptable(amount, &unit).await?;
  56. let ln = self
  57. .ln
  58. .get(&PaymentProcessorKey::new(
  59. unit.clone(),
  60. PaymentMethod::Bolt11,
  61. ))
  62. .ok_or_else(|| {
  63. tracing::info!("Bolt11 mint request for unsupported unit");
  64. Error::UnsupportedUnit
  65. })?;
  66. let mint_ttl = self.localstore.get_quote_ttl().await?.mint_ttl;
  67. let quote_expiry = unix_time() + mint_ttl;
  68. let settings = ln.get_settings().await?;
  69. let settings: Bolt11Settings = serde_json::from_value(settings)?;
  70. if description.is_some() && !settings.invoice_description {
  71. tracing::error!("Backend does not support invoice description");
  72. return Err(Error::InvoiceDescriptionUnsupported);
  73. }
  74. let create_invoice_response = ln
  75. .create_incoming_payment_request(
  76. amount,
  77. &unit,
  78. description.unwrap_or("".to_string()),
  79. Some(quote_expiry),
  80. )
  81. .await
  82. .map_err(|err| {
  83. tracing::error!("Could not create invoice: {}", err);
  84. Error::InvalidPaymentRequest
  85. })?;
  86. let quote = MintQuote::new(
  87. create_invoice_response.request.to_string(),
  88. unit.clone(),
  89. amount,
  90. create_invoice_response.expiry.unwrap_or(0),
  91. create_invoice_response.request_lookup_id.clone(),
  92. pubkey,
  93. );
  94. tracing::debug!(
  95. "New mint quote {} for {} {} with request id {}",
  96. quote.id,
  97. amount,
  98. unit,
  99. create_invoice_response.request_lookup_id,
  100. );
  101. self.localstore.add_mint_quote(quote.clone()).await?;
  102. let quote: MintQuoteBolt11Response<Uuid> = quote.into();
  103. self.pubsub_manager
  104. .broadcast(NotificationPayload::MintQuoteBolt11Response(quote.clone()));
  105. Ok(quote)
  106. }
  107. /// Check mint quote
  108. #[instrument(skip(self))]
  109. pub async fn check_mint_quote(
  110. &self,
  111. quote_id: &Uuid,
  112. ) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
  113. let quote = self
  114. .localstore
  115. .get_mint_quote(quote_id)
  116. .await?
  117. .ok_or(Error::UnknownQuote)?;
  118. // Since the pending state is not part of the NUT it should not be part of the
  119. // response. In practice the wallet should not be checking the state of
  120. // a quote while waiting for the mint response.
  121. let state = match quote.state {
  122. MintQuoteState::Pending => MintQuoteState::Paid,
  123. MintQuoteState::Unpaid => self.check_mint_quote_paid(quote_id).await?,
  124. s => s,
  125. };
  126. Ok(MintQuoteBolt11Response {
  127. quote: quote.id,
  128. request: quote.request,
  129. state,
  130. expiry: Some(quote.expiry),
  131. pubkey: quote.pubkey,
  132. amount: Some(quote.amount),
  133. unit: Some(quote.unit.clone()),
  134. })
  135. }
  136. /// Update mint quote
  137. #[instrument(skip_all)]
  138. pub async fn update_mint_quote(&self, quote: MintQuote) -> Result<(), Error> {
  139. self.localstore.add_mint_quote(quote).await?;
  140. Ok(())
  141. }
  142. /// Get mint quotes
  143. #[instrument(skip_all)]
  144. pub async fn mint_quotes(&self) -> Result<Vec<MintQuote>, Error> {
  145. let quotes = self.localstore.get_mint_quotes().await?;
  146. Ok(quotes)
  147. }
  148. /// Get pending mint quotes
  149. #[instrument(skip_all)]
  150. pub async fn get_pending_mint_quotes(&self) -> Result<Vec<MintQuote>, Error> {
  151. let mint_quotes = self
  152. .localstore
  153. .get_mint_quotes_with_state(MintQuoteState::Pending)
  154. .await?;
  155. Ok(mint_quotes)
  156. }
  157. /// Get pending mint quotes
  158. #[instrument(skip_all)]
  159. pub async fn get_unpaid_mint_quotes(&self) -> Result<Vec<MintQuote>, Error> {
  160. let mint_quotes = self
  161. .localstore
  162. .get_mint_quotes_with_state(MintQuoteState::Unpaid)
  163. .await?;
  164. Ok(mint_quotes)
  165. }
  166. /// Remove mint quote
  167. #[instrument(skip_all)]
  168. pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
  169. self.localstore.remove_mint_quote(quote_id).await?;
  170. Ok(())
  171. }
  172. /// Flag mint quote as paid
  173. #[instrument(skip_all)]
  174. pub async fn pay_mint_quote_for_request_id(
  175. &self,
  176. request_lookup_id: &str,
  177. ) -> Result<(), Error> {
  178. if let Ok(Some(mint_quote)) = self
  179. .localstore
  180. .get_mint_quote_by_request_lookup_id(request_lookup_id)
  181. .await
  182. {
  183. self.pay_mint_quote(&mint_quote).await?;
  184. }
  185. Ok(())
  186. }
  187. /// Mark mint quote as paid
  188. #[instrument(skip_all)]
  189. pub async fn pay_mint_quote(&self, mint_quote: &MintQuote) -> Result<(), Error> {
  190. tracing::debug!(
  191. "Received payment notification for mint quote {}",
  192. mint_quote.id
  193. );
  194. if mint_quote.state != MintQuoteState::Issued && mint_quote.state != MintQuoteState::Paid {
  195. let unix_time = unix_time();
  196. if mint_quote.expiry < unix_time {
  197. tracing::warn!(
  198. "Mint quote {} paid at {} expired at {}, leaving current state",
  199. mint_quote.id,
  200. mint_quote.expiry,
  201. unix_time,
  202. );
  203. return Err(Error::ExpiredQuote(mint_quote.expiry, unix_time));
  204. }
  205. self.localstore
  206. .update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
  207. .await?;
  208. } else {
  209. tracing::debug!(
  210. "{} Quote already {} continuing",
  211. mint_quote.id,
  212. mint_quote.state
  213. );
  214. }
  215. self.pubsub_manager
  216. .mint_quote_bolt11_status(mint_quote.clone(), MintQuoteState::Paid);
  217. Ok(())
  218. }
  219. /// Process mint request
  220. #[instrument(skip_all)]
  221. pub async fn process_mint_request(
  222. &self,
  223. mint_request: nut04::MintBolt11Request<Uuid>,
  224. ) -> Result<nut04::MintBolt11Response, Error> {
  225. let mint_quote = self
  226. .localstore
  227. .get_mint_quote(&mint_request.quote)
  228. .await?
  229. .ok_or(Error::UnknownQuote)?;
  230. let state = self
  231. .localstore
  232. .update_mint_quote_state(&mint_request.quote, MintQuoteState::Pending)
  233. .await?;
  234. let state = if state == MintQuoteState::Unpaid {
  235. self.check_mint_quote_paid(&mint_quote.id).await?
  236. } else {
  237. state
  238. };
  239. match state {
  240. MintQuoteState::Unpaid => {
  241. let _state = self
  242. .localstore
  243. .update_mint_quote_state(&mint_request.quote, MintQuoteState::Unpaid)
  244. .await?;
  245. return Err(Error::UnpaidQuote);
  246. }
  247. MintQuoteState::Pending => {
  248. return Err(Error::PendingQuote);
  249. }
  250. MintQuoteState::Issued => {
  251. let _state = self
  252. .localstore
  253. .update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
  254. .await?;
  255. return Err(Error::IssuedQuote);
  256. }
  257. MintQuoteState::Paid => (),
  258. }
  259. // If the there is a public key provoided in mint quote request
  260. // verify the signature is provided for the mint request
  261. if let Some(pubkey) = mint_quote.pubkey {
  262. mint_request.verify_signature(pubkey)?;
  263. }
  264. let Verification { amount, unit } = match self.verify_outputs(&mint_request.outputs).await {
  265. Ok(verification) => verification,
  266. Err(err) => {
  267. tracing::debug!("Could not verify mint outputs");
  268. self.localstore
  269. .update_mint_quote_state(&mint_request.quote, MintQuoteState::Paid)
  270. .await?;
  271. return Err(err);
  272. }
  273. };
  274. // We check the the total value of blinded messages == mint quote
  275. if amount != mint_quote.amount {
  276. return Err(Error::TransactionUnbalanced(
  277. mint_quote.amount.into(),
  278. mint_request.total_amount()?.into(),
  279. 0,
  280. ));
  281. }
  282. ensure_cdk!(unit == mint_quote.unit, Error::UnsupportedUnit);
  283. let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());
  284. for blinded_message in mint_request.outputs.iter() {
  285. let blind_signature = self.blind_sign(blinded_message).await?;
  286. blind_signatures.push(blind_signature);
  287. }
  288. self.localstore
  289. .add_blind_signatures(
  290. &mint_request
  291. .outputs
  292. .iter()
  293. .map(|p| p.blinded_secret)
  294. .collect::<Vec<PublicKey>>(),
  295. &blind_signatures,
  296. Some(mint_request.quote),
  297. )
  298. .await?;
  299. self.localstore
  300. .update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
  301. .await?;
  302. self.pubsub_manager
  303. .mint_quote_bolt11_status(mint_quote, MintQuoteState::Issued);
  304. Ok(nut04::MintBolt11Response {
  305. signatures: blind_signatures,
  306. })
  307. }
  308. }