lib.rs 38 KB

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