1use std::collections::HashMap;
2use std::fs::OpenOptions;
3use std::io::Write;
4use std::str::FromStr;
5use std::sync::Arc;
6use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
7
8use anyhow::{anyhow, Result};
9use bip39::*;
10use bitcoin::hashes::hex::ToHex;
11use bitcoin::hashes::{sha256, Hash};
12use bitcoin::util::bip32::ChildNumber;
13use chrono::Local;
14use futures::{StreamExt, TryFutureExt};
15use log::{LevelFilter, Metadata, Record};
16use sdk_common::grpc;
17use sdk_common::prelude::*;
18use serde::Serialize;
19use serde_json::{json, Value};
20use strum_macros::EnumString;
21use tokio::sync::{mpsc, watch, Mutex};
22use tokio::time::{sleep, MissedTickBehavior};
23
24use crate::backup::{BackupRequest, BackupTransport, BackupWatcher};
25use crate::buy::{BuyBitcoinApi, BuyBitcoinService};
26use crate::chain::{
27 ChainService, Outspend, RecommendedFees, RedundantChainService, RedundantChainServiceTrait,
28 DEFAULT_MEMPOOL_SPACE_URL,
29};
30use crate::error::{
31 ConnectError, ReceiveOnchainError, ReceiveOnchainResult, ReceivePaymentError,
32 RedeemOnchainResult, SdkError, SdkResult, SendOnchainError, SendPaymentError,
33};
34use crate::lnurl::auth::SdkLnurlAuthSigner;
35use crate::lnurl::pay::*;
36use crate::lsp::LspInformation;
37use crate::models::{
38 sanitize::*, ChannelState, ClosedChannelPaymentDetails, Config, EnvironmentType, LspAPI,
39 NodeState, Payment, PaymentDetails, PaymentType, ReverseSwapPairInfo, ReverseSwapServiceAPI,
40 SwapInfo, SwapperAPI, INVOICE_PAYMENT_FEE_EXPIRY_SECONDS,
41};
42use crate::node_api::{CreateInvoiceRequest, NodeAPI};
43use crate::persist::cache::NodeStateStorage;
44use crate::persist::db::SqliteStorage;
45use crate::persist::swap::SwapStorage;
46use crate::persist::transactions::PaymentStorage;
47use crate::swap_in::{BTCReceiveSwap, BTCReceiveSwapParameters, TaprootSwapperAPI};
48use crate::swap_out::boltzswap::BoltzApi;
49use crate::swap_out::reverseswap::BTCSendSwap;
50use crate::*;
51
52pub type BreezServicesResult<T, E = ConnectError> = Result<T, E>;
53
54pub trait EventListener: Send + Sync {
56 fn on_event(&self, e: BreezEvent);
57}
58
59#[derive(Clone, Debug, PartialEq)]
62#[allow(clippy::large_enum_variant)]
63pub enum BreezEvent {
64 NewBlock { block: u32 },
66 InvoicePaid { details: InvoicePaidDetails },
68 Synced,
70 PaymentSucceed { details: Payment },
72 PaymentFailed { details: PaymentFailedData },
74 BackupStarted,
76 BackupSucceeded,
78 BackupFailed { details: BackupFailedData },
80 ReverseSwapUpdated { details: ReverseSwapInfo },
83 SwapUpdated { details: SwapInfo },
86}
87
88#[derive(Clone, Debug, PartialEq)]
89pub struct BackupFailedData {
90 pub error: String,
91}
92
93#[derive(Clone, Debug, PartialEq)]
94pub struct PaymentFailedData {
95 pub error: String,
96 pub node_id: String,
97 pub invoice: Option<LNInvoice>,
98 pub label: Option<String>,
99}
100
101#[derive(Clone, Debug, PartialEq)]
103pub struct InvoicePaidDetails {
104 pub payment_hash: String,
105 pub bolt11: String,
106 pub payment: Option<Payment>,
107}
108
109pub trait LogStream: Send + Sync {
110 fn log(&self, l: LogEntry);
111}
112
113#[derive(Clone, Debug, PartialEq)]
115pub struct SignMessageRequest {
116 pub message: String,
118}
119
120#[derive(Clone, Debug, PartialEq)]
122pub struct SignMessageResponse {
123 pub signature: String,
126}
127
128#[derive(Clone, Debug, PartialEq)]
130pub struct CheckMessageRequest {
131 pub message: String,
133 pub pubkey: String,
135 pub signature: String,
137}
138
139#[derive(Clone, Debug, PartialEq)]
141pub struct CheckMessageResponse {
142 pub is_valid: bool,
145}
146
147#[derive(Clone, PartialEq, EnumString, Serialize)]
148enum DevCommand {
149 #[strum(serialize = "generatediagnosticdata")]
151 GenerateDiagnosticData,
152}
153
154pub struct BreezServices {
156 config: Config,
157 started: Mutex<bool>,
158 node_api: Arc<dyn NodeAPI>,
159 lsp_api: Arc<dyn LspAPI>,
160 fiat_api: Arc<dyn FiatAPI>,
161 buy_bitcoin_api: Arc<dyn BuyBitcoinApi>,
162 support_api: Arc<dyn SupportAPI>,
163 chain_service: Arc<dyn ChainService>,
164 persister: Arc<SqliteStorage>,
165 rest_client: Arc<dyn RestClient>,
166 payment_receiver: Arc<PaymentReceiver>,
167 btc_receive_swapper: Arc<BTCReceiveSwap>,
168 btc_send_swapper: Arc<BTCSendSwap>,
169 event_listener: Option<Box<dyn EventListener>>,
170 backup_watcher: Arc<BackupWatcher>,
171 shutdown_sender: watch::Sender<()>,
172}
173
174impl BreezServices {
175 pub async fn connect(
192 req: ConnectRequest,
193 event_listener: Box<dyn EventListener>,
194 ) -> BreezServicesResult<Arc<BreezServices>> {
195 let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
196 info!("SDK v{sdk_version} ({sdk_git_hash})");
197 let start = Instant::now();
198 let services = BreezServicesBuilder::new(req.config)
199 .seed(req.seed)
200 .build(req.restore_only, Some(event_listener))
201 .await?;
202 services.start().await?;
203 let connect_duration = start.elapsed();
204 info!("SDK connected in: {connect_duration:?}");
205 Ok(services)
206 }
207
208 fn get_sdk_version() -> (&'static str, &'static str) {
209 let sdk_version = option_env!("CARGO_PKG_VERSION").unwrap_or_default();
210 let sdk_git_hash = option_env!("SDK_GIT_HASH").unwrap_or_default();
211 (sdk_version, sdk_git_hash)
212 }
213
214 async fn start(self: &Arc<BreezServices>) -> BreezServicesResult<()> {
222 let mut started = self.started.lock().await;
223 ensure_sdk!(
224 !*started,
225 ConnectError::Generic {
226 err: "BreezServices already started".into()
227 }
228 );
229
230 let start = Instant::now();
231 self.start_background_tasks().await?;
232 let start_duration = start.elapsed();
233 info!("SDK initialized in: {start_duration:?}");
234 *started = true;
235 Ok(())
236 }
237
238 pub async fn disconnect(&self) -> SdkResult<()> {
240 let mut started = self.started.lock().await;
241 ensure_sdk!(
242 *started,
243 SdkError::Generic {
244 err: "BreezServices is not running".into(),
245 }
246 );
247 self.shutdown_sender
248 .send(())
249 .map_err(|e| SdkError::Generic {
250 err: format!("Shutdown failed: {e}"),
251 })?;
252 self.shutdown_sender.closed().await;
253 *started = false;
254 Ok(())
255 }
256
257 pub async fn configure_node(&self, req: ConfigureNodeRequest) -> SdkResult<()> {
264 Ok(self.node_api.configure_node(req.close_to_address).await?)
265 }
266
267 pub async fn send_payment(
272 &self,
273 req: SendPaymentRequest,
274 ) -> Result<SendPaymentResponse, SendPaymentError> {
275 let parsed_invoice = parse_invoice(req.bolt11.as_str())?;
276 let invoice_expiration = parsed_invoice.timestamp + parsed_invoice.expiry;
277 let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
278 if invoice_expiration < current_time {
279 return Err(SendPaymentError::InvoiceExpired {
280 err: format!("Invoice expired at {invoice_expiration}"),
281 });
282 }
283 let invoice_amount_msat = parsed_invoice.amount_msat.unwrap_or_default();
284 let provided_amount_msat = req.amount_msat.unwrap_or_default();
285
286 validate_network(parsed_invoice.clone(), self.config.network)?;
288
289 let amount_msat = match (provided_amount_msat, invoice_amount_msat) {
290 (0, 0) => {
291 return Err(SendPaymentError::InvalidAmount {
292 err: "Amount must be provided when paying a zero invoice".into(),
293 })
294 }
295 (0, amount_msat) => amount_msat,
296 (amount_msat, 0) => amount_msat,
297 (_amount_1, _amount_2) => {
298 return Err(SendPaymentError::InvalidAmount {
299 err: "Amount should not be provided when paying a non zero invoice".into(),
300 })
301 }
302 };
303
304 if self
305 .persister
306 .get_completed_payment_by_hash(&parsed_invoice.payment_hash)?
307 .is_some()
308 {
309 return Err(SendPaymentError::AlreadyPaid);
310 }
311
312 let maybe_trampoline_id = self.get_trampoline_id(&req, &parsed_invoice)?;
316
317 self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
318
319 let trampoline_result = if let Some(trampoline_id) = maybe_trampoline_id {
321 debug!("attempting trampoline payment");
322 match self
323 .node_api
324 .send_trampoline_payment(
325 parsed_invoice.bolt11.clone(),
326 amount_msat,
327 req.label.clone(),
328 trampoline_id,
329 )
330 .await
331 {
332 Ok(res) => Some(res),
333 Err(e) => {
334 if e.to_string().contains("missing balance") {
335 debug!("trampoline payment failed due to insufficient balance: {e:?}");
336 return Err(SendPaymentError::InsufficientBalance {
337 err: "Trampoline payment failed".into(),
338 });
339 }
340
341 warn!("trampoline payment failed: {e:?}");
342 None
343 }
344 }
345 } else {
346 debug!("not attempting trampoline payment");
347 None
348 };
349
350 let payment_res = match trampoline_result {
352 Some(res) => Ok(res),
353 None => {
354 debug!("attempting normal payment");
355 self.node_api
356 .send_payment(
357 parsed_invoice.bolt11.clone(),
358 req.amount_msat,
359 req.label.clone(),
360 )
361 .map_err(Into::into)
362 .await
363 }
364 };
365
366 debug!("payment returned {payment_res:?}");
367 let payment = self
368 .on_payment_completed(
369 parsed_invoice.payee_pubkey.clone(),
370 Some(parsed_invoice),
371 req.label,
372 payment_res,
373 )
374 .await?;
375 Ok(SendPaymentResponse { payment })
376 }
377
378 fn get_trampoline_id(
379 &self,
380 req: &SendPaymentRequest,
381 invoice: &LNInvoice,
382 ) -> Result<Option<Vec<u8>>, SendPaymentError> {
383 if !req.use_trampoline {
385 return Ok(None);
386 }
387
388 let lsp_pubkey = match self.persister.get_lsp_pubkey()? {
390 Some(lsp_pubkey) => lsp_pubkey,
391 None => return Ok(None),
392 };
393
394 if invoice.routing_hints.iter().any(|hint| {
397 hint.hops
398 .last()
399 .map(|hop| hop.src_node_id == lsp_pubkey)
400 .unwrap_or(false)
401 }) {
402 return Ok(None);
403 }
404
405 Ok(Some(hex::decode(lsp_pubkey).map_err(|_| {
407 SendPaymentError::Generic {
408 err: "failed to decode lsp pubkey".to_string(),
409 }
410 })?))
411 }
412
413 pub async fn send_spontaneous_payment(
415 &self,
416 req: SendSpontaneousPaymentRequest,
417 ) -> Result<SendPaymentResponse, SendPaymentError> {
418 let payment_res = self
419 .node_api
420 .send_spontaneous_payment(
421 req.node_id.clone(),
422 req.amount_msat,
423 req.extra_tlvs,
424 req.label.clone(),
425 )
426 .map_err(Into::into)
427 .await;
428 let payment = self
429 .on_payment_completed(req.node_id, None, req.label, payment_res)
430 .await?;
431 Ok(SendPaymentResponse { payment })
432 }
433
434 pub async fn lnurl_pay(&self, req: LnUrlPayRequest) -> Result<LnUrlPayResult, LnUrlPayError> {
443 match validate_lnurl_pay(
444 self.rest_client.as_ref(),
445 req.amount_msat,
446 &req.comment,
447 &req.data,
448 self.config.network,
449 req.validate_success_action_url,
450 )
451 .await?
452 {
453 ValidatedCallbackResponse::EndpointError { data: e } => {
454 Ok(LnUrlPayResult::EndpointError { data: e })
455 }
456 ValidatedCallbackResponse::EndpointSuccess { data: cb } => {
457 let pay_req = SendPaymentRequest {
458 bolt11: cb.pr.clone(),
459 amount_msat: None,
460 use_trampoline: req.use_trampoline,
461 label: req.payment_label,
462 };
463 let invoice = parse_invoice(cb.pr.as_str())?;
464
465 let payment = match self.send_payment(pay_req).await {
466 Ok(p) => Ok(p),
467 e @ Err(
468 SendPaymentError::InvalidInvoice { .. }
469 | SendPaymentError::ServiceConnectivity { .. },
470 ) => e,
471 Err(e) => {
472 return Ok(LnUrlPayResult::PayError {
473 data: LnUrlPayErrorData {
474 payment_hash: invoice.payment_hash,
475 reason: e.to_string(),
476 },
477 })
478 }
479 }?
480 .payment;
481 let details = match &payment.details {
482 PaymentDetails::ClosedChannel { .. } => {
483 return Err(LnUrlPayError::Generic {
484 err: "Payment lookup found unexpected payment type".into(),
485 });
486 }
487 PaymentDetails::Ln { data } => data,
488 };
489
490 let maybe_sa_processed: Option<SuccessActionProcessed> = match cb.success_action {
491 Some(sa) => {
492 let processed_sa = match sa {
493 SuccessAction::Aes { data } => {
495 let preimage = sha256::Hash::from_str(&details.payment_preimage)?;
496 let preimage_arr: [u8; 32] = preimage.into_inner();
497 let result = match (data, &preimage_arr).try_into() {
498 Ok(data) => AesSuccessActionDataResult::Decrypted { data },
499 Err(e) => AesSuccessActionDataResult::ErrorStatus {
500 reason: e.to_string(),
501 },
502 };
503 SuccessActionProcessed::Aes { result }
504 }
505 SuccessAction::Message { data } => {
506 SuccessActionProcessed::Message { data }
507 }
508 SuccessAction::Url { data } => SuccessActionProcessed::Url { data },
509 };
510 Some(processed_sa)
511 }
512 None => None,
513 };
514
515 let lnurl_pay_domain = match req.data.ln_address {
516 Some(_) => None,
517 None => Some(req.data.domain),
518 };
519 self.persister.insert_payment_external_info(
521 &details.payment_hash,
522 PaymentExternalInfo {
523 lnurl_pay_success_action: maybe_sa_processed.clone(),
524 lnurl_pay_domain,
525 lnurl_pay_comment: req.comment,
526 lnurl_metadata: Some(req.data.metadata_str),
527 ln_address: req.data.ln_address,
528 lnurl_withdraw_endpoint: None,
529 attempted_amount_msat: invoice.amount_msat,
530 attempted_error: None,
531 },
532 )?;
533
534 Ok(LnUrlPayResult::EndpointSuccess {
535 data: lnurl::pay::LnUrlPaySuccessData {
536 payment,
537 success_action: maybe_sa_processed,
538 },
539 })
540 }
541 }
542 }
543
544 pub async fn lnurl_withdraw(
551 &self,
552 req: LnUrlWithdrawRequest,
553 ) -> Result<LnUrlWithdrawResult, LnUrlWithdrawError> {
554 let invoice = self
555 .receive_payment(ReceivePaymentRequest {
556 amount_msat: req.amount_msat,
557 description: req.description.unwrap_or_default(),
558 use_description_hash: Some(false),
559 ..Default::default()
560 })
561 .await?
562 .ln_invoice;
563
564 let lnurl_w_endpoint = req.data.callback.clone();
565 let res = validate_lnurl_withdraw(self.rest_client.as_ref(), req.data, invoice).await?;
566
567 if let LnUrlWithdrawResult::Ok { ref data } = res {
568 self.persister.insert_payment_external_info(
570 &data.invoice.payment_hash,
571 PaymentExternalInfo {
572 lnurl_pay_success_action: None,
573 lnurl_pay_domain: None,
574 lnurl_pay_comment: None,
575 lnurl_metadata: None,
576 ln_address: None,
577 lnurl_withdraw_endpoint: Some(lnurl_w_endpoint),
578 attempted_amount_msat: None,
579 attempted_error: None,
580 },
581 )?;
582 }
583
584 Ok(res)
585 }
586
587 pub async fn lnurl_auth(
593 &self,
594 req_data: LnUrlAuthRequestData,
595 ) -> Result<LnUrlCallbackStatus, LnUrlAuthError> {
596 Ok(perform_lnurl_auth(
597 self.rest_client.as_ref(),
598 &req_data,
599 &SdkLnurlAuthSigner::new(self.node_api.clone()),
600 )
601 .await?)
602 }
603
604 pub async fn receive_payment(
609 &self,
610 req: ReceivePaymentRequest,
611 ) -> Result<ReceivePaymentResponse, ReceivePaymentError> {
612 self.payment_receiver.receive_payment(req).await
613 }
614
615 pub async fn report_issue(&self, req: ReportIssueRequest) -> SdkResult<()> {
621 match self.persister.get_node_state()? {
622 Some(node_state) => match req {
623 ReportIssueRequest::PaymentFailure { data } => {
624 let payment = self
625 .persister
626 .get_payment_by_hash(&data.payment_hash)?
627 .ok_or(SdkError::Generic {
628 err: "Payment not found".into(),
629 })?;
630 let lsp_id = self.persister.get_lsp_id()?;
631
632 self.support_api
633 .report_payment_failure(node_state, payment, lsp_id, data.comment)
634 .await
635 }
636 },
637 None => Err(SdkError::Generic {
638 err: "Node state not found".into(),
639 }),
640 }
641 }
642
643 pub async fn node_credentials(&self) -> SdkResult<Option<NodeCredentials>> {
645 Ok(self.node_api.node_credentials().await?)
646 }
647
648 pub fn node_info(&self) -> SdkResult<NodeState> {
652 self.persister.get_node_state()?.ok_or(SdkError::Generic {
653 err: "Node info not found".into(),
654 })
655 }
656
657 pub async fn sign_message(&self, req: SignMessageRequest) -> SdkResult<SignMessageResponse> {
660 let signature = self.node_api.sign_message(&req.message).await?;
661 Ok(SignMessageResponse { signature })
662 }
663
664 pub async fn check_message(&self, req: CheckMessageRequest) -> SdkResult<CheckMessageResponse> {
667 let is_valid = self
668 .node_api
669 .check_message(&req.message, &req.pubkey, &req.signature)
670 .await?;
671 Ok(CheckMessageResponse { is_valid })
672 }
673
674 pub fn backup_status(&self) -> SdkResult<BackupStatus> {
676 let backup_time = self.persister.get_last_backup_time()?;
677 let sync_request = self.persister.get_last_sync_request()?;
678 Ok(BackupStatus {
679 last_backup_time: backup_time,
680 backed_up: sync_request.is_none(),
681 })
682 }
683
684 pub async fn backup(&self) -> SdkResult<()> {
686 let (on_complete, mut on_complete_receiver) = mpsc::channel::<Result<()>>(1);
687 let req = BackupRequest::with(on_complete, true);
688 self.backup_watcher.request_backup(req).await?;
689
690 match on_complete_receiver.recv().await {
691 Some(res) => res.map_err(|e| SdkError::Generic {
692 err: format!("Backup failed: {e}"),
693 }),
694 None => Err(SdkError::Generic {
695 err: "Backup process failed to complete".into(),
696 }),
697 }
698 }
699
700 pub async fn list_payments(&self, req: ListPaymentsRequest) -> SdkResult<Vec<Payment>> {
702 Ok(self.persister.list_payments(req)?)
703 }
704
705 pub async fn payment_by_hash(&self, hash: String) -> SdkResult<Option<Payment>> {
707 Ok(self.persister.get_payment_by_hash(&hash)?)
708 }
709
710 pub async fn set_payment_metadata(&self, hash: String, metadata: String) -> SdkResult<()> {
712 Ok(self
713 .persister
714 .set_payment_external_metadata(hash, metadata)?)
715 }
716
717 pub async fn redeem_onchain_funds(
719 &self,
720 req: RedeemOnchainFundsRequest,
721 ) -> RedeemOnchainResult<RedeemOnchainFundsResponse> {
722 let txid = self
723 .node_api
724 .redeem_onchain_funds(req.to_address, req.sat_per_vbyte)
725 .await?;
726 self.sync().await?;
727 Ok(RedeemOnchainFundsResponse { txid })
728 }
729
730 pub async fn prepare_redeem_onchain_funds(
731 &self,
732 req: PrepareRedeemOnchainFundsRequest,
733 ) -> RedeemOnchainResult<PrepareRedeemOnchainFundsResponse> {
734 let response = self.node_api.prepare_redeem_onchain_funds(req).await?;
735 Ok(response)
736 }
737
738 pub async fn fetch_fiat_rates(&self) -> SdkResult<Vec<Rate>> {
740 self.fiat_api.fetch_fiat_rates().await.map_err(Into::into)
741 }
742
743 pub async fn list_fiat_currencies(&self) -> SdkResult<Vec<FiatCurrency>> {
746 self.fiat_api
747 .list_fiat_currencies()
748 .await
749 .map_err(Into::into)
750 }
751
752 pub async fn list_lsps(&self) -> SdkResult<Vec<LspInformation>> {
754 self.lsp_api.list_lsps(self.node_info()?.id).await
755 }
756
757 pub async fn connect_lsp(&self, lsp_id: String) -> SdkResult<()> {
759 let lsp_pubkey = match self.list_lsps().await?.iter().find(|lsp| lsp.id == lsp_id) {
760 Some(lsp) => lsp.pubkey.clone(),
761 None => {
762 return Err(SdkError::Generic {
763 err: format!("Unknown LSP: {lsp_id}"),
764 })
765 }
766 };
767
768 self.persister.set_lsp(lsp_id, Some(lsp_pubkey))?;
769 self.sync().await?;
770 if let Some(webhook_url) = self.persister.get_webhook_url()? {
771 self.register_payment_notifications(webhook_url).await?
772 }
773 Ok(())
774 }
775
776 pub async fn lsp_id(&self) -> SdkResult<Option<String>> {
778 Ok(self.persister.get_lsp_id()?)
779 }
780
781 pub async fn fetch_lsp_info(&self, id: String) -> SdkResult<Option<LspInformation>> {
783 get_lsp_by_id(self.persister.clone(), self.lsp_api.clone(), id.as_str()).await
784 }
785
786 pub async fn open_channel_fee(
789 &self,
790 req: OpenChannelFeeRequest,
791 ) -> SdkResult<OpenChannelFeeResponse> {
792 let lsp_info = self.lsp_info().await?;
793 let fee_params = lsp_info
794 .cheapest_open_channel_fee(req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS))?
795 .clone();
796
797 let node_state = self.node_info()?;
798 let fee_msat = req.amount_msat.map(|req_amount_msat| {
799 match node_state.max_receivable_single_payment_amount_msat >= req_amount_msat {
800 true => 0,
802 false => fee_params.get_channel_fees_msat_for(req_amount_msat),
804 }
805 });
806
807 Ok(OpenChannelFeeResponse {
808 fee_msat,
809 fee_params,
810 })
811 }
812
813 pub async fn close_lsp_channels(&self) -> SdkResult<Vec<String>> {
817 let lsps = self.get_lsps().await?;
818 let mut all_txids = Vec::new();
819 for lsp in lsps {
820 let txids = self.node_api.close_peer_channels(lsp.pubkey).await?;
821 all_txids.extend(txids);
822 }
823 self.sync().await?;
824 Ok(all_txids)
825 }
826
827 pub async fn receive_onchain(
840 &self,
841 req: ReceiveOnchainRequest,
842 ) -> ReceiveOnchainResult<SwapInfo> {
843 if let Some(in_progress) = self.in_progress_swap().await? {
844 return Err(ReceiveOnchainError::SwapInProgress{ err:format!(
845 "A swap was detected for address {}. Use in_progress_swap method to get the current swap state",
846 in_progress.bitcoin_address
847 )});
848 }
849 let channel_opening_fees = req.opening_fee_params.unwrap_or(
850 self.lsp_info()
851 .await?
852 .cheapest_open_channel_fee(SWAP_PAYMENT_FEE_EXPIRY_SECONDS)?
853 .clone(),
854 );
855
856 let swap_info = self
857 .btc_receive_swapper
858 .create_swap(channel_opening_fees)
859 .await?;
860 if let Some(webhook_url) = self.persister.get_webhook_url()? {
861 let address = &swap_info.bitcoin_address;
862 info!("Registering for onchain tx notification for address {address}");
863 self.register_onchain_tx_notification(address, &webhook_url)
864 .await?;
865 }
866 Ok(swap_info)
867 }
868
869 pub async fn in_progress_swap(&self) -> SdkResult<Option<SwapInfo>> {
872 let tip = self.chain_service.current_tip().await?;
873 self.btc_receive_swapper.rescan_monitored_swaps(tip).await?;
874 let in_progress = self.btc_receive_swapper.list_in_progress_swaps()?;
875 if !in_progress.is_empty() {
876 return Ok(Some(in_progress[0].clone()));
877 }
878 Ok(None)
879 }
880
881 pub async fn rescan_swaps(&self) -> SdkResult<()> {
884 let tip = self.chain_service.current_tip().await?;
885 self.btc_receive_swapper.rescan_swaps(tip).await?;
886 Ok(())
887 }
888
889 pub async fn redeem_swap(&self, swap_address: String) -> SdkResult<()> {
896 let tip = self.chain_service.current_tip().await?;
897 self.btc_receive_swapper
898 .rescan_swap(&swap_address, tip)
899 .await?;
900 self.btc_receive_swapper.redeem_swap(swap_address).await?;
901 Ok(())
902 }
903
904 pub async fn list_swaps(&self, req: ListSwapsRequest) -> SdkResult<Vec<SwapInfo>> {
908 Ok(self.persister.list_swaps(req)?)
909 }
910
911 pub async fn claim_reverse_swap(&self, lockup_address: String) -> SdkResult<()> {
918 Ok(self
919 .btc_send_swapper
920 .claim_reverse_swap(lockup_address)
921 .await?)
922 }
923
924 pub async fn fetch_reverse_swap_fees(
939 &self,
940 req: ReverseSwapFeesRequest,
941 ) -> SdkResult<ReverseSwapPairInfo> {
942 let mut res = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
943
944 if let Some(amt) = req.send_amount_sat {
945 ensure_sdk!(amt <= res.max, SdkError::generic("Send amount is too high"));
946 ensure_sdk!(amt >= res.min, SdkError::generic("Send amount is too low"));
947
948 if let Some(claim_tx_feerate) = req.claim_tx_feerate {
949 res.fees_claim = BTCSendSwap::calculate_claim_tx_fee(claim_tx_feerate)?;
950 }
951
952 let service_fee_sat = swap_out::get_service_fee_sat(amt, res.fees_percentage);
953 res.total_fees = Some(service_fee_sat + res.fees_lockup + res.fees_claim);
954 }
955
956 Ok(res)
957 }
958 async fn max_reverse_swap_amount(&self) -> SdkResult<u64> {
964 let last_hop = self.btc_send_swapper.last_hop_for_payment().await?;
966 info!("max_reverse_swap_amount last_hop={last_hop:?}");
967 let max_to_pay = self
971 .node_api
972 .max_sendable_amount(
973 Some(
974 hex::decode(&last_hop.src_node_id).map_err(|e| SdkError::Generic {
975 err: format!("Failed to decode hex node_id: {e}"),
976 })?,
977 ),
978 swap_out::reverseswap::MAX_PAYMENT_PATH_HOPS,
979 Some(&last_hop),
980 )
981 .await?;
982
983 let total_msat: u64 = max_to_pay.into_iter().map(|m| m.amount_msat).sum();
985 let total_sat = total_msat / 1000;
986 Ok(total_sat)
987 }
988
989 pub async fn list_refundables(&self) -> SdkResult<Vec<SwapInfo>> {
991 Ok(self.btc_receive_swapper.list_refundables()?)
992 }
993
994 pub async fn prepare_refund(
999 &self,
1000 req: PrepareRefundRequest,
1001 ) -> SdkResult<PrepareRefundResponse> {
1002 Ok(self.btc_receive_swapper.prepare_refund(req).await?)
1003 }
1004
1005 pub async fn refund(&self, req: RefundRequest) -> SdkResult<RefundResponse> {
1009 Ok(self.btc_receive_swapper.refund(req).await?)
1010 }
1011
1012 pub async fn onchain_payment_limits(&self) -> SdkResult<OnchainPaymentLimitsResponse> {
1013 let fee_info = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
1014 debug!("Reverse swap pair info: {fee_info:?}");
1015 let max_amt_current_channels = self.max_reverse_swap_amount().await?;
1016 debug!("Max send amount possible with current channels: {max_amt_current_channels:?}");
1017
1018 Ok(OnchainPaymentLimitsResponse {
1019 min_sat: fee_info.min,
1020 max_sat: fee_info.max,
1021 max_payable_sat: max_amt_current_channels,
1022 })
1023 }
1024
1025 pub async fn prepare_onchain_payment(
1033 &self,
1034 req: PrepareOnchainPaymentRequest,
1035 ) -> Result<PrepareOnchainPaymentResponse, SendOnchainError> {
1036 let fees_claim = BTCSendSwap::calculate_claim_tx_fee(req.claim_tx_feerate)?;
1037 BTCSendSwap::validate_claim_tx_fee(fees_claim)?;
1038
1039 let fee_info = self.btc_send_swapper.fetch_reverse_swap_fees().await?;
1040
1041 let fees_lockup = fee_info.fees_lockup;
1043 let p = fee_info.fees_percentage;
1044 let (send_amt, recv_amt) = match req.amount_type {
1045 SwapAmountType::Send => {
1046 let temp_send_amt = req.amount_sat;
1047 let service_fees = swap_out::get_service_fee_sat(temp_send_amt, p);
1048 let total_fees = service_fees + fees_lockup + fees_claim;
1049 ensure_sdk!(
1050 temp_send_amt > total_fees,
1051 SendOnchainError::generic(
1052 "Send amount is not high enough to account for all fees"
1053 )
1054 );
1055
1056 (temp_send_amt, temp_send_amt - total_fees)
1057 }
1058 SwapAmountType::Receive => {
1059 let temp_recv_amt = req.amount_sat;
1060 let send_amt_minus_service_fee = temp_recv_amt + fees_lockup + fees_claim;
1061 let temp_send_amt = swap_out::get_invoice_amount_sat(send_amt_minus_service_fee, p);
1062
1063 (temp_send_amt, temp_recv_amt)
1064 }
1065 };
1066
1067 let is_send_in_range = send_amt >= fee_info.min && send_amt <= fee_info.max;
1068 ensure_sdk!(is_send_in_range, SendOnchainError::OutOfRange);
1069
1070 Ok(PrepareOnchainPaymentResponse {
1071 fees_hash: fee_info.fees_hash.clone(),
1072 fees_percentage: p,
1073 fees_lockup,
1074 fees_claim,
1075 sender_amount_sat: send_amt,
1076 recipient_amount_sat: recv_amt,
1077 total_fees: send_amt - recv_amt,
1078 })
1079 }
1080
1081 pub async fn pay_onchain(
1083 &self,
1084 req: PayOnchainRequest,
1085 ) -> Result<PayOnchainResponse, SendOnchainError> {
1086 ensure_sdk!(
1087 req.prepare_res.sender_amount_sat > req.prepare_res.recipient_amount_sat,
1088 SendOnchainError::generic("Send amount must be bigger than receive amount")
1089 );
1090
1091 ensure_sdk!(self.in_progress_onchain_payments().await?.is_empty(), SendOnchainError::Generic { err:
1092 "You can only start a new one after after the ongoing ones finish. \
1093 Use the in_progress_onchain_payments method to get an overview of currently ongoing reverse swaps".into(),
1094 });
1095
1096 let full_rsi = self.btc_send_swapper.create_reverse_swap(req).await?;
1097 let reverse_swap_info = self
1098 .btc_send_swapper
1099 .convert_reverse_swap_info(full_rsi.clone())
1100 .await?;
1101 self.do_sync(false).await?;
1102
1103 if let Some(webhook_url) = self.persister.get_webhook_url()? {
1104 let address = &full_rsi
1105 .get_lockup_address(self.config.network)?
1106 .to_string();
1107 info!("Registering for onchain tx notification for address {address}");
1108 self.register_onchain_tx_notification(address, &webhook_url)
1109 .await?;
1110 }
1111 Ok(PayOnchainResponse { reverse_swap_info })
1112 }
1113
1114 pub async fn in_progress_onchain_payments(&self) -> SdkResult<Vec<ReverseSwapInfo>> {
1116 let full_rsis = self.btc_send_swapper.list_blocking().await?;
1117
1118 let mut rsis = vec![];
1119 for full_rsi in full_rsis {
1120 let rsi = self
1121 .btc_send_swapper
1122 .convert_reverse_swap_info(full_rsi)
1123 .await?;
1124 rsis.push(rsi);
1125 }
1126
1127 Ok(rsis)
1128 }
1129
1130 pub async fn execute_dev_command(&self, command: String) -> SdkResult<String> {
1133 let dev_cmd_res = DevCommand::from_str(&command);
1134
1135 match dev_cmd_res {
1136 Ok(dev_cmd) => match dev_cmd {
1137 DevCommand::GenerateDiagnosticData => self.generate_diagnostic_data().await,
1138 },
1139 Err(_) => Ok(crate::serializer::to_string_pretty(
1140 &self.node_api.execute_command(command).await?,
1141 )?),
1142 }
1143 }
1144
1145 pub async fn generate_diagnostic_data(&self) -> SdkResult<String> {
1148 let now_sec = SystemTime::now()
1149 .duration_since(UNIX_EPOCH)
1150 .map(|d| d.as_secs())
1151 .unwrap_or_default();
1152 let node_data = self
1153 .node_api
1154 .generate_diagnostic_data()
1155 .await
1156 .unwrap_or_else(|e| json!({"error": e.to_string()}));
1157 let sdk_data = self
1158 .generate_sdk_diagnostic_data()
1159 .await
1160 .unwrap_or_else(|e| json!({"error": e.to_string()}));
1161 let result = json!({
1162 "timestamp": now_sec,
1163 "node": node_data,
1164 "sdk": sdk_data
1165 });
1166 Ok(crate::serializer::to_string_pretty(&result)?)
1167 }
1168
1169 pub async fn sync(&self) -> SdkResult<()> {
1175 Ok(self.do_sync(false).await?)
1176 }
1177
1178 async fn do_sync(&self, match_local_balance: bool) -> Result<()> {
1179 let start = Instant::now();
1180 let node_pubkey = self.node_api.node_id().await?;
1181 self.connect_lsp_peer(node_pubkey).await?;
1182
1183 let sync_state = self.persister.get_sync_state()?;
1185 let new_data = &self
1186 .node_api
1187 .pull_changed(sync_state.clone(), match_local_balance)
1188 .await?;
1189
1190 debug!(
1191 "pull changed old state={:?} new state={:?}",
1192 sync_state, new_data.sync_state
1193 );
1194
1195 self.persister.set_node_state(&new_data.node_state)?;
1197
1198 let channels_before_update = self.persister.list_channels()?;
1199 self.persister.update_channels(&new_data.channels)?;
1200 let channels_after_update = self.persister.list_channels()?;
1201
1202 if channels_before_update.len() != channels_after_update.len() {
1204 info!("fetching static backup file from node");
1205 let backup = self.node_api.static_backup().await?;
1206 self.persister.set_static_backup(backup)?;
1207 }
1208
1209 let mut closed_channel_payments: Vec<Payment> = vec![];
1211 for closed_channel in
1212 self.persister.list_channels()?.into_iter().filter(|c| {
1213 c.state == ChannelState::Closed || c.state == ChannelState::PendingClose
1214 })
1215 {
1216 let closed_channel_tx = self.closed_channel_to_transaction(closed_channel).await?;
1217 closed_channel_payments.push(closed_channel_tx);
1218 }
1219
1220 let mut payments = closed_channel_payments;
1222 payments.extend(new_data.payments.clone());
1223 self.persister.delete_pseudo_payments()?;
1224 self.persister.insert_or_update_payments(&payments, false)?;
1225 let duration = start.elapsed();
1226 info!("Sync duration: {duration:?}");
1227
1228 self.persister.set_sync_state(&new_data.sync_state)?;
1230 self.notify_event_listeners(BreezEvent::Synced).await?;
1231 Ok(())
1232 }
1233
1234 async fn connect_lsp_peer(&self, node_pubkey: String) -> SdkResult<()> {
1238 let lsps = self.lsp_api.list_lsps(node_pubkey).await?;
1239 let lsp = match self
1240 .persister
1241 .get_lsp_id()?
1242 .and_then(|lsp_id| lsps.iter().find(|lsp| lsp.id == lsp_id))
1243 .or_else(|| lsps.first())
1244 {
1245 Some(lsp) => lsp.clone(),
1246 None => return Ok(()),
1247 };
1248
1249 self.persister.set_lsp(lsp.id, Some(lsp.pubkey.clone()))?;
1250 let node_state = match self.node_info() {
1251 Ok(node_state) => node_state,
1252 Err(_) => return Ok(()),
1253 };
1254
1255 let node_id = lsp.pubkey;
1256 let address = lsp.host;
1257 let lsp_connected = node_state
1258 .connected_peers
1259 .iter()
1260 .any(|e| e == node_id.as_str());
1261 if !lsp_connected {
1262 debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
1263 self.node_api
1264 .connect_peer(node_id.clone(), address.clone())
1265 .await
1266 .map_err(|e| SdkError::ServiceConnectivity {
1267 err: format!("(LSP: {node_id}) Failed to connect: {e}"),
1268 })?;
1269 debug!("connected to lsp {node_id}@{address}");
1270 }
1271
1272 Ok(())
1273 }
1274
1275 fn persist_pending_payment(
1276 &self,
1277 invoice: &LNInvoice,
1278 amount_msat: u64,
1279 label: Option<String>,
1280 ) -> Result<(), SendPaymentError> {
1281 self.persister.insert_or_update_payments(
1282 &[Payment {
1283 id: invoice.payment_hash.clone(),
1284 payment_type: PaymentType::Sent,
1285 payment_time: SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64,
1286 amount_msat,
1287 fee_msat: 0,
1288 status: PaymentStatus::Pending,
1289 error: None,
1290 description: invoice.description.clone(),
1291 details: PaymentDetails::Ln {
1292 data: LnPaymentDetails {
1293 payment_hash: invoice.payment_hash.clone(),
1294 label: label.unwrap_or_default(),
1295 destination_pubkey: invoice.payee_pubkey.clone(),
1296 payment_preimage: String::new(),
1297 keysend: false,
1298 bolt11: invoice.bolt11.clone(),
1299 lnurl_success_action: None,
1300 lnurl_pay_domain: None,
1301 lnurl_pay_comment: None,
1302 ln_address: None,
1303 lnurl_metadata: None,
1304 lnurl_withdraw_endpoint: None,
1305 swap_info: None,
1306 reverse_swap_info: None,
1307 pending_expiration_block: None,
1308 open_channel_bolt11: None,
1309 },
1310 },
1311 metadata: None,
1312 }],
1313 true,
1314 )?;
1315
1316 self.persister.insert_payment_external_info(
1317 &invoice.payment_hash,
1318 PaymentExternalInfo {
1319 lnurl_pay_success_action: None,
1320 lnurl_pay_domain: None,
1321 lnurl_pay_comment: None,
1322 lnurl_metadata: None,
1323 ln_address: None,
1324 lnurl_withdraw_endpoint: None,
1325 attempted_amount_msat: invoice.amount_msat.map_or(Some(amount_msat), |_| None),
1326 attempted_error: None,
1327 },
1328 )?;
1329 Ok(())
1330 }
1331
1332 async fn on_payment_completed(
1333 &self,
1334 node_id: String,
1335 invoice: Option<LNInvoice>,
1336 label: Option<String>,
1337 payment_res: Result<Payment, SendPaymentError>,
1338 ) -> Result<Payment, SendPaymentError> {
1339 self.do_sync(false).await?;
1340 match payment_res {
1341 Ok(payment) => {
1342 self.notify_event_listeners(BreezEvent::PaymentSucceed {
1343 details: payment.clone(),
1344 })
1345 .await?;
1346 Ok(payment)
1347 }
1348 Err(e) => {
1349 if let Some(invoice) = invoice.clone() {
1350 self.persister.update_payment_attempted_error(
1351 &invoice.payment_hash,
1352 Some(e.to_string()),
1353 )?;
1354 }
1355 self.notify_event_listeners(BreezEvent::PaymentFailed {
1356 details: PaymentFailedData {
1357 error: e.to_string(),
1358 node_id,
1359 invoice,
1360 label,
1361 },
1362 })
1363 .await?;
1364 Err(e)
1365 }
1366 }
1367 }
1368
1369 async fn on_event(&self, e: BreezEvent) -> Result<()> {
1370 debug!("breez services got event {e:?}");
1371 self.notify_event_listeners(e.clone()).await
1372 }
1373
1374 async fn notify_event_listeners(&self, e: BreezEvent) -> Result<()> {
1375 if let Err(err) = self.btc_receive_swapper.on_event(e.clone()).await {
1376 debug!("btc_receive_swapper failed to process event {e:?}: {err:?}")
1377 };
1378 if let Err(err) = self.btc_send_swapper.on_event(e.clone()).await {
1379 debug!("btc_send_swapper failed to process event {e:?}: {err:?}")
1380 };
1381
1382 if self.event_listener.is_some() {
1383 self.event_listener.as_ref().unwrap().on_event(e.clone())
1384 }
1385 Ok(())
1386 }
1387
1388 pub async fn lsp_info(&self) -> SdkResult<LspInformation> {
1390 get_lsp(self.persister.clone(), self.lsp_api.clone()).await
1391 }
1392
1393 async fn get_lsps(&self) -> SdkResult<Vec<LspInformation>> {
1394 get_lsps(self.persister.clone(), self.lsp_api.clone()).await
1395 }
1396
1397 pub async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
1399 self.chain_service.recommended_fees().await
1400 }
1401
1402 pub fn default_config(
1404 env_type: EnvironmentType,
1405 api_key: String,
1406 node_config: NodeConfig,
1407 ) -> Config {
1408 match env_type {
1409 EnvironmentType::Production => Config::production(api_key, node_config),
1410 EnvironmentType::Staging => Config::staging(api_key, node_config),
1411 EnvironmentType::Regtest => Config::regtest(api_key, node_config),
1412 }
1413 }
1414
1415 pub fn static_backup(req: StaticBackupRequest) -> SdkResult<StaticBackupResponse> {
1419 let storage = SqliteStorage::new(req.working_dir);
1420 Ok(StaticBackupResponse {
1421 backup: storage.get_static_backup()?,
1422 })
1423 }
1424
1425 pub async fn service_health_check(api_key: String) -> SdkResult<ServiceHealthCheckResponse> {
1427 let support_api: Arc<dyn SupportAPI> = Arc::new(BreezServer::new(
1428 PRODUCTION_BREEZSERVER_URL.to_string(),
1429 Some(api_key),
1430 )?);
1431
1432 support_api.service_health_check().await
1433 }
1434
1435 pub async fn buy_bitcoin(
1440 &self,
1441 req: BuyBitcoinRequest,
1442 ) -> Result<BuyBitcoinResponse, ReceiveOnchainError> {
1443 let swap_info = self
1444 .receive_onchain(ReceiveOnchainRequest {
1445 opening_fee_params: req.opening_fee_params,
1446 })
1447 .await?;
1448 let url = self
1449 .buy_bitcoin_api
1450 .buy_bitcoin(req.provider, &swap_info, req.redirect_url)
1451 .await?;
1452
1453 Ok(BuyBitcoinResponse {
1454 url,
1455 opening_fee_params: swap_info.channel_opening_fees,
1456 })
1457 }
1458
1459 async fn start_background_tasks(self: &Arc<BreezServices>) -> SdkResult<()> {
1463 let (shutdown_signer_sender, signer_signer_receiver) = watch::channel(());
1465 self.start_signer(signer_signer_receiver).await;
1466 self.start_node_keep_alive(self.shutdown_sender.subscribe())
1467 .await;
1468
1469 match self.persister.get_node_state()? {
1471 Some(node) => {
1472 info!("Starting existing node {}", node.id);
1473 self.connect_lsp_peer(node.id).await?;
1474 }
1475 None => {
1476 info!("First run, syncing in foreground");
1478 self.sync().await?;
1479 info!("First run, finished running syncing in foreground");
1480 }
1481 }
1482
1483 self.start_backup_watcher().await?;
1485
1486 self.track_backup_events().await;
1488
1489 self.track_swap_events().await;
1491
1492 self.track_invoices().await;
1494
1495 self.track_new_blocks().await;
1497
1498 self.track_logs().await;
1500
1501 let mut shutdown_receiver = self.shutdown_sender.subscribe();
1503 tokio::spawn(async move {
1504 _ = shutdown_receiver.changed().await;
1505 _ = shutdown_signer_sender.send(());
1506 debug!("Received the signal to exit signer");
1507 });
1508
1509 self.init_chainservice_urls().await?;
1510
1511 Ok(())
1512 }
1513
1514 async fn start_signer(self: &Arc<BreezServices>, mut shutdown_receiver: watch::Receiver<()>) {
1515 let node_api = self.node_api.clone();
1516
1517 tokio::spawn(async move {
1518 loop {
1519 let (tx, rx) = mpsc::channel(1);
1520 let mut node_future = node_api.start(rx);
1521 tokio::select! {
1522 _ = &mut node_future => {
1523 warn!("Node exited itself, restarting");
1524 tokio::time::sleep(Duration::from_secs(1)).await;
1525 }
1526 _ = shutdown_receiver.changed() => {
1527 debug!("Shutting down node");
1528 drop(tx);
1529 debug!("Waiting for node to shut down");
1530 node_future.await;
1531 return;
1532 }
1533 };
1534 }
1535 });
1536 }
1537
1538 async fn start_node_keep_alive(
1539 self: &Arc<BreezServices>,
1540 shutdown_receiver: watch::Receiver<()>,
1541 ) {
1542 let cloned = self.clone();
1543 tokio::spawn(async move {
1544 cloned.node_api.start_keep_alive(shutdown_receiver).await;
1545 });
1546 }
1547
1548 async fn start_backup_watcher(self: &Arc<BreezServices>) -> Result<()> {
1549 self.backup_watcher
1550 .start(self.shutdown_sender.subscribe())
1551 .await
1552 .map_err(|e| anyhow!("Failed to start backup watcher: {e}"))?;
1553
1554 let force_backup = self
1556 .persister
1557 .get_last_sync_version()
1558 .map_err(|e| anyhow!("Failed to read last sync version: {e}"))?
1559 .is_none();
1560 self.backup_watcher
1561 .request_backup(BackupRequest::new(force_backup))
1562 .await
1563 .map_err(|e| anyhow!("Failed to request backup: {e}"))
1564 }
1565
1566 async fn track_backup_events(self: &Arc<BreezServices>) {
1567 let cloned = self.clone();
1568 tokio::spawn(async move {
1569 let mut events_stream = cloned.backup_watcher.subscribe_events();
1570 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1571 loop {
1572 tokio::select! {
1573 backup_event = events_stream.recv() => {
1574 if let Ok(e) = backup_event {
1575 if let Err(err) = cloned.notify_event_listeners(e).await {
1576 error!("error handling backup event: {err:?}");
1577 }
1578 }
1579 let backup_status = cloned.backup_status();
1580 info!("backup status: {backup_status:?}");
1581 },
1582 _ = shutdown_receiver.changed() => {
1583 debug!("Backup watcher task completed");
1584 break;
1585 }
1586 }
1587 }
1588 });
1589 }
1590
1591 async fn track_swap_events(self: &Arc<BreezServices>) {
1592 let cloned = self.clone();
1593 tokio::spawn(async move {
1594 let mut swap_events_stream = cloned.btc_receive_swapper.subscribe_status_changes();
1595 let mut rev_swap_events_stream = cloned.btc_send_swapper.subscribe_status_changes();
1596 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1597 loop {
1598 tokio::select! {
1599 swap_event = swap_events_stream.recv() => {
1600 if let Ok(e) = swap_event {
1601 if let Err(err) = cloned.notify_event_listeners(e).await {
1602 error!("error handling swap event: {err:?}");
1603 }
1604 }
1605 },
1606 rev_swap_event = rev_swap_events_stream.recv() => {
1607 if let Ok(e) = rev_swap_event {
1608 if let Err(err) = cloned.notify_event_listeners(e).await {
1609 error!("error handling reverse swap event: {err:?}");
1610 }
1611 }
1612 },
1613 _ = shutdown_receiver.changed() => {
1614 debug!("Swap events handling task completed");
1615 break;
1616 }
1617 }
1618 }
1619 });
1620 }
1621
1622 async fn track_invoices(self: &Arc<BreezServices>) {
1623 let cloned = self.clone();
1624 tokio::spawn(async move {
1625 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1626 loop {
1627 if shutdown_receiver.has_changed().unwrap_or(true) {
1628 return;
1629 }
1630 let mut invoice_stream = match cloned.node_api.stream_incoming_payments().await {
1631 Ok(invoice_stream) => invoice_stream,
1632 Err(e) => {
1633 warn!("stream incoming payments returned error: {e:?}");
1634 tokio::select! {
1635 _ = sleep(Duration::from_secs(1)) => {
1636 continue
1637 }
1638 _ = shutdown_receiver.changed() => {
1639 debug!("Invoice tracking task has completed");
1640 return;
1641 }
1642 };
1643 }
1644 };
1645
1646 loop {
1647 let paid_invoice_res = tokio::select! {
1648 paid_invoice_res = invoice_stream.next() => paid_invoice_res,
1649 _ = shutdown_receiver.changed() => {
1650 debug!("Invoice tracking task has completed");
1651 return;
1652 }
1653 };
1654
1655 let p = match paid_invoice_res {
1656 Some(p) => p,
1657 None => {
1658 debug!("invoice stream got None");
1659 break;
1660 }
1661 };
1662
1663 debug!("invoice stream got new invoice");
1664 let mut payment: Option<crate::models::Payment> = p.clone().try_into().ok();
1665 if let Some(ref p) = payment {
1666 let res = cloned
1667 .persister
1668 .insert_or_update_payments(&vec![p.clone()], false);
1669 debug!("paid invoice was added to payments list {res:?}");
1670 if let Ok(Some(mut node_info)) = cloned.persister.get_node_state() {
1671 node_info.channels_balance_msat += p.amount_msat;
1672 let res = cloned.persister.set_node_state(&node_info);
1673 debug!("channel balance was updated {res:?}");
1674 }
1675 payment = cloned
1676 .persister
1677 .get_payment_by_hash(&p.id)
1678 .unwrap_or(payment);
1679 }
1680 _ = cloned
1681 .on_event(BreezEvent::InvoicePaid {
1682 details: InvoicePaidDetails {
1683 payment_hash: hex::encode(p.payment_hash),
1684 bolt11: p.bolt11,
1685 payment,
1686 },
1687 })
1688 .await;
1689 if let Err(e) = cloned.do_sync(true).await {
1690 error!("failed to sync after paid invoice: {e:?}");
1691 }
1692 }
1693
1694 tokio::select! {
1695 _ = sleep(Duration::from_secs(1)) => {
1696 continue
1697 }
1698 _ = shutdown_receiver.changed() => {
1699 debug!("Invoice tracking task has completed");
1700 return;
1701 }
1702 };
1703 }
1704 });
1705 }
1706
1707 async fn track_logs(self: &Arc<BreezServices>) {
1708 let cloned = self.clone();
1709 tokio::spawn(async move {
1710 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1711 loop {
1712 if shutdown_receiver.has_changed().unwrap_or(true) {
1713 return;
1714 }
1715 let mut log_stream = match cloned.node_api.stream_log_messages().await {
1716 Ok(log_stream) => log_stream,
1717 Err(e) => {
1718 warn!("stream log messages returned error: {e:?}");
1719 tokio::select! {
1720 _ = sleep(Duration::from_secs(1)) => {
1721 continue
1722 }
1723 _ = shutdown_receiver.changed() => {
1724 debug!("Invoice tracking task has completed");
1725 return;
1726 }
1727 };
1728 }
1729 };
1730
1731 loop {
1732 let log_message_res = tokio::select! {
1733 log_message_res = log_stream.next() => log_message_res,
1734 _ = shutdown_receiver.changed() => {
1735 debug!("Track logs task has completed");
1736 return;
1737 }
1738 };
1739
1740 match log_message_res {
1741 Some(l) => info!("node-logs: {l}"),
1742 None => {
1743 break;
1745 }
1746 };
1747 }
1748
1749 tokio::select! {
1750 _ = sleep(Duration::from_secs(1)) => {
1751 continue
1752 }
1753 _ = shutdown_receiver.changed() => {
1754 debug!("Invoice tracking task has completed");
1755 return;
1756 }
1757 };
1758 }
1759 });
1760 }
1761
1762 async fn track_new_blocks(self: &Arc<BreezServices>) {
1763 let cloned = self.clone();
1764 tokio::spawn(async move {
1765 let mut current_block: u32 = 0;
1766 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1767 let mut interval = tokio::time::interval(Duration::from_secs(30));
1768 interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
1769 loop {
1770 tokio::select! {
1771 _ = interval.tick() => { }
1772
1773 _ = shutdown_receiver.changed() => {
1774 debug!("New blocks task has completed");
1775 return;
1776 }
1777 }
1778
1779 let next_block = match cloned.chain_service.current_tip().await {
1780 Ok(next_block) => next_block,
1781 Err(e) => {
1782 error!("failed to fetch next block {e}");
1783 continue;
1784 }
1785 };
1786
1787 debug!("got tip {next_block:?}");
1788 if next_block > current_block {
1789 _ = cloned.sync().await;
1790 _ = cloned
1791 .on_event(BreezEvent::NewBlock { block: next_block })
1792 .await;
1793 }
1794 current_block = next_block
1795 }
1796 });
1797 }
1798
1799 async fn init_chainservice_urls(&self) -> Result<()> {
1800 let breez_server = Arc::new(BreezServer::new(
1801 PRODUCTION_BREEZSERVER_URL.to_string(),
1802 None,
1803 )?);
1804 let persister = &self.persister;
1805
1806 let cloned_breez_server = breez_server.clone();
1807 let cloned_persister = persister.clone();
1808 tokio::spawn(async move {
1809 match cloned_breez_server.fetch_mempoolspace_urls().await {
1810 Ok(fresh_urls) => {
1811 if let Err(e) = cloned_persister.set_mempoolspace_base_urls(fresh_urls) {
1812 error!("Failed to cache mempool.space URLs: {e}");
1813 }
1814 }
1815 Err(e) => error!("Failed to fetch mempool.space URLs: {e}"),
1816 }
1817 });
1818
1819 Ok(())
1820 }
1821
1822 pub fn init_logging(log_dir: &str, app_logger: Option<Box<dyn log::Log>>) -> Result<()> {
1851 let target_log_file = Box::new(
1852 OpenOptions::new()
1853 .create(true)
1854 .append(true)
1855 .open(format!("{log_dir}/sdk.log"))
1856 .map_err(|e| anyhow!("Can't create log file: {e}"))?,
1857 );
1858 let logger = env_logger::Builder::new()
1859 .target(env_logger::Target::Pipe(target_log_file))
1860 .parse_filters(
1861 r#"
1862 info,
1863 breez_sdk_core=debug,
1864 sdk_common=debug,
1865 gl_client=debug,
1866 h2=warn,
1867 hyper=warn,
1868 lightning_signer=warn,
1869 reqwest=warn,
1870 rustls=warn,
1871 rustyline=warn,
1872 vls_protocol_signer=warn
1873 "#,
1874 )
1875 .format(|buf, record| {
1876 writeln!(
1877 buf,
1878 "[{} {} {}:{}] {}",
1879 Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
1880 record.level(),
1881 record.module_path().unwrap_or("unknown"),
1882 record.line().unwrap_or(0),
1883 record.args()
1884 )
1885 })
1886 .build();
1887
1888 let global_logger = GlobalSdkLogger {
1889 logger,
1890 log_listener: app_logger,
1891 };
1892
1893 log::set_boxed_logger(Box::new(global_logger))
1894 .map_err(|e| anyhow!("Failed to set global logger: {e}"))?;
1895 log::set_max_level(LevelFilter::Trace);
1896
1897 Ok(())
1898 }
1899
1900 async fn lookup_chain_service_closing_outspend(
1901 &self,
1902 channel: crate::models::Channel,
1903 ) -> Result<Option<Outspend>> {
1904 match channel.funding_outnum {
1905 None => Ok(None),
1906 Some(outnum) => {
1907 let outspends = self
1909 .chain_service
1910 .transaction_outspends(channel.funding_txid.clone())
1911 .await?;
1912
1913 Ok(outspends.get(outnum as usize).cloned())
1914 }
1915 }
1916 }
1917
1918 async fn lookup_channel_closing_data(
1922 &self,
1923 channel: &crate::models::Channel,
1924 ) -> Result<(Option<u64>, Option<String>)> {
1925 let maybe_outspend_res = self
1926 .lookup_chain_service_closing_outspend(channel.clone())
1927 .await;
1928 let maybe_outspend: Option<Outspend> = match maybe_outspend_res {
1929 Ok(s) => s,
1930 Err(e) => {
1931 error!("Failed to lookup channel closing data: {e:?}");
1932 None
1933 }
1934 };
1935
1936 let maybe_closed_at = maybe_outspend
1937 .clone()
1938 .and_then(|outspend| outspend.status)
1939 .and_then(|s| s.block_time);
1940 let maybe_closing_txid = maybe_outspend.and_then(|outspend| outspend.txid);
1941
1942 Ok((maybe_closed_at, maybe_closing_txid))
1943 }
1944
1945 async fn closed_channel_to_transaction(
1946 &self,
1947 channel: crate::models::Channel,
1948 ) -> Result<Payment> {
1949 let (payment_time, closing_txid) = match (channel.closed_at, channel.closing_txid.clone()) {
1950 (Some(closed_at), Some(closing_txid)) => (closed_at as i64, Some(closing_txid)),
1951 (_, _) => {
1952 let (maybe_closed_at, maybe_closing_txid) =
1954 self.lookup_channel_closing_data(&channel).await?;
1955
1956 let processed_closed_at = match maybe_closed_at {
1957 None => {
1958 warn!("Blocktime could not be determined for from closing outspend, defaulting closed_at to epoch time");
1959 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
1960 }
1961 Some(block_time) => block_time,
1962 };
1963
1964 let mut updated_channel = channel.clone();
1965 updated_channel.closed_at = Some(processed_closed_at);
1966 updated_channel.closing_txid.clone_from(&maybe_closing_txid);
1968 self.persister.insert_or_update_channel(updated_channel)?;
1969
1970 (processed_closed_at as i64, maybe_closing_txid)
1971 }
1972 };
1973
1974 Ok(Payment {
1975 id: channel.funding_txid.clone(),
1976 payment_type: PaymentType::ClosedChannel,
1977 payment_time,
1978 amount_msat: channel.local_balance_msat,
1979 fee_msat: 0,
1980 status: match channel.state {
1981 ChannelState::PendingClose => PaymentStatus::Pending,
1982 _ => PaymentStatus::Complete,
1983 },
1984 description: Some("Closed Channel".to_string()),
1985 details: PaymentDetails::ClosedChannel {
1986 data: ClosedChannelPaymentDetails {
1987 short_channel_id: channel.short_channel_id,
1988 state: channel.state,
1989 funding_txid: channel.funding_txid,
1990 closing_txid,
1991 },
1992 },
1993 error: None,
1994 metadata: None,
1995 })
1996 }
1997
1998 pub async fn register_webhook(&self, webhook_url: String) -> SdkResult<()> {
2009 info!("Registering for webhook notifications");
2010 let is_new_webhook_url = match self.persister.get_webhook_url()? {
2011 None => true,
2012 Some(cached_webhook_url) => cached_webhook_url != webhook_url,
2013 };
2014 match is_new_webhook_url {
2015 false => debug!("Webhook URL not changed, no need to (re-)register for monitored swap tx notifications"),
2016 true => {
2017 for swap in self.btc_receive_swapper.list_swaps(ListSwapsRequest {
2018 status: Some(SwapStatus::unexpired()),
2019 ..Default::default()
2020 })?
2021 {
2022 let swap_address = &swap.bitcoin_address;
2023 info!("Found non-refundable monitored swap with address {swap_address}, registering for onchain tx notifications");
2024 self.register_onchain_tx_notification(swap_address, &webhook_url)
2025 .await?;
2026 }
2027
2028 for rev_swap in self
2029 .btc_send_swapper
2030 .list_monitored()
2031 .await?
2032 .iter()
2033 {
2034 let lockup_address = &rev_swap.get_lockup_address(self.config.network)?.to_string();
2035 info!("Found monitored reverse swap with address {lockup_address}, registering for onchain tx notifications");
2036 self.register_onchain_tx_notification(lockup_address, &webhook_url)
2037 .await?;
2038 }
2039 }
2040 }
2041
2042 self.register_payment_notifications(webhook_url.clone())
2045 .await?;
2046
2047 self.persister.set_webhook_url(webhook_url)?;
2051 Ok(())
2052 }
2053
2054 pub async fn unregister_webhook(&self, webhook_url: String) -> SdkResult<()> {
2064 info!("Unregistering for webhook notifications");
2065 self.unregister_onchain_tx_notifications(&webhook_url)
2066 .await?;
2067 self.unregister_payment_notifications(webhook_url).await?;
2068 self.persister.remove_webhook_url()?;
2069 Ok(())
2070 }
2071
2072 async fn register_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2078 let message = webhook_url.clone();
2079 let sign_request = SignMessageRequest { message };
2080 let sign_response = self.sign_message(sign_request).await?;
2081
2082 let mut error_found = false;
2084 for lsp_info in get_notification_lsps(
2085 self.persister.clone(),
2086 self.lsp_api.clone(),
2087 self.node_api.clone(),
2088 )
2089 .await?
2090 {
2091 let lsp_id = lsp_info.id;
2092 let res = self
2093 .lsp_api
2094 .register_payment_notifications(
2095 lsp_id.clone(),
2096 lsp_info.lsp_pubkey,
2097 webhook_url.clone(),
2098 sign_response.signature.clone(),
2099 )
2100 .await;
2101 if res.is_err() {
2102 error_found = true;
2103 warn!("Failed to register notifications for LSP {lsp_id}: {res:?}");
2104 }
2105 }
2106
2107 match error_found {
2108 true => Err(SdkError::generic(
2109 "Failed to register notifications for at least one LSP, see logs for details",
2110 )),
2111 false => Ok(()),
2112 }
2113 }
2114
2115 async fn unregister_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2120 let message = webhook_url.clone();
2121 let sign_request = SignMessageRequest { message };
2122 let sign_response = self.sign_message(sign_request).await?;
2123
2124 let mut error_found = false;
2126 for lsp_info in get_notification_lsps(
2127 self.persister.clone(),
2128 self.lsp_api.clone(),
2129 self.node_api.clone(),
2130 )
2131 .await?
2132 {
2133 let lsp_id = lsp_info.id;
2134 let res = self
2135 .lsp_api
2136 .unregister_payment_notifications(
2137 lsp_id.clone(),
2138 lsp_info.lsp_pubkey,
2139 webhook_url.clone(),
2140 sign_response.signature.clone(),
2141 )
2142 .await;
2143 if res.is_err() {
2144 error_found = true;
2145 warn!("Failed to un-register notifications for LSP {lsp_id}: {res:?}");
2146 }
2147 }
2148
2149 match error_found {
2150 true => Err(SdkError::generic(
2151 "Failed to un-register notifications for at least one LSP, see logs for details",
2152 )),
2153 false => Ok(()),
2154 }
2155 }
2156
2157 async fn register_onchain_tx_notification(
2160 &self,
2161 address: &str,
2162 webhook_url: &str,
2163 ) -> SdkResult<()> {
2164 let url = format!("{}/api/v1/register", self.config.chainnotifier_url);
2165 let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2166 let body = json!({
2167 "address": address,
2168 "webhook": webhook_url
2169 })
2170 .to_string();
2171 self.rest_client
2172 .post(&url, Some(headers), Some(body))
2173 .await
2174 .map(|_| ())
2175 .map_err(|e| SdkError::ServiceConnectivity {
2176 err: format!("Failed to register for tx confirmation notifications: {e}"),
2177 })
2178 }
2179
2180 async fn unregister_onchain_tx_notifications(&self, webhook_url: &str) -> SdkResult<()> {
2182 let url = format!("{}/api/v1/unregister", self.config.chainnotifier_url);
2183 let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2184 let body = json!({
2185 "webhook": webhook_url
2186 })
2187 .to_string();
2188 self.rest_client
2189 .post(&url, Some(headers), Some(body))
2190 .await
2191 .map(|_| ())
2192 .map_err(|e| SdkError::ServiceConnectivity {
2193 err: format!("Failed to unregister for tx confirmation notifications: {e}"),
2194 })
2195 }
2196
2197 async fn generate_sdk_diagnostic_data(&self) -> SdkResult<Value> {
2198 let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
2199 let version = format!("SDK v{sdk_version} ({sdk_git_hash})");
2200 let state = crate::serializer::value::to_value(&self.persister.get_node_state()?)?;
2201 let payments = crate::serializer::value::to_value(
2202 &self
2203 .persister
2204 .list_payments(ListPaymentsRequest::default())?,
2205 )?;
2206 let channels = crate::serializer::value::to_value(&self.persister.list_channels()?)?;
2207 let settings = crate::serializer::value::to_value(&self.persister.list_settings()?)?;
2208 let reverse_swaps = crate::serializer::value::to_value(
2209 self.persister.list_reverse_swaps().map(sanitize_vec)?,
2210 )?;
2211 let swaps = crate::serializer::value::to_value(
2212 self.btc_receive_swapper
2213 .list_swaps(ListSwapsRequest::default())
2214 .map(sanitize_vec)?,
2215 )?;
2216 let lsp_id = crate::serializer::value::to_value(&self.persister.get_lsp_id()?)?;
2217
2218 let res = json!({
2219 "version": version,
2220 "node_state": state,
2221 "payments": payments,
2222 "channels": channels,
2223 "settings": settings,
2224 "reverse_swaps": reverse_swaps,
2225 "swaps": swaps,
2226 "lsp_id": lsp_id,
2227 });
2228 Ok(res)
2229 }
2230}
2231
2232struct GlobalSdkLogger {
2233 logger: env_logger::Logger,
2235 log_listener: Option<Box<dyn log::Log>>,
2237}
2238impl log::Log for GlobalSdkLogger {
2239 fn enabled(&self, metadata: &Metadata) -> bool {
2240 metadata.level() <= log::Level::Trace
2241 }
2242
2243 fn log(&self, record: &Record) {
2244 if self.enabled(record.metadata()) {
2245 self.logger.log(record);
2246
2247 if let Some(s) = &self.log_listener.as_ref() {
2248 if s.enabled(record.metadata()) {
2249 s.log(record);
2250 }
2251 }
2252 }
2253 }
2254
2255 fn flush(&self) {}
2256}
2257
2258struct BreezServicesBuilder {
2260 config: Config,
2261 node_api: Option<Arc<dyn NodeAPI>>,
2262 backup_transport: Option<Arc<dyn BackupTransport>>,
2263 seed: Option<Vec<u8>>,
2264 lsp_api: Option<Arc<dyn LspAPI>>,
2265 fiat_api: Option<Arc<dyn FiatAPI>>,
2266 persister: Option<Arc<SqliteStorage>>,
2267 rest_client: Option<Arc<dyn RestClient>>,
2268 support_api: Option<Arc<dyn SupportAPI>>,
2269 swapper_api: Option<Arc<dyn SwapperAPI>>,
2270 taproot_swapper_api: Option<Arc<dyn TaprootSwapperAPI>>,
2271 reverse_swapper_api: Option<Arc<dyn ReverseSwapperRoutingAPI>>,
2273 reverse_swap_service_api: Option<Arc<dyn ReverseSwapServiceAPI>>,
2275 buy_bitcoin_api: Option<Arc<dyn BuyBitcoinApi>>,
2276}
2277
2278#[allow(dead_code)]
2279impl BreezServicesBuilder {
2280 pub fn new(config: Config) -> BreezServicesBuilder {
2281 BreezServicesBuilder {
2282 config,
2283 node_api: None,
2284 seed: None,
2285 lsp_api: None,
2286 fiat_api: None,
2287 persister: None,
2288 rest_client: None,
2289 support_api: None,
2290 swapper_api: None,
2291 taproot_swapper_api: None,
2292 reverse_swapper_api: None,
2293 reverse_swap_service_api: None,
2294 buy_bitcoin_api: None,
2295 backup_transport: None,
2296 }
2297 }
2298
2299 pub fn node_api(&mut self, node_api: Arc<dyn NodeAPI>) -> &mut Self {
2300 self.node_api = Some(node_api);
2301 self
2302 }
2303
2304 pub fn lsp_api(&mut self, lsp_api: Arc<dyn LspAPI>) -> &mut Self {
2305 self.lsp_api = Some(lsp_api.clone());
2306 self
2307 }
2308
2309 pub fn fiat_api(&mut self, fiat_api: Arc<dyn FiatAPI>) -> &mut Self {
2310 self.fiat_api = Some(fiat_api.clone());
2311 self
2312 }
2313
2314 pub fn buy_bitcoin_api(&mut self, buy_bitcoin_api: Arc<dyn BuyBitcoinApi>) -> &mut Self {
2315 self.buy_bitcoin_api = Some(buy_bitcoin_api.clone());
2316 self
2317 }
2318
2319 pub fn persister(&mut self, persister: Arc<SqliteStorage>) -> &mut Self {
2320 self.persister = Some(persister);
2321 self
2322 }
2323
2324 pub fn support_api(&mut self, support_api: Arc<dyn SupportAPI>) -> &mut Self {
2325 self.support_api = Some(support_api.clone());
2326 self
2327 }
2328
2329 pub fn rest_client(&mut self, rest_client: Arc<dyn RestClient>) -> &mut Self {
2330 self.rest_client = Some(rest_client.clone());
2331 self
2332 }
2333
2334 pub fn swapper_api(&mut self, swapper_api: Arc<dyn SwapperAPI>) -> &mut Self {
2335 self.swapper_api = Some(swapper_api.clone());
2336 self
2337 }
2338
2339 pub fn taproot_swapper_api(&mut self, swapper_api: Arc<dyn TaprootSwapperAPI>) -> &mut Self {
2340 self.taproot_swapper_api = Some(swapper_api.clone());
2341 self
2342 }
2343
2344 pub fn reverse_swapper_api(
2345 &mut self,
2346 reverse_swapper_api: Arc<dyn ReverseSwapperRoutingAPI>,
2347 ) -> &mut Self {
2348 self.reverse_swapper_api = Some(reverse_swapper_api.clone());
2349 self
2350 }
2351
2352 pub fn reverse_swap_service_api(
2353 &mut self,
2354 reverse_swap_service_api: Arc<dyn ReverseSwapServiceAPI>,
2355 ) -> &mut Self {
2356 self.reverse_swap_service_api = Some(reverse_swap_service_api.clone());
2357 self
2358 }
2359
2360 pub fn backup_transport(&mut self, backup_transport: Arc<dyn BackupTransport>) -> &mut Self {
2361 self.backup_transport = Some(backup_transport.clone());
2362 self
2363 }
2364
2365 pub fn seed(&mut self, seed: Vec<u8>) -> &mut Self {
2366 self.seed = Some(seed);
2367 self
2368 }
2369
2370 pub async fn build(
2371 &self,
2372 restore_only: Option<bool>,
2373 event_listener: Option<Box<dyn EventListener>>,
2374 ) -> BreezServicesResult<Arc<BreezServices>> {
2375 if self.node_api.is_none() && self.seed.is_none() {
2376 return Err(ConnectError::Generic {
2377 err: "Either node_api or both credentials and seed should be provided".into(),
2378 });
2379 }
2380
2381 let persister = self
2383 .persister
2384 .clone()
2385 .unwrap_or_else(|| Arc::new(SqliteStorage::new(self.config.working_dir.clone())));
2386 persister.init()?;
2387
2388 let mut node_api = self.node_api.clone();
2389 let mut backup_transport = self.backup_transport.clone();
2390 if node_api.is_none() {
2391 let (node_impl, backup_transport_impl) = node_builder::build_node(
2392 self.config.clone(),
2393 self.seed.clone().unwrap(),
2394 restore_only,
2395 persister.clone(),
2396 )
2397 .await?;
2398 node_api = Some(node_impl);
2399 if backup_transport.is_none() {
2400 backup_transport = Some(backup_transport_impl);
2401 }
2402 }
2403
2404 if backup_transport.is_none() {
2405 return Err(ConnectError::Generic {
2406 err: "State synchronizer should be provided".into(),
2407 });
2408 }
2409
2410 let unwrapped_node_api = node_api.unwrap();
2411 let unwrapped_backup_transport = backup_transport.unwrap();
2412
2413 let backup_encryption_key = unwrapped_node_api
2415 .derive_bip32_key(vec![
2416 ChildNumber::from_hardened_idx(139)?,
2417 ChildNumber::from(0),
2418 ])
2419 .await?;
2420
2421 let legacy_backup_encryption_key = unwrapped_node_api
2424 .legacy_derive_bip32_key(vec![
2425 ChildNumber::from_hardened_idx(139)?,
2426 ChildNumber::from(0),
2427 ])
2428 .await?;
2429 let backup_watcher = BackupWatcher::new(
2430 self.config.clone(),
2431 unwrapped_backup_transport.clone(),
2432 persister.clone(),
2433 backup_encryption_key.to_priv().to_bytes(),
2434 legacy_backup_encryption_key.to_priv().to_bytes(),
2435 );
2436
2437 let breez_server = Arc::new(
2439 BreezServer::new(self.config.breezserver.clone(), self.config.api_key.clone())
2440 .map_err(|e| ConnectError::ServiceConnectivity {
2441 err: format!("Failed to create BreezServer: {e}"),
2442 })?,
2443 );
2444
2445 let cloned_breez_server = breez_server.clone();
2447 tokio::spawn(async move {
2448 if let Err(e) = cloned_breez_server.ping().await {
2449 error!("Failed to ping breez server: {e}");
2450 }
2451 });
2452
2453 let current_lsp_id = persister.get_lsp_id()?;
2454 if current_lsp_id.is_none() && self.config.default_lsp_id.is_some() {
2455 persister.set_lsp(self.config.default_lsp_id.clone().unwrap(), None)?;
2456 }
2457
2458 let payment_receiver = Arc::new(PaymentReceiver {
2459 config: self.config.clone(),
2460 node_api: unwrapped_node_api.clone(),
2461 lsp: breez_server.clone(),
2462 persister: persister.clone(),
2463 });
2464
2465 let rest_client: Arc<dyn RestClient> = match self.rest_client.clone() {
2466 Some(rest_client) => rest_client,
2467 None => Arc::new(ReqwestRestClient::new()?),
2468 };
2469
2470 let mempoolspace_urls = match self.config.mempoolspace_url.clone() {
2472 None => {
2473 let cached = persister.get_mempoolspace_base_urls()?;
2474 match cached.len() {
2475 0 => {
2477 let fresh_urls = breez_server
2478 .fetch_mempoolspace_urls()
2479 .await
2480 .unwrap_or(vec![DEFAULT_MEMPOOL_SPACE_URL.into()]);
2481 persister.set_mempoolspace_base_urls(fresh_urls.clone())?;
2482 fresh_urls
2483 }
2484 _ => cached,
2486 }
2487 }
2488 Some(mempoolspace_url_from_config) => vec![mempoolspace_url_from_config],
2489 };
2490 let chain_service = Arc::new(RedundantChainService::from_base_urls(
2491 rest_client.clone(),
2492 mempoolspace_urls,
2493 ));
2494
2495 let btc_receive_swapper = Arc::new(BTCReceiveSwap::new(BTCReceiveSwapParameters {
2496 chain_service: chain_service.clone(),
2497 payment_storage: persister.clone(),
2498 network: self.config.network.into(),
2499 node_api: unwrapped_node_api.clone(),
2500 node_state_storage: persister.clone(),
2501 payment_receiver: payment_receiver.clone(),
2502 segwit_swapper_api: self
2503 .swapper_api
2504 .clone()
2505 .unwrap_or_else(|| breez_server.clone()),
2506 swap_storage: persister.clone(),
2507 taproot_swapper_api: self
2508 .taproot_swapper_api
2509 .clone()
2510 .unwrap_or_else(|| breez_server.clone()),
2511 }));
2512
2513 let btc_send_swapper = Arc::new(BTCSendSwap::new(
2514 self.config.clone(),
2515 self.reverse_swapper_api
2516 .clone()
2517 .unwrap_or_else(|| breez_server.clone()),
2518 self.reverse_swap_service_api
2519 .clone()
2520 .unwrap_or_else(|| Arc::new(BoltzApi::new(rest_client.clone()))),
2521 persister.clone(),
2522 chain_service.clone(),
2523 unwrapped_node_api.clone(),
2524 ));
2525
2526 let (shutdown_sender, _shutdown_receiver) = watch::channel::<()>(());
2528
2529 let buy_bitcoin_api = self
2530 .buy_bitcoin_api
2531 .clone()
2532 .unwrap_or_else(|| Arc::new(BuyBitcoinService::new(breez_server.clone())));
2533
2534 let breez_services = Arc::new(BreezServices {
2536 config: self.config.clone(),
2537 started: Mutex::new(false),
2538 node_api: unwrapped_node_api.clone(),
2539 lsp_api: self.lsp_api.clone().unwrap_or_else(|| breez_server.clone()),
2540 fiat_api: self
2541 .fiat_api
2542 .clone()
2543 .unwrap_or_else(|| breez_server.clone()),
2544 support_api: self
2545 .support_api
2546 .clone()
2547 .unwrap_or_else(|| breez_server.clone()),
2548 buy_bitcoin_api,
2549 chain_service,
2550 persister: persister.clone(),
2551 rest_client,
2552 btc_receive_swapper,
2553 btc_send_swapper,
2554 payment_receiver,
2555 event_listener,
2556 backup_watcher: Arc::new(backup_watcher),
2557 shutdown_sender,
2558 });
2559
2560 Ok(breez_services)
2561 }
2562}
2563
2564pub fn mnemonic_to_seed(phrase: String) -> Result<Vec<u8>> {
2568 let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
2569 let seed = Seed::new(&mnemonic, "");
2570 Ok(seed.as_bytes().to_vec())
2571}
2572
2573pub struct OpenChannelParams {
2574 pub payer_amount_msat: u64,
2575 pub opening_fee_params: models::OpeningFeeParams,
2576}
2577
2578#[tonic::async_trait]
2579pub trait Receiver: Send + Sync {
2580 fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError>;
2581 async fn receive_payment(
2582 &self,
2583 req: ReceivePaymentRequest,
2584 ) -> Result<ReceivePaymentResponse, ReceivePaymentError>;
2585 async fn wrap_node_invoice(
2586 &self,
2587 invoice: &str,
2588 params: Option<OpenChannelParams>,
2589 lsp_info: Option<LspInformation>,
2590 ) -> Result<String, ReceivePaymentError>;
2591}
2592
2593pub(crate) struct PaymentReceiver {
2594 config: Config,
2595 node_api: Arc<dyn NodeAPI>,
2596 lsp: Arc<dyn LspAPI>,
2597 persister: Arc<SqliteStorage>,
2598}
2599
2600#[tonic::async_trait]
2601impl Receiver for PaymentReceiver {
2602 fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError> {
2603 let node_state = self
2604 .persister
2605 .get_node_state()?
2606 .ok_or(ReceivePaymentError::Generic {
2607 err: "Node info not found".into(),
2608 })?;
2609 Ok(node_state.max_receivable_single_payment_amount_msat < amount_msat)
2610 }
2611
2612 async fn receive_payment(
2613 &self,
2614 req: ReceivePaymentRequest,
2615 ) -> Result<ReceivePaymentResponse, ReceivePaymentError> {
2616 let lsp_info = get_lsp(self.persister.clone(), self.lsp.clone()).await?;
2617 let expiry = req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS);
2618
2619 ensure_sdk!(
2620 req.amount_msat > 0,
2621 ReceivePaymentError::InvalidAmount {
2622 err: "Receive amount must be more than 0".into()
2623 }
2624 );
2625
2626 let mut destination_invoice_amount_msat = req.amount_msat;
2627 let mut channel_opening_fee_params = None;
2628 let mut channel_fees_msat = None;
2629
2630 let open_channel_needed = self.open_channel_needed(req.amount_msat)?;
2632 if open_channel_needed {
2633 info!("We need to open a channel");
2634
2635 let ofp = match req.opening_fee_params {
2637 Some(fee_params) => fee_params,
2638 None => lsp_info.cheapest_open_channel_fee(expiry)?.clone(),
2639 };
2640
2641 channel_opening_fee_params = Some(ofp.clone());
2642 channel_fees_msat = Some(ofp.get_channel_fees_msat_for(req.amount_msat));
2643 if let Some(channel_fees_msat) = channel_fees_msat {
2644 info!("zero-conf fee calculation option: lsp fee rate (proportional): {}: (minimum {}), total fees for channel: {}",
2645 ofp.proportional, ofp.min_msat, channel_fees_msat);
2646
2647 if req.amount_msat < channel_fees_msat + 1000 {
2648 return Err(
2649 ReceivePaymentError::InvalidAmount{err: format!(
2650 "Amount should be more than the minimum fees {channel_fees_msat} msat, but is {} msat",
2651 req.amount_msat
2652 )}
2653 );
2654 }
2655 destination_invoice_amount_msat = req.amount_msat - channel_fees_msat;
2657 }
2658 }
2659
2660 info!("Creating invoice on NodeAPI");
2661 let invoice = self
2662 .node_api
2663 .create_invoice(CreateInvoiceRequest {
2664 amount_msat: destination_invoice_amount_msat,
2665 description: req.description,
2666 payer_amount_msat: match open_channel_needed {
2667 true => Some(req.amount_msat),
2668 false => None,
2669 },
2670 preimage: req.preimage,
2671 use_description_hash: req.use_description_hash,
2672 expiry: Some(expiry),
2673 cltv: Some(req.cltv.unwrap_or(144)),
2674 })
2675 .await?;
2676 info!("Invoice created {invoice}");
2677
2678 let open_channel_params = match open_channel_needed {
2679 true => Some(OpenChannelParams {
2680 payer_amount_msat: req.amount_msat,
2681 opening_fee_params: channel_opening_fee_params.clone().ok_or(
2682 ReceivePaymentError::Generic {
2683 err: "We need to open a channel, but no channel opening fee params found"
2684 .into(),
2685 },
2686 )?,
2687 }),
2688 false => None,
2689 };
2690
2691 let invoice = self
2692 .wrap_node_invoice(&invoice, open_channel_params, Some(lsp_info))
2693 .await?;
2694 let parsed_invoice = parse_invoice(&invoice)?;
2695
2696 Ok(ReceivePaymentResponse {
2698 ln_invoice: parsed_invoice,
2699 opening_fee_params: channel_opening_fee_params,
2700 opening_fee_msat: channel_fees_msat,
2701 })
2702 }
2703
2704 async fn wrap_node_invoice(
2705 &self,
2706 invoice: &str,
2707 params: Option<OpenChannelParams>,
2708 lsp_info: Option<LspInformation>,
2709 ) -> Result<String, ReceivePaymentError> {
2710 let lsp_info = match lsp_info {
2711 Some(lsp_info) => lsp_info,
2712 None => get_lsp(self.persister.clone(), self.lsp.clone()).await?,
2713 };
2714
2715 match params {
2716 Some(params) => {
2717 self.wrap_open_channel_invoice(invoice, params, &lsp_info)
2718 .await
2719 }
2720 None => self.ensure_hint(invoice, &lsp_info).await,
2721 }
2722 }
2723}
2724
2725impl PaymentReceiver {
2726 async fn ensure_hint(
2727 &self,
2728 invoice: &str,
2729 lsp_info: &LspInformation,
2730 ) -> Result<String, ReceivePaymentError> {
2731 info!("Getting routing hints from node");
2732 let (mut hints, has_public_channel) = self.node_api.get_routing_hints(lsp_info).await?;
2733 if !has_public_channel && hints.is_empty() {
2734 return Err(ReceivePaymentError::InvoiceNoRoutingHints {
2735 err: "Must have at least one active channel".into(),
2736 });
2737 }
2738
2739 let parsed_invoice = parse_invoice(invoice)?;
2740
2741 info!("Existing routing hints {:?}", parsed_invoice.routing_hints);
2743
2744 if let Some(lsp_hint) = Self::limit_and_extract_lsp_hint(&mut hints, lsp_info) {
2746 if parsed_invoice.contains_hint_for_node(lsp_info.pubkey.as_str()) {
2747 return Ok(String::from(invoice));
2748 }
2749
2750 info!("Adding lsp hint: {lsp_hint:?}");
2751 let modified =
2752 add_routing_hints(invoice, true, &vec![lsp_hint], parsed_invoice.amount_msat)?;
2753
2754 let invoice = self.node_api.sign_invoice(modified).await?;
2755 info!("Signed invoice with hint = {invoice}");
2756 return Ok(invoice);
2757 }
2758
2759 if parsed_invoice.routing_hints.is_empty() {
2760 info!("Adding custom hints: {hints:?}");
2761 let modified = add_routing_hints(invoice, false, &hints, parsed_invoice.amount_msat)?;
2762 let invoice = self.node_api.sign_invoice(modified).await?;
2763 info!("Signed invoice with hints = {invoice}");
2764 return Ok(invoice);
2765 }
2766
2767 Ok(String::from(invoice))
2768 }
2769
2770 async fn wrap_open_channel_invoice(
2771 &self,
2772 invoice: &str,
2773 params: OpenChannelParams,
2774 lsp_info: &LspInformation,
2775 ) -> Result<String, ReceivePaymentError> {
2776 let parsed_invoice = parse_invoice(invoice)?;
2777 let open_channel_hint = RouteHint {
2778 hops: vec![RouteHintHop {
2779 src_node_id: lsp_info.pubkey.clone(),
2780 short_channel_id: "1x0x0".to_string(),
2781 fees_base_msat: lsp_info.base_fee_msat as u32,
2782 fees_proportional_millionths: (lsp_info.fee_rate * 1000000.0) as u32,
2783 cltv_expiry_delta: lsp_info.time_lock_delta as u64,
2784 htlc_minimum_msat: Some(lsp_info.min_htlc_msat as u64),
2785 htlc_maximum_msat: None,
2786 }],
2787 };
2788 info!("Adding open channel hint: {open_channel_hint:?}");
2789 let invoice_with_hint = add_routing_hints(
2790 invoice,
2791 false,
2792 &vec![open_channel_hint],
2793 Some(params.payer_amount_msat),
2794 )?;
2795 let signed_invoice = self.node_api.sign_invoice(invoice_with_hint).await?;
2796
2797 info!("Registering payment with LSP");
2798 let api_key = self.config.api_key.clone().unwrap_or_default();
2799 let api_key_hash = sha256::Hash::hash(api_key.as_bytes()).to_hex();
2800
2801 self.lsp
2802 .register_payment(
2803 lsp_info.id.clone(),
2804 lsp_info.lsp_pubkey.clone(),
2805 grpc::PaymentInformation {
2806 payment_hash: hex::decode(parsed_invoice.payment_hash.clone())
2807 .map_err(|e| anyhow!("Failed to decode hex payment hash: {e}"))?,
2808 payment_secret: parsed_invoice.payment_secret.clone(),
2809 destination: hex::decode(parsed_invoice.payee_pubkey.clone())
2810 .map_err(|e| anyhow!("Failed to decode hex payee pubkey: {e}"))?,
2811 incoming_amount_msat: params.payer_amount_msat as i64,
2812 outgoing_amount_msat: parsed_invoice
2813 .amount_msat
2814 .ok_or(anyhow!("Open channel invoice must have an amount"))?
2815 as i64,
2816 tag: json!({ "apiKeyHash": api_key_hash }).to_string(),
2817 opening_fee_params: Some(params.opening_fee_params.into()),
2818 },
2819 )
2820 .await?;
2821 self.persister.insert_open_channel_payment_info(
2823 &parsed_invoice.payment_hash,
2824 params.payer_amount_msat,
2825 &signed_invoice,
2826 )?;
2827
2828 Ok(signed_invoice)
2829 }
2830
2831 fn limit_and_extract_lsp_hint(
2832 routing_hints: &mut Vec<RouteHint>,
2833 lsp_info: &LspInformation,
2834 ) -> Option<RouteHint> {
2835 let mut lsp_hint: Option<RouteHint> = None;
2836 if let Some(lsp_index) = routing_hints.iter().position(|r| {
2837 r.hops
2838 .iter()
2839 .any(|h| h.src_node_id == lsp_info.pubkey.clone())
2840 }) {
2841 lsp_hint = Some(routing_hints.remove(lsp_index));
2842 }
2843 if routing_hints.len() > 3 {
2844 routing_hints.drain(3..);
2845 }
2846 lsp_hint
2847 }
2848}
2849
2850async fn get_lsp(
2852 persister: Arc<SqliteStorage>,
2853 lsp_api: Arc<dyn LspAPI>,
2854) -> SdkResult<LspInformation> {
2855 let lsp_id = persister
2856 .get_lsp_id()?
2857 .ok_or(SdkError::generic("No LSP ID found"))?;
2858
2859 get_lsp_by_id(persister, lsp_api, lsp_id.as_str())
2860 .await?
2861 .ok_or_else(|| SdkError::Generic {
2862 err: format!("No LSP found for id {lsp_id}"),
2863 })
2864}
2865
2866async fn get_lsps(
2867 persister: Arc<SqliteStorage>,
2868 lsp_api: Arc<dyn LspAPI>,
2869) -> SdkResult<Vec<LspInformation>> {
2870 let node_pubkey = persister
2871 .get_node_state()?
2872 .ok_or(SdkError::generic("Node info not found"))?
2873 .id;
2874
2875 lsp_api.list_lsps(node_pubkey).await
2876}
2877
2878async fn get_lsp_by_id(
2879 persister: Arc<SqliteStorage>,
2880 lsp_api: Arc<dyn LspAPI>,
2881 lsp_id: &str,
2882) -> SdkResult<Option<LspInformation>> {
2883 Ok(get_lsps(persister, lsp_api)
2884 .await?
2885 .into_iter()
2886 .find(|lsp| lsp.id.as_str() == lsp_id))
2887}
2888
2889async fn get_notification_lsps(
2892 persister: Arc<SqliteStorage>,
2893 lsp_api: Arc<dyn LspAPI>,
2894 node_api: Arc<dyn NodeAPI>,
2895) -> SdkResult<Vec<LspInformation>> {
2896 let node_pubkey = persister
2897 .get_node_state()?
2898 .ok_or(SdkError::generic("Node info not found"))?
2899 .id;
2900 let mut open_peers = None;
2901
2902 let mut notification_lsps = vec![];
2903 for lsp in lsp_api.list_used_lsps(node_pubkey).await? {
2904 match !lsp.opening_fee_params_list.values.is_empty() {
2905 true => {
2906 notification_lsps.push(lsp);
2909 }
2910 false => {
2911 let lsp_pubkey = hex::decode(&lsp.pubkey)
2913 .map_err(|e| anyhow!("Failed decode lsp pubkey: {e}"))?;
2914 let open_peers = match &open_peers {
2915 Some(open_peers) => open_peers,
2916 None => {
2917 open_peers = Some(node_api.get_open_peers().await?);
2918 open_peers.as_ref().unwrap()
2919 }
2920 };
2921 let has_active_channel_to_lsp = open_peers.contains(&lsp_pubkey);
2922 if has_active_channel_to_lsp {
2923 notification_lsps.push(lsp);
2924 }
2925 }
2926 }
2927 }
2928 Ok(notification_lsps)
2929}
2930
2931#[cfg(test)]
2932pub(crate) mod tests {
2933 use std::collections::HashMap;
2934 use std::sync::Arc;
2935
2936 use anyhow::{anyhow, Result};
2937 use regex::Regex;
2938 use reqwest::Url;
2939 use sdk_common::prelude::Rate;
2940
2941 use crate::breez_services::{BreezServices, BreezServicesBuilder};
2942 use crate::models::{LnPaymentDetails, NodeState, Payment, PaymentDetails, PaymentTypeFilter};
2943 use crate::node_api::NodeAPI;
2944 use crate::persist::cache::NodeStateStorage;
2945 use crate::persist::swap::SwapStorage;
2946 use crate::test_utils::*;
2947 use crate::*;
2948
2949 use super::{PaymentReceiver, Receiver};
2950
2951 #[tokio::test]
2952 async fn test_node_state() -> Result<()> {
2953 let dummy_node_state = get_dummy_node_state();
2957
2958 let lnurl_metadata = "{'key': 'sample-metadata-val'}";
2959 let test_ln_address = "test@ln-address.com";
2960 let test_lnurl_withdraw_endpoint = "https://test.endpoint.lnurl-w";
2961 let sa = SuccessActionProcessed::Message {
2962 data: MessageSuccessActionData {
2963 message: "test message".into(),
2964 },
2965 };
2966
2967 let payment_hash_lnurl_withdraw = "2222";
2968 let payment_hash_with_lnurl_success_action = "3333";
2969 let payment_hash_swap: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
2970 let swap_info = SwapInfo {
2971 bitcoin_address: "123".to_string(),
2972 created_at: 12345678,
2973 lock_height: 654321,
2974 payment_hash: payment_hash_swap.clone(),
2975 preimage: vec![],
2976 private_key: vec![],
2977 public_key: vec![],
2978 swapper_public_key: vec![],
2979 script: vec![],
2980 bolt11: Some("312".into()),
2981 paid_msat: 1000,
2982 confirmed_sats: 1,
2983 unconfirmed_sats: 0,
2984 total_incoming_txs: 1,
2985 status: SwapStatus::Refundable,
2986 refund_tx_ids: vec![],
2987 unconfirmed_tx_ids: vec![],
2988 confirmed_tx_ids: vec![],
2989 min_allowed_deposit: 5_000,
2990 max_allowed_deposit: 1_000_000,
2991 max_swapper_payable: 2_000_000,
2992 last_redeem_error: None,
2993 channel_opening_fees: Some(OpeningFeeParams {
2994 min_msat: 5_000_000,
2995 proportional: 50,
2996 valid_until: "date".to_string(),
2997 max_idle_time: 12345,
2998 max_client_to_self_delay: 234,
2999 promise: "promise".to_string(),
3000 }),
3001 confirmed_at: Some(555),
3002 };
3003 let payment_hash_rev_swap: Vec<u8> = vec![8, 7, 6, 5, 4, 3, 2, 1];
3004 let preimage_rev_swap: Vec<u8> = vec![6, 6, 6, 6];
3005 let full_ref_swap_info = FullReverseSwapInfo {
3006 id: "rev_swap_id".to_string(),
3007 created_at_block_height: 0,
3008 preimage: preimage_rev_swap.clone(),
3009 private_key: vec![],
3010 claim_pubkey: "claim_pubkey".to_string(),
3011 timeout_block_height: 600_000,
3012 invoice: "645".to_string(),
3013 redeem_script: "redeem_script".to_string(),
3014 onchain_amount_sat: 250,
3015 sat_per_vbyte: Some(50),
3016 receive_amount_sat: None,
3017 cache: ReverseSwapInfoCached {
3018 status: ReverseSwapStatus::CompletedConfirmed,
3019 lockup_txid: Some("lockup_txid".to_string()),
3020 claim_txid: Some("claim_txid".to_string()),
3021 },
3022 };
3023 let rev_swap_info = ReverseSwapInfo {
3024 id: "rev_swap_id".to_string(),
3025 claim_pubkey: "claim_pubkey".to_string(),
3026 lockup_txid: Some("lockup_txid".to_string()),
3027 claim_txid: Some("claim_txid".to_string()),
3028 onchain_amount_sat: 250,
3029 status: ReverseSwapStatus::CompletedConfirmed,
3030 };
3031 let dummy_transactions = vec![
3032 Payment {
3033 id: "1111".to_string(),
3034 payment_type: PaymentType::Received,
3035 payment_time: 100000,
3036 amount_msat: 10,
3037 fee_msat: 0,
3038 status: PaymentStatus::Complete,
3039 error: None,
3040 description: Some("test receive".to_string()),
3041 details: PaymentDetails::Ln {
3042 data: LnPaymentDetails {
3043 payment_hash: "1111".to_string(),
3044 label: "".to_string(),
3045 destination_pubkey: "1111".to_string(),
3046 payment_preimage: "2222".to_string(),
3047 keysend: false,
3048 bolt11: "1111".to_string(),
3049 lnurl_success_action: None,
3050 lnurl_pay_domain: None,
3051 lnurl_pay_comment: None,
3052 lnurl_metadata: None,
3053 ln_address: None,
3054 lnurl_withdraw_endpoint: None,
3055 swap_info: None,
3056 reverse_swap_info: None,
3057 pending_expiration_block: None,
3058 open_channel_bolt11: None,
3059 },
3060 },
3061 metadata: None,
3062 },
3063 Payment {
3064 id: payment_hash_lnurl_withdraw.to_string(),
3065 payment_type: PaymentType::Received,
3066 payment_time: 150000,
3067 amount_msat: 10,
3068 fee_msat: 0,
3069 status: PaymentStatus::Complete,
3070 error: None,
3071 description: Some("test lnurl-withdraw receive".to_string()),
3072 details: PaymentDetails::Ln {
3073 data: LnPaymentDetails {
3074 payment_hash: payment_hash_lnurl_withdraw.to_string(),
3075 label: "".to_string(),
3076 destination_pubkey: "1111".to_string(),
3077 payment_preimage: "3333".to_string(),
3078 keysend: false,
3079 bolt11: "1111".to_string(),
3080 lnurl_success_action: None,
3081 lnurl_pay_domain: None,
3082 lnurl_pay_comment: None,
3083 lnurl_metadata: None,
3084 ln_address: None,
3085 lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3086 swap_info: None,
3087 reverse_swap_info: None,
3088 pending_expiration_block: None,
3089 open_channel_bolt11: None,
3090 },
3091 },
3092 metadata: None,
3093 },
3094 Payment {
3095 id: payment_hash_with_lnurl_success_action.to_string(),
3096 payment_type: PaymentType::Sent,
3097 payment_time: 200000,
3098 amount_msat: 8,
3099 fee_msat: 2,
3100 status: PaymentStatus::Complete,
3101 error: None,
3102 description: Some("test payment".to_string()),
3103 details: PaymentDetails::Ln {
3104 data: LnPaymentDetails {
3105 payment_hash: payment_hash_with_lnurl_success_action.to_string(),
3106 label: "".to_string(),
3107 destination_pubkey: "123".to_string(),
3108 payment_preimage: "4444".to_string(),
3109 keysend: false,
3110 bolt11: "123".to_string(),
3111 lnurl_success_action: Some(sa.clone()),
3112 lnurl_pay_domain: None,
3113 lnurl_pay_comment: None,
3114 lnurl_metadata: Some(lnurl_metadata.to_string()),
3115 ln_address: Some(test_ln_address.to_string()),
3116 lnurl_withdraw_endpoint: None,
3117 swap_info: None,
3118 reverse_swap_info: None,
3119 pending_expiration_block: None,
3120 open_channel_bolt11: None,
3121 },
3122 },
3123 metadata: None,
3124 },
3125 Payment {
3126 id: hex::encode(payment_hash_swap.clone()),
3127 payment_type: PaymentType::Received,
3128 payment_time: 250000,
3129 amount_msat: 1_000,
3130 fee_msat: 0,
3131 status: PaymentStatus::Complete,
3132 error: None,
3133 description: Some("test receive".to_string()),
3134 details: PaymentDetails::Ln {
3135 data: LnPaymentDetails {
3136 payment_hash: hex::encode(payment_hash_swap),
3137 label: "".to_string(),
3138 destination_pubkey: "321".to_string(),
3139 payment_preimage: "5555".to_string(),
3140 keysend: false,
3141 bolt11: "312".to_string(),
3142 lnurl_success_action: None,
3143 lnurl_pay_domain: None,
3144 lnurl_pay_comment: None,
3145 lnurl_metadata: None,
3146 ln_address: None,
3147 lnurl_withdraw_endpoint: None,
3148 swap_info: Some(swap_info.clone()),
3149 reverse_swap_info: None,
3150 pending_expiration_block: None,
3151 open_channel_bolt11: None,
3152 },
3153 },
3154 metadata: None,
3155 },
3156 Payment {
3157 id: hex::encode(payment_hash_rev_swap.clone()),
3158 payment_type: PaymentType::Sent,
3159 payment_time: 300000,
3160 amount_msat: 50_000_000,
3161 fee_msat: 2_000,
3162 status: PaymentStatus::Complete,
3163 error: None,
3164 description: Some("test send onchain".to_string()),
3165 details: PaymentDetails::Ln {
3166 data: LnPaymentDetails {
3167 payment_hash: hex::encode(payment_hash_rev_swap),
3168 label: "".to_string(),
3169 destination_pubkey: "321".to_string(),
3170 payment_preimage: hex::encode(preimage_rev_swap),
3171 keysend: false,
3172 bolt11: "312".to_string(),
3173 lnurl_success_action: None,
3174 lnurl_metadata: None,
3175 lnurl_pay_domain: None,
3176 lnurl_pay_comment: None,
3177 ln_address: None,
3178 lnurl_withdraw_endpoint: None,
3179 swap_info: None,
3180 reverse_swap_info: Some(rev_swap_info.clone()),
3181 pending_expiration_block: None,
3182 open_channel_bolt11: None,
3183 },
3184 },
3185 metadata: None,
3186 },
3187 ];
3188 let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3189
3190 let test_config = create_test_config();
3191 let persister = Arc::new(create_test_persister(test_config.clone()));
3192 persister.init()?;
3193 persister.insert_or_update_payments(&dummy_transactions, false)?;
3194 persister.insert_payment_external_info(
3195 payment_hash_with_lnurl_success_action,
3196 PaymentExternalInfo {
3197 lnurl_pay_success_action: Some(sa.clone()),
3198 lnurl_pay_domain: None,
3199 lnurl_pay_comment: None,
3200 lnurl_metadata: Some(lnurl_metadata.to_string()),
3201 ln_address: Some(test_ln_address.to_string()),
3202 lnurl_withdraw_endpoint: None,
3203 attempted_amount_msat: None,
3204 attempted_error: None,
3205 },
3206 )?;
3207 persister.insert_payment_external_info(
3208 payment_hash_lnurl_withdraw,
3209 PaymentExternalInfo {
3210 lnurl_pay_success_action: None,
3211 lnurl_pay_domain: None,
3212 lnurl_pay_comment: None,
3213 lnurl_metadata: None,
3214 ln_address: None,
3215 lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3216 attempted_amount_msat: None,
3217 attempted_error: None,
3218 },
3219 )?;
3220 persister.insert_swap(&swap_info)?;
3221 persister.update_swap_bolt11(
3222 swap_info.bitcoin_address.clone(),
3223 swap_info.bolt11.clone().unwrap(),
3224 )?;
3225 persister.insert_reverse_swap(&full_ref_swap_info)?;
3226 persister
3227 .update_reverse_swap_status("rev_swap_id", &ReverseSwapStatus::CompletedConfirmed)?;
3228 persister
3229 .update_reverse_swap_lockup_txid("rev_swap_id", Some("lockup_txid".to_string()))?;
3230 persister.update_reverse_swap_claim_txid("rev_swap_id", Some("claim_txid".to_string()))?;
3231
3232 let mut builder = BreezServicesBuilder::new(test_config.clone());
3233 let breez_services = builder
3234 .lsp_api(Arc::new(MockBreezServer {}))
3235 .fiat_api(Arc::new(MockBreezServer {}))
3236 .node_api(node_api)
3237 .persister(persister)
3238 .backup_transport(Arc::new(MockBackupTransport::new()))
3239 .build(None, None)
3240 .await?;
3241
3242 breez_services.sync().await?;
3243 let fetched_state = breez_services.node_info()?;
3244 assert_eq!(fetched_state, dummy_node_state);
3245
3246 let all = breez_services
3247 .list_payments(ListPaymentsRequest::default())
3248 .await?;
3249 let mut cloned = all.clone();
3250
3251 cloned.reverse();
3253 assert_eq!(dummy_transactions, cloned);
3254
3255 let received = breez_services
3256 .list_payments(ListPaymentsRequest {
3257 filters: Some(vec![PaymentTypeFilter::Received]),
3258 ..Default::default()
3259 })
3260 .await?;
3261 assert_eq!(
3262 received,
3263 vec![cloned[3].clone(), cloned[1].clone(), cloned[0].clone()]
3264 );
3265
3266 let sent = breez_services
3267 .list_payments(ListPaymentsRequest {
3268 filters: Some(vec![
3269 PaymentTypeFilter::Sent,
3270 PaymentTypeFilter::ClosedChannel,
3271 ]),
3272 ..Default::default()
3273 })
3274 .await?;
3275 assert_eq!(sent, vec![cloned[4].clone(), cloned[2].clone()]);
3276 assert!(matches!(
3277 &sent[1].details,
3278 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_success_action, ..}}
3279 if lnurl_success_action == &Some(sa)));
3280 assert!(matches!(
3281 &sent[1].details,
3282 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_pay_domain, ln_address, ..}}
3283 if lnurl_pay_domain.is_none() && ln_address == &Some(test_ln_address.to_string())));
3284 assert!(matches!(
3285 &received[1].details,
3286 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_withdraw_endpoint, ..}}
3287 if lnurl_withdraw_endpoint == &Some(test_lnurl_withdraw_endpoint.to_string())));
3288 assert!(matches!(
3289 &received[0].details,
3290 PaymentDetails::Ln {data: LnPaymentDetails {swap_info: swap, ..}}
3291 if swap == &Some(swap_info)));
3292 assert!(matches!(
3293 &sent[0].details,
3294 PaymentDetails::Ln {data: LnPaymentDetails {reverse_swap_info: rev_swap, ..}}
3295 if rev_swap == &Some(rev_swap_info)));
3296
3297 Ok(())
3298 }
3299
3300 #[tokio::test]
3301 async fn test_receive_with_open_channel() -> Result<()> {
3302 let config = create_test_config();
3303 let persister = Arc::new(create_test_persister(config.clone()));
3304 persister.init().unwrap();
3305
3306 let dummy_node_state = get_dummy_node_state();
3307
3308 let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3309
3310 let breez_server = Arc::new(MockBreezServer {});
3311 persister.set_lsp(breez_server.lsp_id(), None).unwrap();
3312 persister.set_node_state(&dummy_node_state).unwrap();
3313
3314 let receiver: Arc<dyn Receiver> = Arc::new(PaymentReceiver {
3315 config,
3316 node_api,
3317 persister,
3318 lsp: breez_server.clone(),
3319 });
3320 let ln_invoice = receiver
3321 .receive_payment(ReceivePaymentRequest {
3322 amount_msat: 3_000_000,
3323 description: "should populate lsp hints".to_string(),
3324 use_description_hash: Some(false),
3325 ..Default::default()
3326 })
3327 .await?
3328 .ln_invoice;
3329 assert_eq!(ln_invoice.routing_hints[0].hops.len(), 1);
3330 let lsp_hop = &ln_invoice.routing_hints[0].hops[0];
3331 assert_eq!(lsp_hop.src_node_id, breez_server.clone().lsp_pub_key());
3332 assert_eq!(lsp_hop.short_channel_id, "1x0x0");
3333 Ok(())
3334 }
3335
3336 #[tokio::test]
3337 async fn test_list_lsps() -> Result<()> {
3338 let storage_path = format!("{}/storage.sql", get_test_working_dir());
3339 std::fs::remove_file(storage_path).ok();
3340
3341 let breez_services = breez_services()
3342 .await
3343 .map_err(|e| anyhow!("Failed to get the BreezServices: {e}"))?;
3344 breez_services.sync().await?;
3345
3346 let node_pubkey = breez_services.node_info()?.id;
3347 let lsps = breez_services.lsp_api.list_lsps(node_pubkey).await?;
3348 assert_eq!(lsps.len(), 1);
3349
3350 Ok(())
3351 }
3352
3353 #[tokio::test]
3354 async fn test_fetch_rates() -> Result<(), Box<dyn std::error::Error>> {
3355 let breez_services = breez_services().await?;
3356 breez_services.sync().await?;
3357
3358 let rates = breez_services.fiat_api.fetch_fiat_rates().await?;
3359 assert_eq!(rates.len(), 1);
3360 assert_eq!(
3361 rates[0],
3362 Rate {
3363 coin: "USD".to_string(),
3364 value: 20_000.00,
3365 }
3366 );
3367
3368 Ok(())
3369 }
3370
3371 #[tokio::test]
3372 async fn test_buy_bitcoin_with_moonpay() -> Result<(), Box<dyn std::error::Error>> {
3373 let mock_rest_client = MockRestClient::new();
3374 mock_rest_client.add_response(MockResponse::new(200, "800000".to_string()));
3375 let rest_client: Arc<dyn RestClient> = Arc::new(mock_rest_client);
3376
3377 let breez_services = breez_services_with(None, Some(rest_client.clone()), vec![]).await?;
3378 breez_services.sync().await?;
3379
3380 let moonpay_url = breez_services
3381 .buy_bitcoin(BuyBitcoinRequest {
3382 provider: BuyBitcoinProvider::Moonpay,
3383 opening_fee_params: None,
3384 redirect_url: None,
3385 })
3386 .await?
3387 .url;
3388 let parsed = Url::parse(&moonpay_url)?;
3389 let query_pairs = parsed.query_pairs().into_owned().collect::<HashMap<_, _>>();
3390
3391 assert_eq!(parsed.host_str(), Some("mock.moonpay"));
3392 assert_eq!(parsed.path(), "/");
3393
3394 let wallet_address =
3395 parse_with_rest_client(rest_client.as_ref(), query_pairs.get("wa").unwrap(), None)
3396 .await?;
3397 assert!(matches!(wallet_address, InputType::BitcoinAddress { .. }));
3398
3399 let max_amount = query_pairs.get("ma").unwrap();
3400 assert!(Regex::new(r"^\d+\.\d{8}$").unwrap().is_match(max_amount));
3401
3402 Ok(())
3403 }
3404
3405 pub(crate) async fn breez_services() -> Result<Arc<BreezServices>> {
3407 breez_services_with(None, None, vec![]).await
3408 }
3409
3410 pub(crate) async fn breez_services_with(
3412 node_api: Option<Arc<dyn NodeAPI>>,
3413 rest_client: Option<Arc<dyn RestClient>>,
3414 known_payments: Vec<Payment>,
3415 ) -> Result<Arc<BreezServices>> {
3416 let node_api =
3417 node_api.unwrap_or_else(|| Arc::new(MockNodeAPI::new(get_dummy_node_state())));
3418 let rest_client: Arc<dyn RestClient> =
3419 rest_client.unwrap_or_else(|| Arc::new(MockRestClient::new()));
3420
3421 let test_config = create_test_config();
3422 let persister = Arc::new(create_test_persister(test_config.clone()));
3423 persister.init()?;
3424 persister.insert_or_update_payments(&known_payments, false)?;
3425 persister.set_lsp(MockBreezServer {}.lsp_id(), None)?;
3426
3427 let mut builder = BreezServicesBuilder::new(test_config.clone());
3428 let breez_services = builder
3429 .lsp_api(Arc::new(MockBreezServer {}))
3430 .fiat_api(Arc::new(MockBreezServer {}))
3431 .taproot_swapper_api(Arc::new(MockBreezServer {}))
3432 .reverse_swap_service_api(Arc::new(MockReverseSwapperAPI {}))
3433 .buy_bitcoin_api(Arc::new(MockBuyBitcoinService {}))
3434 .persister(persister)
3435 .node_api(node_api)
3436 .rest_client(rest_client)
3437 .backup_transport(Arc::new(MockBackupTransport::new()))
3438 .build(None, None)
3439 .await?;
3440
3441 Ok(breez_services)
3442 }
3443
3444 pub(crate) fn get_dummy_node_state() -> NodeState {
3446 NodeState {
3447 id: "tx1".to_string(),
3448 block_height: 1,
3449 channels_balance_msat: 100,
3450 onchain_balance_msat: 1_000,
3451 pending_onchain_balance_msat: 100,
3452 utxos: vec![],
3453 max_payable_msat: 95,
3454 max_receivable_msat: 4_000_000_000,
3455 max_single_payment_amount_msat: 1_000,
3456 max_chan_reserve_msats: 0,
3457 connected_peers: vec!["1111".to_string()],
3458 max_receivable_single_payment_amount_msat: 2_000,
3459 total_inbound_liquidity_msats: 10_000,
3460 }
3461 }
3462}