lib.rs 36 KB

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