melt.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. use std::str::FromStr;
  2. use anyhow::bail;
  3. use cdk_common::nut00::ProofsMethods;
  4. use cdk_common::MeltOptions;
  5. use lightning_invoice::Bolt11Invoice;
  6. use tracing::instrument;
  7. use uuid::Uuid;
  8. use super::{
  9. CurrencyUnit, MeltBolt11Request, MeltQuote, MeltQuoteBolt11Request, MeltQuoteBolt11Response,
  10. Mint, PaymentMethod, PublicKey, State,
  11. };
  12. use crate::amount::to_unit;
  13. use crate::cdk_lightning::{MintLightning, PayInvoiceResponse};
  14. use crate::mint::verification::Verification;
  15. use crate::mint::SigFlag;
  16. use crate::nuts::nut11::{enforce_sig_flag, EnforceSigFlag};
  17. use crate::nuts::MeltQuoteState;
  18. use crate::types::LnKey;
  19. use crate::util::unix_time;
  20. use crate::{cdk_lightning, ensure_cdk, Amount, Error};
  21. impl Mint {
  22. #[instrument(skip_all)]
  23. async fn check_melt_request_acceptable(
  24. &self,
  25. amount: Amount,
  26. unit: CurrencyUnit,
  27. method: PaymentMethod,
  28. request: String,
  29. options: Option<MeltOptions>,
  30. ) -> Result<(), Error> {
  31. let mint_info = self.localstore.get_mint_info().await?;
  32. let nut05 = mint_info.nuts.nut05;
  33. let nut15 = mint_info.nuts.nut15;
  34. ensure_cdk!(!nut05.disabled, Error::MeltingDisabled);
  35. let settings = nut05
  36. .get_settings(&unit, &method)
  37. .ok_or(Error::UnsupportedUnit)?;
  38. let amount = match options {
  39. Some(MeltOptions::Mpp { mpp: _ }) => {
  40. // Verify there is no corresponding mint quote.
  41. // Otherwise a wallet is trying to pay someone internally, but
  42. // with a multi-part quote. And that's just not possible.
  43. if (self.localstore.get_mint_quote_by_request(&request).await?).is_some() {
  44. return Err(Error::InternalMultiPartMeltQuote);
  45. }
  46. // Verify MPP is enabled for unit and method
  47. if !nut15
  48. .methods
  49. .into_iter()
  50. .any(|m| m.method == method && m.unit == unit)
  51. {
  52. return Err(Error::MppUnitMethodNotSupported(unit, method));
  53. }
  54. // Assign `amount`
  55. // because should have already been converted to the partial amount
  56. amount
  57. }
  58. None => amount,
  59. };
  60. let is_above_max = matches!(settings.max_amount, Some(max) if amount > max);
  61. let is_below_min = matches!(settings.min_amount, Some(min) if amount < min);
  62. match is_above_max || is_below_min {
  63. true => {
  64. tracing::error!(
  65. "Melt amount out of range: {} is not within {} and {}",
  66. amount,
  67. settings.min_amount.unwrap_or_default(),
  68. settings.max_amount.unwrap_or_default(),
  69. );
  70. Err(Error::AmountOutofLimitRange(
  71. settings.min_amount.unwrap_or_default(),
  72. settings.max_amount.unwrap_or_default(),
  73. amount,
  74. ))
  75. }
  76. false => Ok(()),
  77. }
  78. }
  79. /// Get melt bolt11 quote
  80. #[instrument(skip_all)]
  81. pub async fn get_melt_bolt11_quote(
  82. &self,
  83. melt_request: &MeltQuoteBolt11Request,
  84. ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
  85. let MeltQuoteBolt11Request {
  86. request,
  87. unit,
  88. options,
  89. ..
  90. } = melt_request;
  91. let amount_msats = melt_request.amount_msat()?;
  92. let amount_quote_unit = to_unit(amount_msats, &CurrencyUnit::Msat, unit)?;
  93. self.check_melt_request_acceptable(
  94. amount_quote_unit,
  95. unit.clone(),
  96. PaymentMethod::Bolt11,
  97. request.to_string(),
  98. *options,
  99. )
  100. .await?;
  101. let ln = self
  102. .ln
  103. .get(&LnKey::new(unit.clone(), PaymentMethod::Bolt11))
  104. .ok_or_else(|| {
  105. tracing::info!("Could not get ln backend for {}, bolt11 ", unit);
  106. Error::UnsupportedUnit
  107. })?;
  108. let payment_quote = ln.get_payment_quote(melt_request).await.map_err(|err| {
  109. tracing::error!(
  110. "Could not get payment quote for mint quote, {} bolt11, {}",
  111. unit,
  112. err
  113. );
  114. Error::UnsupportedUnit
  115. })?;
  116. // We only want to set the msats_to_pay of the melt quote if the invoice is amountless
  117. // or we want to ignore the amount and do an mpp payment
  118. let msats_to_pay = options.map(|opt| opt.amount_msat());
  119. let melt_ttl = self.localstore.get_quote_ttl().await?.melt_ttl;
  120. let quote = MeltQuote::new(
  121. request.to_string(),
  122. unit.clone(),
  123. payment_quote.amount,
  124. payment_quote.fee,
  125. unix_time() + melt_ttl,
  126. payment_quote.request_lookup_id.clone(),
  127. msats_to_pay,
  128. );
  129. tracing::debug!(
  130. "New melt quote {} for {} {} with request id {}",
  131. quote.id,
  132. amount_quote_unit,
  133. unit,
  134. payment_quote.request_lookup_id
  135. );
  136. self.localstore.add_melt_quote(quote.clone()).await?;
  137. Ok(quote.into())
  138. }
  139. /// Check melt quote status
  140. #[instrument(skip(self))]
  141. pub async fn check_melt_quote(
  142. &self,
  143. quote_id: &Uuid,
  144. ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
  145. let quote = self
  146. .localstore
  147. .get_melt_quote(quote_id)
  148. .await?
  149. .ok_or(Error::UnknownQuote)?;
  150. let blind_signatures = self
  151. .localstore
  152. .get_blind_signatures_for_quote(quote_id)
  153. .await?;
  154. let change = (!blind_signatures.is_empty()).then_some(blind_signatures);
  155. Ok(MeltQuoteBolt11Response {
  156. quote: quote.id,
  157. paid: Some(quote.state == MeltQuoteState::Paid),
  158. state: quote.state,
  159. expiry: quote.expiry,
  160. amount: quote.amount,
  161. fee_reserve: quote.fee_reserve,
  162. payment_preimage: quote.payment_preimage,
  163. change,
  164. })
  165. }
  166. /// Update melt quote
  167. #[instrument(skip_all)]
  168. pub async fn update_melt_quote(&self, quote: MeltQuote) -> Result<(), Error> {
  169. self.localstore.add_melt_quote(quote).await?;
  170. Ok(())
  171. }
  172. /// Get melt quotes
  173. #[instrument(skip_all)]
  174. pub async fn melt_quotes(&self) -> Result<Vec<MeltQuote>, Error> {
  175. let quotes = self.localstore.get_melt_quotes().await?;
  176. Ok(quotes)
  177. }
  178. /// Remove melt quote
  179. #[instrument(skip(self))]
  180. pub async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
  181. self.localstore.remove_melt_quote(quote_id).await?;
  182. Ok(())
  183. }
  184. /// Check melt has expected fees
  185. #[instrument(skip_all)]
  186. pub async fn check_melt_expected_ln_fees(
  187. &self,
  188. melt_quote: &MeltQuote,
  189. melt_request: &MeltBolt11Request<Uuid>,
  190. ) -> Result<Option<Amount>, Error> {
  191. let invoice = Bolt11Invoice::from_str(&melt_quote.request)?;
  192. let quote_msats = to_unit(melt_quote.amount, &melt_quote.unit, &CurrencyUnit::Msat)
  193. .expect("Quote unit is checked above that it can convert to msat");
  194. let invoice_amount_msats: Amount = match invoice.amount_milli_satoshis() {
  195. Some(amt) => amt.into(),
  196. None => melt_quote
  197. .msat_to_pay
  198. .ok_or(Error::InvoiceAmountUndefined)?,
  199. };
  200. let partial_amount = match invoice_amount_msats > quote_msats {
  201. true => {
  202. let partial_msats = invoice_amount_msats - quote_msats;
  203. Some(
  204. to_unit(partial_msats, &CurrencyUnit::Msat, &melt_quote.unit)
  205. .map_err(|_| Error::UnsupportedUnit)?,
  206. )
  207. }
  208. false => None,
  209. };
  210. let amount_to_pay = match partial_amount {
  211. Some(amount_to_pay) => amount_to_pay,
  212. None => to_unit(invoice_amount_msats, &CurrencyUnit::Msat, &melt_quote.unit)
  213. .map_err(|_| Error::UnsupportedUnit)?,
  214. };
  215. let inputs_amount_quote_unit = melt_request.proofs_amount().map_err(|_| {
  216. tracing::error!("Proof inputs in melt quote overflowed");
  217. Error::AmountOverflow
  218. })?;
  219. if amount_to_pay + melt_quote.fee_reserve > inputs_amount_quote_unit {
  220. tracing::debug!(
  221. "Not enough inputs provided: {} msats needed {} msats",
  222. inputs_amount_quote_unit,
  223. amount_to_pay
  224. );
  225. return Err(Error::TransactionUnbalanced(
  226. inputs_amount_quote_unit.into(),
  227. amount_to_pay.into(),
  228. melt_quote.fee_reserve.into(),
  229. ));
  230. }
  231. Ok(partial_amount)
  232. }
  233. /// Verify melt request is valid
  234. #[instrument(skip_all)]
  235. pub async fn verify_melt_request(
  236. &self,
  237. melt_request: &MeltBolt11Request<Uuid>,
  238. ) -> Result<MeltQuote, Error> {
  239. let state = self
  240. .localstore
  241. .update_melt_quote_state(&melt_request.quote, MeltQuoteState::Pending)
  242. .await?;
  243. match state {
  244. MeltQuoteState::Unpaid | MeltQuoteState::Failed => Ok(()),
  245. MeltQuoteState::Pending => Err(Error::PendingQuote),
  246. MeltQuoteState::Paid => Err(Error::PaidQuote),
  247. MeltQuoteState::Unknown => Err(Error::UnknownPaymentState),
  248. }?;
  249. let quote = self
  250. .localstore
  251. .get_melt_quote(&melt_request.quote)
  252. .await?
  253. .ok_or(Error::UnknownQuote)?;
  254. let Verification {
  255. amount: input_amount,
  256. unit: input_unit,
  257. } = self.verify_inputs(&melt_request.inputs).await?;
  258. let input_ys = melt_request.inputs.ys()?;
  259. let fee = self.get_proofs_fee(&melt_request.inputs).await?;
  260. let required_total = quote.amount + quote.fee_reserve + fee;
  261. // Check that the inputs proofs are greater then total.
  262. // Transaction does not need to be balanced as wallet may not want change.
  263. if input_amount < required_total {
  264. tracing::info!(
  265. "Swap request unbalanced: {}, outputs {}, fee {}",
  266. input_amount,
  267. quote.amount,
  268. fee
  269. );
  270. return Err(Error::TransactionUnbalanced(
  271. input_amount.into(),
  272. quote.amount.into(),
  273. (fee + quote.fee_reserve).into(),
  274. ));
  275. }
  276. self.localstore
  277. .add_proofs(melt_request.inputs.clone(), None)
  278. .await?;
  279. self.check_ys_spendable(&input_ys, State::Pending).await?;
  280. let EnforceSigFlag { sig_flag, .. } = enforce_sig_flag(melt_request.inputs.clone());
  281. ensure_cdk!(sig_flag.ne(&SigFlag::SigAll), Error::SigAllUsedInMelt);
  282. if let Some(outputs) = &melt_request.outputs {
  283. let Verification {
  284. amount: _,
  285. unit: output_unit,
  286. } = self.verify_outputs(outputs).await?;
  287. ensure_cdk!(input_unit == output_unit, Error::UnsupportedUnit);
  288. }
  289. tracing::debug!("Verified melt quote: {}", melt_request.quote);
  290. Ok(quote)
  291. }
  292. /// Process unpaid melt request
  293. /// In the event that a melt request fails and the lighthing payment is not
  294. /// made The [`Proofs`] should be returned to an unspent state and the
  295. /// quote should be unpaid
  296. #[instrument(skip_all)]
  297. pub async fn process_unpaid_melt(
  298. &self,
  299. melt_request: &MeltBolt11Request<Uuid>,
  300. ) -> Result<(), Error> {
  301. let input_ys = melt_request.inputs.ys()?;
  302. self.localstore
  303. .remove_proofs(&input_ys, Some(melt_request.quote))
  304. .await?;
  305. self.localstore
  306. .update_melt_quote_state(&melt_request.quote, MeltQuoteState::Unpaid)
  307. .await?;
  308. if let Ok(Some(quote)) = self.localstore.get_melt_quote(&melt_request.quote).await {
  309. self.pubsub_manager
  310. .melt_quote_status(quote, None, None, MeltQuoteState::Unpaid);
  311. }
  312. for public_key in input_ys {
  313. self.pubsub_manager
  314. .proof_state((public_key, State::Unspent));
  315. }
  316. Ok(())
  317. }
  318. /// Melt Bolt11
  319. #[instrument(skip_all)]
  320. pub async fn melt_bolt11(
  321. &self,
  322. melt_request: &MeltBolt11Request<Uuid>,
  323. ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
  324. use std::sync::Arc;
  325. async fn check_payment_state(
  326. ln: Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>,
  327. melt_quote: &MeltQuote,
  328. ) -> anyhow::Result<PayInvoiceResponse> {
  329. match ln
  330. .check_outgoing_payment(&melt_quote.request_lookup_id)
  331. .await
  332. {
  333. Ok(response) => Ok(response),
  334. Err(check_err) => {
  335. // If we cannot check the status of the payment we keep the proofs stuck as pending.
  336. tracing::error!(
  337. "Could not check the status of payment for {},. Proofs stuck as pending",
  338. melt_quote.id
  339. );
  340. tracing::error!("Checking payment error: {}", check_err);
  341. bail!("Could not check payment status")
  342. }
  343. }
  344. }
  345. let quote = match self.verify_melt_request(melt_request).await {
  346. Ok(quote) => quote,
  347. Err(err) => {
  348. tracing::debug!("Error attempting to verify melt quote: {}", err);
  349. if let Err(err) = self.process_unpaid_melt(melt_request).await {
  350. tracing::error!(
  351. "Could not reset melt quote {} state: {}",
  352. melt_request.quote,
  353. err
  354. );
  355. }
  356. return Err(err);
  357. }
  358. };
  359. let settled_internally_amount =
  360. match self.handle_internal_melt_mint(&quote, melt_request).await {
  361. Ok(amount) => amount,
  362. Err(err) => {
  363. tracing::error!("Attempting to settle internally failed");
  364. if let Err(err) = self.process_unpaid_melt(melt_request).await {
  365. tracing::error!(
  366. "Could not reset melt quote {} state: {}",
  367. melt_request.quote,
  368. err
  369. );
  370. }
  371. return Err(err);
  372. }
  373. };
  374. let (preimage, amount_spent_quote_unit) = match settled_internally_amount {
  375. Some(amount_spent) => (None, amount_spent),
  376. None => {
  377. // If the quote unit is SAT or MSAT we can check that the expected fees are
  378. // provided. We also check if the quote is less then the invoice
  379. // amount in the case that it is a mmp However, if the quote is not
  380. // of a bitcoin unit we cannot do these checks as the mint
  381. // is unaware of a conversion rate. In this case it is assumed that the quote is
  382. // correct and the mint should pay the full invoice amount if inputs
  383. // > `then quote.amount` are included. This is checked in the
  384. // `verify_melt` method.
  385. let partial_amount = match quote.unit {
  386. CurrencyUnit::Sat | CurrencyUnit::Msat => {
  387. match self.check_melt_expected_ln_fees(&quote, melt_request).await {
  388. Ok(amount) => amount,
  389. Err(err) => {
  390. tracing::error!("Fee is not expected: {}", err);
  391. if let Err(err) = self.process_unpaid_melt(melt_request).await {
  392. tracing::error!("Could not reset melt quote state: {}", err);
  393. }
  394. return Err(Error::Internal);
  395. }
  396. }
  397. }
  398. _ => None,
  399. };
  400. tracing::debug!("partial_amount: {:?}", partial_amount);
  401. let ln = match self
  402. .ln
  403. .get(&LnKey::new(quote.unit.clone(), PaymentMethod::Bolt11))
  404. {
  405. Some(ln) => ln,
  406. None => {
  407. tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
  408. if let Err(err) = self.process_unpaid_melt(melt_request).await {
  409. tracing::error!("Could not reset melt quote state: {}", err);
  410. }
  411. return Err(Error::UnsupportedUnit);
  412. }
  413. };
  414. let pre = match ln
  415. .pay_invoice(quote.clone(), partial_amount, Some(quote.fee_reserve))
  416. .await
  417. {
  418. Ok(pay)
  419. if pay.status == MeltQuoteState::Unknown
  420. || pay.status == MeltQuoteState::Failed =>
  421. {
  422. let check_response = check_payment_state(Arc::clone(ln), &quote)
  423. .await
  424. .map_err(|_| Error::Internal)?;
  425. if check_response.status == MeltQuoteState::Paid {
  426. tracing::warn!("Pay invoice returned {} but check returned {}. Proofs stuck as pending", pay.status.to_string(), check_response.status.to_string());
  427. return Err(Error::Internal);
  428. }
  429. check_response
  430. }
  431. Ok(pay) => pay,
  432. Err(err) => {
  433. // If the error is that the invoice was already paid we do not want to hold
  434. // hold the proofs as pending to we reset them and return an error.
  435. if matches!(err, cdk_lightning::Error::InvoiceAlreadyPaid) {
  436. tracing::debug!("Invoice already paid, resetting melt quote");
  437. if let Err(err) = self.process_unpaid_melt(melt_request).await {
  438. tracing::error!("Could not reset melt quote state: {}", err);
  439. }
  440. return Err(Error::RequestAlreadyPaid);
  441. }
  442. tracing::error!("Error returned attempting to pay: {} {}", quote.id, err);
  443. let check_response = check_payment_state(Arc::clone(ln), &quote)
  444. .await
  445. .map_err(|_| Error::Internal)?;
  446. // If there error is something else we want to check the status of the payment ensure it is not pending or has been made.
  447. if check_response.status == MeltQuoteState::Paid {
  448. tracing::warn!("Pay invoice returned an error but check returned {}. Proofs stuck as pending", check_response.status.to_string());
  449. return Err(Error::Internal);
  450. }
  451. check_response
  452. }
  453. };
  454. match pre.status {
  455. MeltQuoteState::Paid => (),
  456. MeltQuoteState::Unpaid | MeltQuoteState::Unknown | MeltQuoteState::Failed => {
  457. tracing::info!(
  458. "Lightning payment for quote {} failed.",
  459. melt_request.quote
  460. );
  461. if let Err(err) = self.process_unpaid_melt(melt_request).await {
  462. tracing::error!("Could not reset melt quote state: {}", err);
  463. }
  464. return Err(Error::PaymentFailed);
  465. }
  466. MeltQuoteState::Pending => {
  467. tracing::warn!(
  468. "LN payment pending, proofs are stuck as pending for quote: {}",
  469. melt_request.quote
  470. );
  471. return Err(Error::PendingQuote);
  472. }
  473. }
  474. // Convert from unit of backend to quote unit
  475. // Note: this should never fail since these conversions happen earlier and would fail there.
  476. // Since it will not fail and even if it does the ln payment has already been paid, proofs should still be burned
  477. let amount_spent =
  478. to_unit(pre.total_spent, &pre.unit, &quote.unit).unwrap_or_default();
  479. let payment_lookup_id = pre.payment_lookup_id;
  480. if payment_lookup_id != quote.request_lookup_id {
  481. tracing::info!(
  482. "Payment lookup id changed post payment from {} to {}",
  483. quote.request_lookup_id,
  484. payment_lookup_id
  485. );
  486. let mut melt_quote = quote;
  487. melt_quote.request_lookup_id = payment_lookup_id;
  488. if let Err(err) = self.localstore.add_melt_quote(melt_quote).await {
  489. tracing::warn!("Could not update payment lookup id: {}", err);
  490. }
  491. }
  492. (pre.payment_preimage, amount_spent)
  493. }
  494. };
  495. // If we made it here the payment has been made.
  496. // We process the melt burning the inputs and returning change
  497. let res = self
  498. .process_melt_request(melt_request, preimage, amount_spent_quote_unit)
  499. .await
  500. .map_err(|err| {
  501. tracing::error!("Could not process melt request: {}", err);
  502. err
  503. })?;
  504. Ok(res)
  505. }
  506. /// Process melt request marking [`Proofs`] as spent
  507. /// The melt request must be verifyed using [`Self::verify_melt_request`]
  508. /// before calling [`Self::process_melt_request`]
  509. #[instrument(skip_all)]
  510. pub async fn process_melt_request(
  511. &self,
  512. melt_request: &MeltBolt11Request<Uuid>,
  513. payment_preimage: Option<String>,
  514. total_spent: Amount,
  515. ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
  516. tracing::debug!("Processing melt quote: {}", melt_request.quote);
  517. let quote = self
  518. .localstore
  519. .get_melt_quote(&melt_request.quote)
  520. .await?
  521. .ok_or(Error::UnknownQuote)?;
  522. let input_ys = melt_request.inputs.ys()?;
  523. self.localstore
  524. .update_proofs_states(&input_ys, State::Spent)
  525. .await?;
  526. self.localstore
  527. .update_melt_quote_state(&melt_request.quote, MeltQuoteState::Paid)
  528. .await?;
  529. self.pubsub_manager.melt_quote_status(
  530. &quote,
  531. payment_preimage.clone(),
  532. None,
  533. MeltQuoteState::Paid,
  534. );
  535. for public_key in input_ys {
  536. self.pubsub_manager.proof_state((public_key, State::Spent));
  537. }
  538. let mut change = None;
  539. // Check if there is change to return
  540. if melt_request.proofs_amount()? > total_spent {
  541. // Check if wallet provided change outputs
  542. if let Some(outputs) = melt_request.outputs.clone() {
  543. let blinded_messages: Vec<PublicKey> =
  544. outputs.iter().map(|b| b.blinded_secret).collect();
  545. if self
  546. .localstore
  547. .get_blind_signatures(&blinded_messages)
  548. .await?
  549. .iter()
  550. .flatten()
  551. .next()
  552. .is_some()
  553. {
  554. tracing::info!("Output has already been signed");
  555. return Err(Error::BlindedMessageAlreadySigned);
  556. }
  557. let change_target = melt_request.proofs_amount()? - total_spent;
  558. let mut amounts = change_target.split();
  559. let mut change_sigs = Vec::with_capacity(amounts.len());
  560. if outputs.len().lt(&amounts.len()) {
  561. tracing::debug!(
  562. "Providing change requires {} blinded messages, but only {} provided",
  563. amounts.len(),
  564. outputs.len()
  565. );
  566. // In the case that not enough outputs are provided to return all change
  567. // Reverse sort the amounts so that the most amount of change possible is
  568. // returned. The rest is burnt
  569. amounts.sort_by(|a, b| b.cmp(a));
  570. }
  571. let mut outputs = outputs;
  572. for (amount, blinded_message) in amounts.iter().zip(&mut outputs) {
  573. blinded_message.amount = *amount;
  574. let blinded_signature = self.blind_sign(blinded_message).await?;
  575. change_sigs.push(blinded_signature)
  576. }
  577. self.localstore
  578. .add_blind_signatures(
  579. &outputs[0..change_sigs.len()]
  580. .iter()
  581. .map(|o| o.blinded_secret)
  582. .collect::<Vec<PublicKey>>(),
  583. &change_sigs,
  584. Some(quote.id),
  585. )
  586. .await?;
  587. change = Some(change_sigs);
  588. }
  589. }
  590. Ok(MeltQuoteBolt11Response {
  591. amount: quote.amount,
  592. paid: Some(true),
  593. payment_preimage,
  594. change,
  595. quote: quote.id,
  596. fee_reserve: quote.fee_reserve,
  597. state: MeltQuoteState::Paid,
  598. expiry: quote.expiry,
  599. })
  600. }
  601. }