lib.rs 36 KB

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