lib.rs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. //! CDK lightning backend for CLN
  2. #![doc = include_str!("../README.md")]
  3. #![warn(missing_docs)]
  4. #![warn(rustdoc::bare_urls)]
  5. use std::cmp::max;
  6. use std::path::PathBuf;
  7. use std::pin::Pin;
  8. use std::str::FromStr;
  9. use std::sync::atomic::{AtomicBool, Ordering};
  10. use std::sync::Arc;
  11. use std::time::Duration;
  12. use async_trait::async_trait;
  13. use bitcoin::hashes::sha256::Hash;
  14. use cdk_common::amount::{to_unit, Amount};
  15. use cdk_common::common::FeeReserve;
  16. use cdk_common::nuts::{CurrencyUnit, MeltOptions, MeltQuoteState};
  17. use cdk_common::payment::{
  18. self, Bolt11IncomingPaymentOptions, Bolt11Settings, Bolt12IncomingPaymentOptions,
  19. CreateIncomingPaymentResponse, IncomingPaymentOptions, MakePaymentResponse, MintPayment,
  20. OutgoingPaymentOptions, PaymentIdentifier, PaymentQuoteResponse, WaitPaymentResponse,
  21. };
  22. use cdk_common::util::{hex, unix_time};
  23. use cdk_common::Bolt11Invoice;
  24. use cln_rpc::model::requests::{
  25. DecodeRequest, FetchinvoiceRequest, InvoiceRequest, ListinvoicesRequest, ListpaysRequest,
  26. OfferRequest, PayRequest, WaitanyinvoiceRequest,
  27. };
  28. use cln_rpc::model::responses::{
  29. DecodeResponse, ListinvoicesInvoices, ListinvoicesInvoicesStatus, ListpaysPaysStatus,
  30. PayStatus, WaitanyinvoiceResponse, WaitanyinvoiceStatus,
  31. };
  32. use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny, Sha256};
  33. use cln_rpc::ClnRpc;
  34. use error::Error;
  35. use futures::{Stream, StreamExt};
  36. use serde_json::Value;
  37. use tokio_util::sync::CancellationToken;
  38. use tracing::instrument;
  39. use uuid::Uuid;
  40. pub mod error;
  41. /// CLN mint backend
  42. #[derive(Clone)]
  43. pub struct Cln {
  44. rpc_socket: PathBuf,
  45. fee_reserve: FeeReserve,
  46. wait_invoice_cancel_token: CancellationToken,
  47. wait_invoice_is_active: Arc<AtomicBool>,
  48. }
  49. impl Cln {
  50. /// Create new [`Cln`]
  51. pub async fn new(rpc_socket: PathBuf, fee_reserve: FeeReserve) -> Result<Self, Error> {
  52. Ok(Self {
  53. rpc_socket,
  54. fee_reserve,
  55. wait_invoice_cancel_token: CancellationToken::new(),
  56. wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
  57. })
  58. }
  59. }
  60. #[async_trait]
  61. impl MintPayment for Cln {
  62. type Err = payment::Error;
  63. async fn get_settings(&self) -> Result<Value, Self::Err> {
  64. Ok(serde_json::to_value(Bolt11Settings {
  65. mpp: true,
  66. unit: CurrencyUnit::Msat,
  67. invoice_description: true,
  68. amountless: true,
  69. bolt12: true,
  70. })?)
  71. }
  72. /// Is wait invoice active
  73. fn is_wait_invoice_active(&self) -> bool {
  74. self.wait_invoice_is_active.load(Ordering::SeqCst)
  75. }
  76. /// Cancel wait invoice
  77. fn cancel_wait_invoice(&self) {
  78. self.wait_invoice_cancel_token.cancel()
  79. }
  80. #[instrument(skip_all)]
  81. async fn wait_any_incoming_payment(
  82. &self,
  83. ) -> Result<Pin<Box<dyn Stream<Item = WaitPaymentResponse> + Send>>, Self::Err> {
  84. tracing::info!(
  85. "CLN: Starting wait_any_incoming_payment with socket: {:?}",
  86. self.rpc_socket
  87. );
  88. let last_pay_index = self.get_last_pay_index().await?.inspect(|&idx| {
  89. tracing::info!("CLN: Found last payment index: {}", idx);
  90. });
  91. tracing::debug!("CLN: Connecting to CLN node...");
  92. let cln_client = match cln_rpc::ClnRpc::new(&self.rpc_socket).await {
  93. Ok(client) => {
  94. tracing::debug!("CLN: Successfully connected to CLN node");
  95. client
  96. }
  97. Err(err) => {
  98. tracing::error!("CLN: Failed to connect to CLN node: {}", err);
  99. return Err(Error::from(err).into());
  100. }
  101. };
  102. tracing::debug!("CLN: Creating stream processing pipeline");
  103. let stream = futures::stream::unfold(
  104. (
  105. cln_client,
  106. last_pay_index,
  107. self.wait_invoice_cancel_token.clone(),
  108. Arc::clone(&self.wait_invoice_is_active),
  109. ),
  110. |(mut cln_client, mut last_pay_idx, cancel_token, is_active)| async move {
  111. // Set the stream as active
  112. is_active.store(true, Ordering::SeqCst);
  113. tracing::debug!("CLN: Stream is now active, waiting for invoice events with lastpay_index: {:?}", last_pay_idx);
  114. loop {
  115. tokio::select! {
  116. _ = cancel_token.cancelled() => {
  117. // Set the stream as inactive
  118. is_active.store(false, Ordering::SeqCst);
  119. tracing::info!("CLN: Invoice stream cancelled");
  120. // End the stream
  121. return None;
  122. }
  123. result = cln_client.call(cln_rpc::Request::WaitAnyInvoice(WaitanyinvoiceRequest {
  124. timeout: None,
  125. lastpay_index: last_pay_idx,
  126. })) => {
  127. tracing::debug!("CLN: Received response from WaitAnyInvoice call");
  128. match result {
  129. Ok(invoice) => {
  130. tracing::debug!("CLN: Successfully received invoice data");
  131. // Try to convert the invoice to WaitanyinvoiceResponse
  132. let wait_any_response_result: Result<WaitanyinvoiceResponse, _> =
  133. invoice.try_into();
  134. let wait_any_response = match wait_any_response_result {
  135. Ok(response) => {
  136. tracing::debug!("CLN: Parsed WaitAnyInvoice response successfully");
  137. response
  138. }
  139. Err(e) => {
  140. tracing::warn!(
  141. "CLN: Failed to parse WaitAnyInvoice response: {:?}",
  142. e
  143. );
  144. // Continue to the next iteration without panicking
  145. continue;
  146. }
  147. };
  148. // Check the status of the invoice
  149. // We only want to yield invoices that have been paid
  150. match wait_any_response.status {
  151. WaitanyinvoiceStatus::PAID => {
  152. tracing::info!("CLN: Invoice with payment index {} is PAID",
  153. wait_any_response.pay_index.unwrap_or_default());
  154. }
  155. WaitanyinvoiceStatus::EXPIRED => {
  156. tracing::debug!("CLN: Invoice with payment index {} is EXPIRED, skipping",
  157. wait_any_response.pay_index.unwrap_or_default());
  158. continue;
  159. }
  160. }
  161. last_pay_idx = wait_any_response.pay_index;
  162. tracing::debug!("CLN: Updated last_pay_idx to {:?}", last_pay_idx);
  163. let payment_hash = wait_any_response.payment_hash;
  164. tracing::debug!("CLN: Payment hash: {}", payment_hash);
  165. let amount_msats = match wait_any_response.amount_received_msat {
  166. Some(amt) => {
  167. tracing::info!("CLN: Received payment of {} msats for {}",
  168. amt.msat(), payment_hash);
  169. amt
  170. }
  171. None => {
  172. tracing::error!("CLN: No amount in paid invoice, this should not happen");
  173. continue;
  174. }
  175. };
  176. let amount_sats = amount_msats.msat() / 1000;
  177. let payment_hash = Hash::from_bytes_ref(payment_hash.as_ref());
  178. let request_lookup_id = match wait_any_response.bolt12 {
  179. // If it is a bolt12 payment we need to get the offer_id as this is what we use as the request look up.
  180. // Since this is not returned in the wait any response,
  181. // we need to do a second query for it.
  182. Some(bolt12) => {
  183. tracing::info!("CLN: Processing BOLT12 payment, bolt12 value: {}", bolt12);
  184. match fetch_invoice_by_payment_hash(
  185. &mut cln_client,
  186. payment_hash,
  187. )
  188. .await
  189. {
  190. Ok(Some(invoice)) => {
  191. if let Some(local_offer_id) = invoice.local_offer_id {
  192. tracing::info!("CLN: Received bolt12 payment of {} sats for offer {}",
  193. amount_sats, local_offer_id);
  194. PaymentIdentifier::OfferId(local_offer_id.to_string())
  195. } else {
  196. tracing::warn!("CLN: BOLT12 invoice has no local_offer_id, skipping");
  197. continue;
  198. }
  199. }
  200. Ok(None) => {
  201. tracing::warn!("CLN: Failed to find invoice by payment hash, skipping");
  202. continue;
  203. }
  204. Err(e) => {
  205. tracing::warn!(
  206. "CLN: Error fetching invoice by payment hash: {e}"
  207. );
  208. continue;
  209. }
  210. }
  211. }
  212. None => {
  213. tracing::info!("CLN: Processing BOLT11 payment with hash {}", payment_hash);
  214. PaymentIdentifier::PaymentHash(*payment_hash.as_ref())
  215. },
  216. };
  217. let response = WaitPaymentResponse {
  218. payment_identifier: request_lookup_id,
  219. payment_amount: amount_sats.into(),
  220. unit: CurrencyUnit::Sat,
  221. payment_id: payment_hash.to_string()
  222. };
  223. tracing::info!("CLN: Created WaitPaymentResponse with amount {} sats", amount_sats);
  224. break Some((response, (cln_client, last_pay_idx, cancel_token, is_active)));
  225. }
  226. Err(e) => {
  227. tracing::warn!("CLN: Error fetching invoice: {e}");
  228. tokio::time::sleep(Duration::from_secs(1)).await;
  229. continue;
  230. }
  231. }
  232. }
  233. }
  234. }
  235. },
  236. )
  237. .boxed();
  238. tracing::info!("CLN: Successfully initialized invoice stream");
  239. Ok(stream)
  240. }
  241. #[instrument(skip_all)]
  242. async fn get_payment_quote(
  243. &self,
  244. unit: &CurrencyUnit,
  245. options: OutgoingPaymentOptions,
  246. ) -> Result<PaymentQuoteResponse, Self::Err> {
  247. match options {
  248. OutgoingPaymentOptions::Bolt11(bolt11_options) => {
  249. // If we have specific amount options, use those
  250. let amount_msat: Amount = if let Some(melt_options) = bolt11_options.melt_options {
  251. match melt_options {
  252. MeltOptions::Amountless { amountless } => {
  253. let amount_msat = amountless.amount_msat;
  254. if let Some(invoice_amount) =
  255. bolt11_options.bolt11.amount_milli_satoshis()
  256. {
  257. if !invoice_amount == u64::from(amount_msat) {
  258. return Err(payment::Error::AmountMismatch);
  259. }
  260. }
  261. amount_msat
  262. }
  263. MeltOptions::Mpp { mpp } => mpp.amount,
  264. }
  265. } else {
  266. // Fall back to invoice amount
  267. bolt11_options
  268. .bolt11
  269. .amount_milli_satoshis()
  270. .ok_or(Error::UnknownInvoiceAmount)?
  271. .into()
  272. };
  273. // Convert to target unit
  274. let amount = to_unit(amount_msat, &CurrencyUnit::Msat, unit)?;
  275. // Calculate fee
  276. let relative_fee_reserve =
  277. (self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
  278. let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
  279. let fee = max(relative_fee_reserve, absolute_fee_reserve);
  280. Ok(PaymentQuoteResponse {
  281. request_lookup_id: Some(PaymentIdentifier::PaymentHash(
  282. *bolt11_options.bolt11.payment_hash().as_ref(),
  283. )),
  284. amount,
  285. fee: fee.into(),
  286. state: MeltQuoteState::Unpaid,
  287. unit: unit.clone(),
  288. })
  289. }
  290. OutgoingPaymentOptions::Bolt12(bolt12_options) => {
  291. let offer = bolt12_options.offer;
  292. let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
  293. amount.amount_msat().into()
  294. } else {
  295. // Fall back to offer amount
  296. let decode_response = self.decode_string(offer.to_string()).await?;
  297. decode_response
  298. .offer_amount_msat
  299. .ok_or(Error::UnknownInvoiceAmount)?
  300. .msat()
  301. };
  302. // Convert to target unit
  303. let amount = to_unit(amount_msat, &CurrencyUnit::Msat, unit)?;
  304. // Calculate fee
  305. let relative_fee_reserve =
  306. (self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
  307. let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
  308. let fee = max(relative_fee_reserve, absolute_fee_reserve);
  309. Ok(PaymentQuoteResponse {
  310. request_lookup_id: None,
  311. amount,
  312. fee: fee.into(),
  313. state: MeltQuoteState::Unpaid,
  314. unit: unit.clone(),
  315. })
  316. }
  317. }
  318. }
  319. #[instrument(skip_all)]
  320. async fn make_payment(
  321. &self,
  322. unit: &CurrencyUnit,
  323. options: OutgoingPaymentOptions,
  324. ) -> Result<MakePaymentResponse, Self::Err> {
  325. let max_fee_msat: Option<u64>;
  326. let mut partial_amount: Option<u64> = None;
  327. let mut amount_msat: Option<u64> = None;
  328. let mut cln_client = self.cln_client().await?;
  329. let invoice = match &options {
  330. OutgoingPaymentOptions::Bolt11(bolt11_options) => {
  331. let payment_identifier =
  332. PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
  333. self.check_outgoing_unpaided(&payment_identifier).await?;
  334. if let Some(melt_options) = bolt11_options.melt_options {
  335. match melt_options {
  336. MeltOptions::Mpp { mpp } => partial_amount = Some(mpp.amount.into()),
  337. MeltOptions::Amountless { amountless } => {
  338. amount_msat = Some(amountless.amount_msat.into());
  339. }
  340. }
  341. }
  342. max_fee_msat = bolt11_options.max_fee_amount.map(|a| a.into());
  343. bolt11_options.bolt11.to_string()
  344. }
  345. OutgoingPaymentOptions::Bolt12(bolt12_options) => {
  346. let offer = &bolt12_options.offer;
  347. let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
  348. amount.amount_msat().into()
  349. } else {
  350. // Fall back to offer amount
  351. let decode_response = self.decode_string(offer.to_string()).await?;
  352. decode_response
  353. .offer_amount_msat
  354. .ok_or(Error::UnknownInvoiceAmount)?
  355. .msat()
  356. };
  357. // Fetch invoice from offer
  358. let cln_response = cln_client
  359. .call_typed(&FetchinvoiceRequest {
  360. amount_msat: Some(CLN_Amount::from_msat(amount_msat)),
  361. payer_metadata: None,
  362. payer_note: None,
  363. quantity: None,
  364. recurrence_counter: None,
  365. recurrence_label: None,
  366. recurrence_start: None,
  367. timeout: None,
  368. offer: offer.to_string(),
  369. bip353: None,
  370. })
  371. .await
  372. .map_err(|err| {
  373. tracing::error!("Could not fetch invoice for offer: {:?}", err);
  374. Error::ClnRpc(err)
  375. })?;
  376. let decode_response = self.decode_string(cln_response.invoice.clone()).await?;
  377. let payment_identifier = PaymentIdentifier::Bolt12PaymentHash(
  378. hex::decode(
  379. decode_response
  380. .invoice_payment_hash
  381. .ok_or(Error::UnknownInvoice)?,
  382. )
  383. .map_err(|e| Error::Bolt12(e.to_string()))?
  384. .try_into()
  385. .map_err(|_| Error::InvalidHash)?,
  386. );
  387. self.check_outgoing_unpaided(&payment_identifier).await?;
  388. max_fee_msat = bolt12_options.max_fee_amount.map(|a| a.into());
  389. cln_response.invoice
  390. }
  391. };
  392. let cln_response = cln_client
  393. .call_typed(&PayRequest {
  394. bolt11: invoice,
  395. amount_msat: amount_msat.map(CLN_Amount::from_msat),
  396. label: None,
  397. riskfactor: None,
  398. maxfeepercent: None,
  399. retry_for: None,
  400. maxdelay: None,
  401. exemptfee: None,
  402. localinvreqid: None,
  403. exclude: None,
  404. maxfee: max_fee_msat.map(CLN_Amount::from_msat),
  405. description: None,
  406. partial_msat: partial_amount.map(CLN_Amount::from_msat),
  407. })
  408. .await;
  409. let response = match cln_response {
  410. Ok(pay_response) => {
  411. let status = match pay_response.status {
  412. PayStatus::COMPLETE => MeltQuoteState::Paid,
  413. PayStatus::PENDING => MeltQuoteState::Pending,
  414. PayStatus::FAILED => MeltQuoteState::Failed,
  415. };
  416. let payment_identifier = match options {
  417. OutgoingPaymentOptions::Bolt11(_) => {
  418. PaymentIdentifier::PaymentHash(*pay_response.payment_hash.as_ref())
  419. }
  420. OutgoingPaymentOptions::Bolt12(_) => {
  421. PaymentIdentifier::Bolt12PaymentHash(*pay_response.payment_hash.as_ref())
  422. }
  423. };
  424. MakePaymentResponse {
  425. payment_proof: Some(hex::encode(pay_response.payment_preimage.to_vec())),
  426. payment_lookup_id: payment_identifier,
  427. status,
  428. total_spent: to_unit(
  429. pay_response.amount_sent_msat.msat(),
  430. &CurrencyUnit::Msat,
  431. unit,
  432. )?,
  433. unit: unit.clone(),
  434. }
  435. }
  436. Err(err) => {
  437. tracing::error!("Could not pay invoice: {}", err);
  438. return Err(Error::ClnRpc(err).into());
  439. }
  440. };
  441. Ok(response)
  442. }
  443. #[instrument(skip_all)]
  444. async fn create_incoming_payment_request(
  445. &self,
  446. unit: &CurrencyUnit,
  447. options: IncomingPaymentOptions,
  448. ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
  449. match options {
  450. IncomingPaymentOptions::Bolt11(Bolt11IncomingPaymentOptions {
  451. description,
  452. amount,
  453. unix_expiry,
  454. }) => {
  455. let time_now = unix_time();
  456. let mut cln_client = self.cln_client().await?;
  457. let label = Uuid::new_v4().to_string();
  458. let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
  459. let amount_msat = AmountOrAny::Amount(CLN_Amount::from_msat(amount.into()));
  460. let invoice_response = cln_client
  461. .call_typed(&InvoiceRequest {
  462. amount_msat,
  463. description: description.unwrap_or_default(),
  464. label: label.clone(),
  465. expiry: unix_expiry.map(|t| t - time_now),
  466. fallbacks: None,
  467. preimage: None,
  468. cltv: None,
  469. deschashonly: None,
  470. exposeprivatechannels: None,
  471. })
  472. .await
  473. .map_err(Error::from)?;
  474. let request = Bolt11Invoice::from_str(&invoice_response.bolt11)?;
  475. let expiry = request.expires_at().map(|t| t.as_secs());
  476. let payment_hash = request.payment_hash();
  477. Ok(CreateIncomingPaymentResponse {
  478. request_lookup_id: PaymentIdentifier::PaymentHash(*payment_hash.as_ref()),
  479. request: request.to_string(),
  480. expiry,
  481. })
  482. }
  483. IncomingPaymentOptions::Bolt12(bolt12_options) => {
  484. let Bolt12IncomingPaymentOptions {
  485. description,
  486. amount,
  487. unix_expiry,
  488. } = *bolt12_options;
  489. let mut cln_client = self.cln_client().await?;
  490. let label = Uuid::new_v4().to_string();
  491. // Match like this until we change to option
  492. let amount = match amount {
  493. Some(amount) => {
  494. let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
  495. amount.to_string()
  496. }
  497. None => "any".to_string(),
  498. };
  499. // It seems that the only way to force cln to create a unique offer
  500. // is to encode some random data in the offer
  501. let issuer = Uuid::new_v4().to_string();
  502. let offer_response = cln_client
  503. .call_typed(&OfferRequest {
  504. amount,
  505. absolute_expiry: unix_expiry,
  506. description: Some(description.unwrap_or_default()),
  507. issuer: Some(issuer.to_string()),
  508. label: Some(label.to_string()),
  509. single_use: None,
  510. quantity_max: None,
  511. recurrence: None,
  512. recurrence_base: None,
  513. recurrence_limit: None,
  514. recurrence_paywindow: None,
  515. recurrence_start_any_period: None,
  516. })
  517. .await
  518. .map_err(Error::from)?;
  519. Ok(CreateIncomingPaymentResponse {
  520. request_lookup_id: PaymentIdentifier::OfferId(
  521. offer_response.offer_id.to_string(),
  522. ),
  523. request: offer_response.bolt12,
  524. expiry: unix_expiry,
  525. })
  526. }
  527. }
  528. }
  529. #[instrument(skip(self))]
  530. async fn check_incoming_payment_status(
  531. &self,
  532. payment_identifier: &PaymentIdentifier,
  533. ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
  534. let mut cln_client = self.cln_client().await?;
  535. let listinvoices_response = match payment_identifier {
  536. PaymentIdentifier::Label(label) => {
  537. // Query by label
  538. cln_client
  539. .call_typed(&ListinvoicesRequest {
  540. payment_hash: None,
  541. label: Some(label.to_string()),
  542. invstring: None,
  543. offer_id: None,
  544. index: None,
  545. limit: None,
  546. start: None,
  547. })
  548. .await
  549. .map_err(Error::from)?
  550. }
  551. PaymentIdentifier::OfferId(offer_id) => {
  552. // Query by offer_id
  553. cln_client
  554. .call_typed(&ListinvoicesRequest {
  555. payment_hash: None,
  556. label: None,
  557. invstring: None,
  558. offer_id: Some(offer_id.to_string()),
  559. index: None,
  560. limit: None,
  561. start: None,
  562. })
  563. .await
  564. .map_err(Error::from)?
  565. }
  566. PaymentIdentifier::PaymentHash(payment_hash) => {
  567. // Query by payment_hash
  568. cln_client
  569. .call_typed(&ListinvoicesRequest {
  570. payment_hash: Some(hex::encode(payment_hash)),
  571. label: None,
  572. invstring: None,
  573. offer_id: None,
  574. index: None,
  575. limit: None,
  576. start: None,
  577. })
  578. .await
  579. .map_err(Error::from)?
  580. }
  581. PaymentIdentifier::CustomId(_) => {
  582. tracing::error!("Unsupported payment id for CLN");
  583. return Err(payment::Error::UnknownPaymentState);
  584. }
  585. PaymentIdentifier::Bolt12PaymentHash(_) => {
  586. tracing::error!("Unsupported payment id for CLN");
  587. return Err(payment::Error::UnknownPaymentState);
  588. }
  589. };
  590. Ok(listinvoices_response
  591. .invoices
  592. .iter()
  593. .filter(|p| p.status == ListinvoicesInvoicesStatus::PAID)
  594. .filter(|p| p.amount_msat.is_some()) // Filter out invoices without an amount
  595. .map(|p| WaitPaymentResponse {
  596. payment_identifier: payment_identifier.clone(),
  597. payment_amount: p
  598. .amount_msat
  599. // Safe to expect since we filtered for Some
  600. .expect("We have filter out those without amounts")
  601. .msat()
  602. .into(),
  603. unit: CurrencyUnit::Msat,
  604. payment_id: p.payment_hash.to_string(),
  605. })
  606. .collect())
  607. }
  608. #[instrument(skip(self))]
  609. async fn check_outgoing_payment(
  610. &self,
  611. payment_identifier: &PaymentIdentifier,
  612. ) -> Result<MakePaymentResponse, Self::Err> {
  613. let mut cln_client = self.cln_client().await?;
  614. let payment_hash = match payment_identifier {
  615. PaymentIdentifier::PaymentHash(hash) => hash,
  616. PaymentIdentifier::Bolt12PaymentHash(hash) => hash,
  617. _ => {
  618. tracing::error!("Unsupported identifier to check outgoing payment for cln.");
  619. return Err(payment::Error::UnknownPaymentState);
  620. }
  621. };
  622. let listpays_response = cln_client
  623. .call_typed(&ListpaysRequest {
  624. payment_hash: Some(*Sha256::from_bytes_ref(payment_hash)),
  625. bolt11: None,
  626. status: None,
  627. start: None,
  628. index: None,
  629. limit: None,
  630. })
  631. .await
  632. .map_err(Error::from)?;
  633. match listpays_response.pays.first() {
  634. Some(pays_response) => {
  635. let status = cln_pays_status_to_mint_state(pays_response.status);
  636. Ok(MakePaymentResponse {
  637. payment_lookup_id: payment_identifier.clone(),
  638. payment_proof: pays_response.preimage.map(|p| hex::encode(p.to_vec())),
  639. status,
  640. total_spent: pays_response
  641. .amount_sent_msat
  642. .map_or(Amount::ZERO, |a| a.msat().into()),
  643. unit: CurrencyUnit::Msat,
  644. })
  645. }
  646. None => Ok(MakePaymentResponse {
  647. payment_lookup_id: payment_identifier.clone(),
  648. payment_proof: None,
  649. status: MeltQuoteState::Unknown,
  650. total_spent: Amount::ZERO,
  651. unit: CurrencyUnit::Msat,
  652. }),
  653. }
  654. }
  655. }
  656. impl Cln {
  657. async fn cln_client(&self) -> Result<ClnRpc, Error> {
  658. Ok(cln_rpc::ClnRpc::new(&self.rpc_socket).await?)
  659. }
  660. /// Get last pay index for cln
  661. async fn get_last_pay_index(&self) -> Result<Option<u64>, Error> {
  662. let mut cln_client = self.cln_client().await?;
  663. let listinvoices_response = cln_client
  664. .call_typed(&ListinvoicesRequest {
  665. index: None,
  666. invstring: None,
  667. label: None,
  668. limit: None,
  669. offer_id: None,
  670. payment_hash: None,
  671. start: None,
  672. })
  673. .await
  674. .map_err(Error::from)?;
  675. match listinvoices_response.invoices.last() {
  676. Some(last_invoice) => Ok(last_invoice.pay_index),
  677. None => Ok(None),
  678. }
  679. }
  680. /// Decode string
  681. #[instrument(skip(self))]
  682. async fn decode_string(&self, string: String) -> Result<DecodeResponse, Error> {
  683. let mut cln_client = self.cln_client().await?;
  684. cln_client
  685. .call_typed(&DecodeRequest { string })
  686. .await
  687. .map_err(|err| {
  688. tracing::error!("Could not fetch invoice for offer: {:?}", err);
  689. Error::ClnRpc(err)
  690. })
  691. }
  692. /// Checks that outgoing payment is not already paid
  693. #[instrument(skip(self))]
  694. async fn check_outgoing_unpaided(
  695. &self,
  696. payment_identifier: &PaymentIdentifier,
  697. ) -> Result<(), payment::Error> {
  698. let pay_state = self.check_outgoing_payment(payment_identifier).await?;
  699. match pay_state.status {
  700. MeltQuoteState::Unpaid | MeltQuoteState::Unknown | MeltQuoteState::Failed => Ok(()),
  701. MeltQuoteState::Paid => {
  702. tracing::debug!("Melt attempted on invoice already paid");
  703. Err(payment::Error::InvoiceAlreadyPaid)
  704. }
  705. MeltQuoteState::Pending => {
  706. tracing::debug!("Melt attempted on invoice already pending");
  707. Err(payment::Error::InvoicePaymentPending)
  708. }
  709. }
  710. }
  711. }
  712. fn cln_pays_status_to_mint_state(status: ListpaysPaysStatus) -> MeltQuoteState {
  713. match status {
  714. ListpaysPaysStatus::PENDING => MeltQuoteState::Pending,
  715. ListpaysPaysStatus::COMPLETE => MeltQuoteState::Paid,
  716. ListpaysPaysStatus::FAILED => MeltQuoteState::Failed,
  717. }
  718. }
  719. async fn fetch_invoice_by_payment_hash(
  720. cln_client: &mut cln_rpc::ClnRpc,
  721. payment_hash: &Hash,
  722. ) -> Result<Option<ListinvoicesInvoices>, Error> {
  723. tracing::debug!("Fetching invoice by payment hash: {}", payment_hash);
  724. let payment_hash_str = payment_hash.to_string();
  725. tracing::debug!("Payment hash string: {}", payment_hash_str);
  726. let request = ListinvoicesRequest {
  727. payment_hash: Some(payment_hash_str),
  728. index: None,
  729. invstring: None,
  730. label: None,
  731. limit: None,
  732. offer_id: None,
  733. start: None,
  734. };
  735. tracing::debug!("Created ListinvoicesRequest");
  736. match cln_client.call_typed(&request).await {
  737. Ok(invoice_response) => {
  738. let invoice_count = invoice_response.invoices.len();
  739. tracing::debug!(
  740. "Received {} invoices for payment hash {}",
  741. invoice_count,
  742. payment_hash
  743. );
  744. if invoice_count > 0 {
  745. let first_invoice = invoice_response.invoices.first().cloned();
  746. if let Some(invoice) = &first_invoice {
  747. tracing::debug!("Found invoice with payment hash {}", payment_hash);
  748. tracing::debug!(
  749. "Invoice details - local_offer_id: {:?}, status: {:?}",
  750. invoice.local_offer_id,
  751. invoice.status
  752. );
  753. } else {
  754. tracing::warn!("No invoice found with payment hash {}", payment_hash);
  755. }
  756. Ok(first_invoice)
  757. } else {
  758. tracing::warn!("No invoices returned for payment hash {}", payment_hash);
  759. Ok(None)
  760. }
  761. }
  762. Err(e) => {
  763. tracing::error!(
  764. "Error fetching invoice by payment hash {}: {}",
  765. payment_hash,
  766. e
  767. );
  768. Err(Error::from(e))
  769. }
  770. }
  771. }