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 is_shutdown = tokio::select! {
1521 _ = node_api.start(rx) => {
1522 tokio::time::sleep(Duration::from_secs(1)).await;
1523 false
1524 }
1525
1526 _ = shutdown_receiver.changed() => {
1527 true
1528 }
1529 };
1530
1531 debug!("shutting down signer");
1532 drop(tx); if is_shutdown {
1535 return;
1536 }
1537 }
1538 });
1539 }
1540
1541 async fn start_node_keep_alive(
1542 self: &Arc<BreezServices>,
1543 shutdown_receiver: watch::Receiver<()>,
1544 ) {
1545 let cloned = self.clone();
1546 tokio::spawn(async move {
1547 cloned.node_api.start_keep_alive(shutdown_receiver).await;
1548 });
1549 }
1550
1551 async fn start_backup_watcher(self: &Arc<BreezServices>) -> Result<()> {
1552 self.backup_watcher
1553 .start(self.shutdown_sender.subscribe())
1554 .await
1555 .map_err(|e| anyhow!("Failed to start backup watcher: {e}"))?;
1556
1557 let force_backup = self
1559 .persister
1560 .get_last_sync_version()
1561 .map_err(|e| anyhow!("Failed to read last sync version: {e}"))?
1562 .is_none();
1563 self.backup_watcher
1564 .request_backup(BackupRequest::new(force_backup))
1565 .await
1566 .map_err(|e| anyhow!("Failed to request backup: {e}"))
1567 }
1568
1569 async fn track_backup_events(self: &Arc<BreezServices>) {
1570 let cloned = self.clone();
1571 tokio::spawn(async move {
1572 let mut events_stream = cloned.backup_watcher.subscribe_events();
1573 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1574 loop {
1575 tokio::select! {
1576 backup_event = events_stream.recv() => {
1577 if let Ok(e) = backup_event {
1578 if let Err(err) = cloned.notify_event_listeners(e).await {
1579 error!("error handling backup event: {err:?}");
1580 }
1581 }
1582 let backup_status = cloned.backup_status();
1583 info!("backup status: {backup_status:?}");
1584 },
1585 _ = shutdown_receiver.changed() => {
1586 debug!("Backup watcher task completed");
1587 break;
1588 }
1589 }
1590 }
1591 });
1592 }
1593
1594 async fn track_swap_events(self: &Arc<BreezServices>) {
1595 let cloned = self.clone();
1596 tokio::spawn(async move {
1597 let mut swap_events_stream = cloned.btc_receive_swapper.subscribe_status_changes();
1598 let mut rev_swap_events_stream = cloned.btc_send_swapper.subscribe_status_changes();
1599 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1600 loop {
1601 tokio::select! {
1602 swap_event = swap_events_stream.recv() => {
1603 if let Ok(e) = swap_event {
1604 if let Err(err) = cloned.notify_event_listeners(e).await {
1605 error!("error handling swap event: {err:?}");
1606 }
1607 }
1608 },
1609 rev_swap_event = rev_swap_events_stream.recv() => {
1610 if let Ok(e) = rev_swap_event {
1611 if let Err(err) = cloned.notify_event_listeners(e).await {
1612 error!("error handling reverse swap event: {err:?}");
1613 }
1614 }
1615 },
1616 _ = shutdown_receiver.changed() => {
1617 debug!("Swap events handling task completed");
1618 break;
1619 }
1620 }
1621 }
1622 });
1623 }
1624
1625 async fn track_invoices(self: &Arc<BreezServices>) {
1626 let cloned = self.clone();
1627 tokio::spawn(async move {
1628 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1629 loop {
1630 if shutdown_receiver.has_changed().unwrap_or(true) {
1631 return;
1632 }
1633 let mut invoice_stream = match cloned.node_api.stream_incoming_payments().await {
1634 Ok(invoice_stream) => invoice_stream,
1635 Err(e) => {
1636 warn!("stream incoming payments returned error: {e:?}");
1637 tokio::select! {
1638 _ = sleep(Duration::from_secs(1)) => {
1639 continue
1640 }
1641 _ = shutdown_receiver.changed() => {
1642 debug!("Invoice tracking task has completed");
1643 return;
1644 }
1645 };
1646 }
1647 };
1648
1649 loop {
1650 let paid_invoice_res = tokio::select! {
1651 paid_invoice_res = invoice_stream.next() => paid_invoice_res,
1652 _ = shutdown_receiver.changed() => {
1653 debug!("Invoice tracking task has completed");
1654 return;
1655 }
1656 };
1657
1658 let p = match paid_invoice_res {
1659 Some(p) => p,
1660 None => {
1661 debug!("invoice stream got None");
1662 break;
1663 }
1664 };
1665
1666 debug!("invoice stream got new invoice");
1667 let mut payment: Option<crate::models::Payment> = p.clone().try_into().ok();
1668 if let Some(ref p) = payment {
1669 let res = cloned
1670 .persister
1671 .insert_or_update_payments(&vec![p.clone()], false);
1672 debug!("paid invoice was added to payments list {res:?}");
1673 if let Ok(Some(mut node_info)) = cloned.persister.get_node_state() {
1674 node_info.channels_balance_msat += p.amount_msat;
1675 let res = cloned.persister.set_node_state(&node_info);
1676 debug!("channel balance was updated {res:?}");
1677 }
1678 payment = cloned
1679 .persister
1680 .get_payment_by_hash(&p.id)
1681 .unwrap_or(payment);
1682 }
1683 _ = cloned
1684 .on_event(BreezEvent::InvoicePaid {
1685 details: InvoicePaidDetails {
1686 payment_hash: hex::encode(p.payment_hash),
1687 bolt11: p.bolt11,
1688 payment,
1689 },
1690 })
1691 .await;
1692 if let Err(e) = cloned.do_sync(true).await {
1693 error!("failed to sync after paid invoice: {e:?}");
1694 }
1695 }
1696
1697 tokio::select! {
1698 _ = sleep(Duration::from_secs(1)) => {
1699 continue
1700 }
1701 _ = shutdown_receiver.changed() => {
1702 debug!("Invoice tracking task has completed");
1703 return;
1704 }
1705 };
1706 }
1707 });
1708 }
1709
1710 async fn track_logs(self: &Arc<BreezServices>) {
1711 let cloned = self.clone();
1712 tokio::spawn(async move {
1713 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1714 loop {
1715 if shutdown_receiver.has_changed().unwrap_or(true) {
1716 return;
1717 }
1718 let mut log_stream = match cloned.node_api.stream_log_messages().await {
1719 Ok(log_stream) => log_stream,
1720 Err(e) => {
1721 warn!("stream log messages returned error: {e:?}");
1722 tokio::select! {
1723 _ = sleep(Duration::from_secs(1)) => {
1724 continue
1725 }
1726 _ = shutdown_receiver.changed() => {
1727 debug!("Invoice tracking task has completed");
1728 return;
1729 }
1730 };
1731 }
1732 };
1733
1734 loop {
1735 let log_message_res = tokio::select! {
1736 log_message_res = log_stream.next() => log_message_res,
1737 _ = shutdown_receiver.changed() => {
1738 debug!("Track logs task has completed");
1739 return;
1740 }
1741 };
1742
1743 match log_message_res {
1744 Some(l) => info!("node-logs: {l}"),
1745 None => {
1746 break;
1748 }
1749 };
1750 }
1751
1752 tokio::select! {
1753 _ = sleep(Duration::from_secs(1)) => {
1754 continue
1755 }
1756 _ = shutdown_receiver.changed() => {
1757 debug!("Invoice tracking task has completed");
1758 return;
1759 }
1760 };
1761 }
1762 });
1763 }
1764
1765 async fn track_new_blocks(self: &Arc<BreezServices>) {
1766 let cloned = self.clone();
1767 tokio::spawn(async move {
1768 let mut current_block: u32 = 0;
1769 let mut shutdown_receiver = cloned.shutdown_sender.subscribe();
1770 let mut interval = tokio::time::interval(Duration::from_secs(30));
1771 interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
1772 loop {
1773 tokio::select! {
1774 _ = interval.tick() => { }
1775
1776 _ = shutdown_receiver.changed() => {
1777 debug!("New blocks task has completed");
1778 return;
1779 }
1780 }
1781
1782 let next_block = match cloned.chain_service.current_tip().await {
1783 Ok(next_block) => next_block,
1784 Err(e) => {
1785 error!("failed to fetch next block {e}");
1786 continue;
1787 }
1788 };
1789
1790 debug!("got tip {next_block:?}");
1791 if next_block > current_block {
1792 _ = cloned.sync().await;
1793 _ = cloned
1794 .on_event(BreezEvent::NewBlock { block: next_block })
1795 .await;
1796 }
1797 current_block = next_block
1798 }
1799 });
1800 }
1801
1802 async fn init_chainservice_urls(&self) -> Result<()> {
1803 let breez_server = Arc::new(BreezServer::new(
1804 PRODUCTION_BREEZSERVER_URL.to_string(),
1805 None,
1806 )?);
1807 let persister = &self.persister;
1808
1809 let cloned_breez_server = breez_server.clone();
1810 let cloned_persister = persister.clone();
1811 tokio::spawn(async move {
1812 match cloned_breez_server.fetch_mempoolspace_urls().await {
1813 Ok(fresh_urls) => {
1814 if let Err(e) = cloned_persister.set_mempoolspace_base_urls(fresh_urls) {
1815 error!("Failed to cache mempool.space URLs: {e}");
1816 }
1817 }
1818 Err(e) => error!("Failed to fetch mempool.space URLs: {e}"),
1819 }
1820 });
1821
1822 Ok(())
1823 }
1824
1825 pub fn init_logging(log_dir: &str, app_logger: Option<Box<dyn log::Log>>) -> Result<()> {
1854 let target_log_file = Box::new(
1855 OpenOptions::new()
1856 .create(true)
1857 .append(true)
1858 .open(format!("{log_dir}/sdk.log"))
1859 .map_err(|e| anyhow!("Can't create log file: {e}"))?,
1860 );
1861 let logger = env_logger::Builder::new()
1862 .target(env_logger::Target::Pipe(target_log_file))
1863 .parse_filters(
1864 r#"
1865 info,
1866 breez_sdk_core=debug,
1867 sdk_common=debug,
1868 gl_client=debug,
1869 h2=warn,
1870 hyper=warn,
1871 lightning_signer=warn,
1872 reqwest=warn,
1873 rustls=warn,
1874 rustyline=warn,
1875 vls_protocol_signer=warn
1876 "#,
1877 )
1878 .format(|buf, record| {
1879 writeln!(
1880 buf,
1881 "[{} {} {}:{}] {}",
1882 Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
1883 record.level(),
1884 record.module_path().unwrap_or("unknown"),
1885 record.line().unwrap_or(0),
1886 record.args()
1887 )
1888 })
1889 .build();
1890
1891 let global_logger = GlobalSdkLogger {
1892 logger,
1893 log_listener: app_logger,
1894 };
1895
1896 log::set_boxed_logger(Box::new(global_logger))
1897 .map_err(|e| anyhow!("Failed to set global logger: {e}"))?;
1898 log::set_max_level(LevelFilter::Trace);
1899
1900 Ok(())
1901 }
1902
1903 async fn lookup_chain_service_closing_outspend(
1904 &self,
1905 channel: crate::models::Channel,
1906 ) -> Result<Option<Outspend>> {
1907 match channel.funding_outnum {
1908 None => Ok(None),
1909 Some(outnum) => {
1910 let outspends = self
1912 .chain_service
1913 .transaction_outspends(channel.funding_txid.clone())
1914 .await?;
1915
1916 Ok(outspends.get(outnum as usize).cloned())
1917 }
1918 }
1919 }
1920
1921 async fn lookup_channel_closing_data(
1925 &self,
1926 channel: &crate::models::Channel,
1927 ) -> Result<(Option<u64>, Option<String>)> {
1928 let maybe_outspend_res = self
1929 .lookup_chain_service_closing_outspend(channel.clone())
1930 .await;
1931 let maybe_outspend: Option<Outspend> = match maybe_outspend_res {
1932 Ok(s) => s,
1933 Err(e) => {
1934 error!("Failed to lookup channel closing data: {e:?}");
1935 None
1936 }
1937 };
1938
1939 let maybe_closed_at = maybe_outspend
1940 .clone()
1941 .and_then(|outspend| outspend.status)
1942 .and_then(|s| s.block_time);
1943 let maybe_closing_txid = maybe_outspend.and_then(|outspend| outspend.txid);
1944
1945 Ok((maybe_closed_at, maybe_closing_txid))
1946 }
1947
1948 async fn closed_channel_to_transaction(
1949 &self,
1950 channel: crate::models::Channel,
1951 ) -> Result<Payment> {
1952 let (payment_time, closing_txid) = match (channel.closed_at, channel.closing_txid.clone()) {
1953 (Some(closed_at), Some(closing_txid)) => (closed_at as i64, Some(closing_txid)),
1954 (_, _) => {
1955 let (maybe_closed_at, maybe_closing_txid) =
1957 self.lookup_channel_closing_data(&channel).await?;
1958
1959 let processed_closed_at = match maybe_closed_at {
1960 None => {
1961 warn!("Blocktime could not be determined for from closing outspend, defaulting closed_at to epoch time");
1962 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
1963 }
1964 Some(block_time) => block_time,
1965 };
1966
1967 let mut updated_channel = channel.clone();
1968 updated_channel.closed_at = Some(processed_closed_at);
1969 updated_channel.closing_txid.clone_from(&maybe_closing_txid);
1971 self.persister.insert_or_update_channel(updated_channel)?;
1972
1973 (processed_closed_at as i64, maybe_closing_txid)
1974 }
1975 };
1976
1977 Ok(Payment {
1978 id: channel.funding_txid.clone(),
1979 payment_type: PaymentType::ClosedChannel,
1980 payment_time,
1981 amount_msat: channel.local_balance_msat,
1982 fee_msat: 0,
1983 status: match channel.state {
1984 ChannelState::PendingClose => PaymentStatus::Pending,
1985 _ => PaymentStatus::Complete,
1986 },
1987 description: Some("Closed Channel".to_string()),
1988 details: PaymentDetails::ClosedChannel {
1989 data: ClosedChannelPaymentDetails {
1990 short_channel_id: channel.short_channel_id,
1991 state: channel.state,
1992 funding_txid: channel.funding_txid,
1993 closing_txid,
1994 },
1995 },
1996 error: None,
1997 metadata: None,
1998 })
1999 }
2000
2001 pub async fn register_webhook(&self, webhook_url: String) -> SdkResult<()> {
2012 info!("Registering for webhook notifications");
2013 let is_new_webhook_url = match self.persister.get_webhook_url()? {
2014 None => true,
2015 Some(cached_webhook_url) => cached_webhook_url != webhook_url,
2016 };
2017 match is_new_webhook_url {
2018 false => debug!("Webhook URL not changed, no need to (re-)register for monitored swap tx notifications"),
2019 true => {
2020 for swap in self.btc_receive_swapper.list_swaps(ListSwapsRequest {
2021 status: Some(SwapStatus::unexpired()),
2022 ..Default::default()
2023 })?
2024 {
2025 let swap_address = &swap.bitcoin_address;
2026 info!("Found non-refundable monitored swap with address {swap_address}, registering for onchain tx notifications");
2027 self.register_onchain_tx_notification(swap_address, &webhook_url)
2028 .await?;
2029 }
2030
2031 for rev_swap in self
2032 .btc_send_swapper
2033 .list_monitored()
2034 .await?
2035 .iter()
2036 {
2037 let lockup_address = &rev_swap.get_lockup_address(self.config.network)?.to_string();
2038 info!("Found monitored reverse swap with address {lockup_address}, registering for onchain tx notifications");
2039 self.register_onchain_tx_notification(lockup_address, &webhook_url)
2040 .await?;
2041 }
2042 }
2043 }
2044
2045 self.register_payment_notifications(webhook_url.clone())
2048 .await?;
2049
2050 self.persister.set_webhook_url(webhook_url)?;
2054 Ok(())
2055 }
2056
2057 pub async fn unregister_webhook(&self, webhook_url: String) -> SdkResult<()> {
2067 info!("Unregistering for webhook notifications");
2068 self.unregister_onchain_tx_notifications(&webhook_url)
2069 .await?;
2070 self.unregister_payment_notifications(webhook_url).await?;
2071 self.persister.remove_webhook_url()?;
2072 Ok(())
2073 }
2074
2075 async fn register_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2081 let message = webhook_url.clone();
2082 let sign_request = SignMessageRequest { message };
2083 let sign_response = self.sign_message(sign_request).await?;
2084
2085 let mut error_found = false;
2087 for lsp_info in get_notification_lsps(
2088 self.persister.clone(),
2089 self.lsp_api.clone(),
2090 self.node_api.clone(),
2091 )
2092 .await?
2093 {
2094 let lsp_id = lsp_info.id;
2095 let res = self
2096 .lsp_api
2097 .register_payment_notifications(
2098 lsp_id.clone(),
2099 lsp_info.lsp_pubkey,
2100 webhook_url.clone(),
2101 sign_response.signature.clone(),
2102 )
2103 .await;
2104 if res.is_err() {
2105 error_found = true;
2106 warn!("Failed to register notifications for LSP {lsp_id}: {res:?}");
2107 }
2108 }
2109
2110 match error_found {
2111 true => Err(SdkError::generic(
2112 "Failed to register notifications for at least one LSP, see logs for details",
2113 )),
2114 false => Ok(()),
2115 }
2116 }
2117
2118 async fn unregister_payment_notifications(&self, webhook_url: String) -> SdkResult<()> {
2123 let message = webhook_url.clone();
2124 let sign_request = SignMessageRequest { message };
2125 let sign_response = self.sign_message(sign_request).await?;
2126
2127 let mut error_found = false;
2129 for lsp_info in get_notification_lsps(
2130 self.persister.clone(),
2131 self.lsp_api.clone(),
2132 self.node_api.clone(),
2133 )
2134 .await?
2135 {
2136 let lsp_id = lsp_info.id;
2137 let res = self
2138 .lsp_api
2139 .unregister_payment_notifications(
2140 lsp_id.clone(),
2141 lsp_info.lsp_pubkey,
2142 webhook_url.clone(),
2143 sign_response.signature.clone(),
2144 )
2145 .await;
2146 if res.is_err() {
2147 error_found = true;
2148 warn!("Failed to un-register notifications for LSP {lsp_id}: {res:?}");
2149 }
2150 }
2151
2152 match error_found {
2153 true => Err(SdkError::generic(
2154 "Failed to un-register notifications for at least one LSP, see logs for details",
2155 )),
2156 false => Ok(()),
2157 }
2158 }
2159
2160 async fn register_onchain_tx_notification(
2163 &self,
2164 address: &str,
2165 webhook_url: &str,
2166 ) -> SdkResult<()> {
2167 let url = format!("{}/api/v1/register", self.config.chainnotifier_url);
2168 let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2169 let body = json!({
2170 "address": address,
2171 "webhook": webhook_url
2172 })
2173 .to_string();
2174 self.rest_client
2175 .post(&url, Some(headers), Some(body))
2176 .await
2177 .map(|_| ())
2178 .map_err(|e| SdkError::ServiceConnectivity {
2179 err: format!("Failed to register for tx confirmation notifications: {e}"),
2180 })
2181 }
2182
2183 async fn unregister_onchain_tx_notifications(&self, webhook_url: &str) -> SdkResult<()> {
2185 let url = format!("{}/api/v1/unregister", self.config.chainnotifier_url);
2186 let headers = HashMap::from([("Content-Type".to_string(), "application/json".to_string())]);
2187 let body = json!({
2188 "webhook": webhook_url
2189 })
2190 .to_string();
2191 self.rest_client
2192 .post(&url, Some(headers), Some(body))
2193 .await
2194 .map(|_| ())
2195 .map_err(|e| SdkError::ServiceConnectivity {
2196 err: format!("Failed to unregister for tx confirmation notifications: {e}"),
2197 })
2198 }
2199
2200 async fn generate_sdk_diagnostic_data(&self) -> SdkResult<Value> {
2201 let (sdk_version, sdk_git_hash) = Self::get_sdk_version();
2202 let version = format!("SDK v{sdk_version} ({sdk_git_hash})");
2203 let state = crate::serializer::value::to_value(&self.persister.get_node_state()?)?;
2204 let payments = crate::serializer::value::to_value(
2205 &self
2206 .persister
2207 .list_payments(ListPaymentsRequest::default())?,
2208 )?;
2209 let channels = crate::serializer::value::to_value(&self.persister.list_channels()?)?;
2210 let settings = crate::serializer::value::to_value(&self.persister.list_settings()?)?;
2211 let reverse_swaps = crate::serializer::value::to_value(
2212 self.persister.list_reverse_swaps().map(sanitize_vec)?,
2213 )?;
2214 let swaps = crate::serializer::value::to_value(
2215 self.btc_receive_swapper
2216 .list_swaps(ListSwapsRequest::default())
2217 .map(sanitize_vec)?,
2218 )?;
2219 let lsp_id = crate::serializer::value::to_value(&self.persister.get_lsp_id()?)?;
2220
2221 let res = json!({
2222 "version": version,
2223 "node_state": state,
2224 "payments": payments,
2225 "channels": channels,
2226 "settings": settings,
2227 "reverse_swaps": reverse_swaps,
2228 "swaps": swaps,
2229 "lsp_id": lsp_id,
2230 });
2231 Ok(res)
2232 }
2233}
2234
2235struct GlobalSdkLogger {
2236 logger: env_logger::Logger,
2238 log_listener: Option<Box<dyn log::Log>>,
2240}
2241impl log::Log for GlobalSdkLogger {
2242 fn enabled(&self, metadata: &Metadata) -> bool {
2243 metadata.level() <= log::Level::Trace
2244 }
2245
2246 fn log(&self, record: &Record) {
2247 if self.enabled(record.metadata()) {
2248 self.logger.log(record);
2249
2250 if let Some(s) = &self.log_listener.as_ref() {
2251 if s.enabled(record.metadata()) {
2252 s.log(record);
2253 }
2254 }
2255 }
2256 }
2257
2258 fn flush(&self) {}
2259}
2260
2261struct BreezServicesBuilder {
2263 config: Config,
2264 node_api: Option<Arc<dyn NodeAPI>>,
2265 backup_transport: Option<Arc<dyn BackupTransport>>,
2266 seed: Option<Vec<u8>>,
2267 lsp_api: Option<Arc<dyn LspAPI>>,
2268 fiat_api: Option<Arc<dyn FiatAPI>>,
2269 persister: Option<Arc<SqliteStorage>>,
2270 rest_client: Option<Arc<dyn RestClient>>,
2271 support_api: Option<Arc<dyn SupportAPI>>,
2272 swapper_api: Option<Arc<dyn SwapperAPI>>,
2273 taproot_swapper_api: Option<Arc<dyn TaprootSwapperAPI>>,
2274 reverse_swapper_api: Option<Arc<dyn ReverseSwapperRoutingAPI>>,
2276 reverse_swap_service_api: Option<Arc<dyn ReverseSwapServiceAPI>>,
2278 buy_bitcoin_api: Option<Arc<dyn BuyBitcoinApi>>,
2279}
2280
2281#[allow(dead_code)]
2282impl BreezServicesBuilder {
2283 pub fn new(config: Config) -> BreezServicesBuilder {
2284 BreezServicesBuilder {
2285 config,
2286 node_api: None,
2287 seed: None,
2288 lsp_api: None,
2289 fiat_api: None,
2290 persister: None,
2291 rest_client: None,
2292 support_api: None,
2293 swapper_api: None,
2294 taproot_swapper_api: None,
2295 reverse_swapper_api: None,
2296 reverse_swap_service_api: None,
2297 buy_bitcoin_api: None,
2298 backup_transport: None,
2299 }
2300 }
2301
2302 pub fn node_api(&mut self, node_api: Arc<dyn NodeAPI>) -> &mut Self {
2303 self.node_api = Some(node_api);
2304 self
2305 }
2306
2307 pub fn lsp_api(&mut self, lsp_api: Arc<dyn LspAPI>) -> &mut Self {
2308 self.lsp_api = Some(lsp_api.clone());
2309 self
2310 }
2311
2312 pub fn fiat_api(&mut self, fiat_api: Arc<dyn FiatAPI>) -> &mut Self {
2313 self.fiat_api = Some(fiat_api.clone());
2314 self
2315 }
2316
2317 pub fn buy_bitcoin_api(&mut self, buy_bitcoin_api: Arc<dyn BuyBitcoinApi>) -> &mut Self {
2318 self.buy_bitcoin_api = Some(buy_bitcoin_api.clone());
2319 self
2320 }
2321
2322 pub fn persister(&mut self, persister: Arc<SqliteStorage>) -> &mut Self {
2323 self.persister = Some(persister);
2324 self
2325 }
2326
2327 pub fn support_api(&mut self, support_api: Arc<dyn SupportAPI>) -> &mut Self {
2328 self.support_api = Some(support_api.clone());
2329 self
2330 }
2331
2332 pub fn rest_client(&mut self, rest_client: Arc<dyn RestClient>) -> &mut Self {
2333 self.rest_client = Some(rest_client.clone());
2334 self
2335 }
2336
2337 pub fn swapper_api(&mut self, swapper_api: Arc<dyn SwapperAPI>) -> &mut Self {
2338 self.swapper_api = Some(swapper_api.clone());
2339 self
2340 }
2341
2342 pub fn taproot_swapper_api(&mut self, swapper_api: Arc<dyn TaprootSwapperAPI>) -> &mut Self {
2343 self.taproot_swapper_api = Some(swapper_api.clone());
2344 self
2345 }
2346
2347 pub fn reverse_swapper_api(
2348 &mut self,
2349 reverse_swapper_api: Arc<dyn ReverseSwapperRoutingAPI>,
2350 ) -> &mut Self {
2351 self.reverse_swapper_api = Some(reverse_swapper_api.clone());
2352 self
2353 }
2354
2355 pub fn reverse_swap_service_api(
2356 &mut self,
2357 reverse_swap_service_api: Arc<dyn ReverseSwapServiceAPI>,
2358 ) -> &mut Self {
2359 self.reverse_swap_service_api = Some(reverse_swap_service_api.clone());
2360 self
2361 }
2362
2363 pub fn backup_transport(&mut self, backup_transport: Arc<dyn BackupTransport>) -> &mut Self {
2364 self.backup_transport = Some(backup_transport.clone());
2365 self
2366 }
2367
2368 pub fn seed(&mut self, seed: Vec<u8>) -> &mut Self {
2369 self.seed = Some(seed);
2370 self
2371 }
2372
2373 pub async fn build(
2374 &self,
2375 restore_only: Option<bool>,
2376 event_listener: Option<Box<dyn EventListener>>,
2377 ) -> BreezServicesResult<Arc<BreezServices>> {
2378 if self.node_api.is_none() && self.seed.is_none() {
2379 return Err(ConnectError::Generic {
2380 err: "Either node_api or both credentials and seed should be provided".into(),
2381 });
2382 }
2383
2384 let persister = self
2386 .persister
2387 .clone()
2388 .unwrap_or_else(|| Arc::new(SqliteStorage::new(self.config.working_dir.clone())));
2389 persister.init()?;
2390
2391 let mut node_api = self.node_api.clone();
2392 let mut backup_transport = self.backup_transport.clone();
2393 if node_api.is_none() {
2394 let (node_impl, backup_transport_impl) = node_builder::build_node(
2395 self.config.clone(),
2396 self.seed.clone().unwrap(),
2397 restore_only,
2398 persister.clone(),
2399 )
2400 .await?;
2401 node_api = Some(node_impl);
2402 if backup_transport.is_none() {
2403 backup_transport = Some(backup_transport_impl);
2404 }
2405 }
2406
2407 if backup_transport.is_none() {
2408 return Err(ConnectError::Generic {
2409 err: "State synchronizer should be provided".into(),
2410 });
2411 }
2412
2413 let unwrapped_node_api = node_api.unwrap();
2414 let unwrapped_backup_transport = backup_transport.unwrap();
2415
2416 let backup_encryption_key = unwrapped_node_api
2418 .derive_bip32_key(vec![
2419 ChildNumber::from_hardened_idx(139)?,
2420 ChildNumber::from(0),
2421 ])
2422 .await?;
2423
2424 let legacy_backup_encryption_key = unwrapped_node_api
2427 .legacy_derive_bip32_key(vec![
2428 ChildNumber::from_hardened_idx(139)?,
2429 ChildNumber::from(0),
2430 ])
2431 .await?;
2432 let backup_watcher = BackupWatcher::new(
2433 self.config.clone(),
2434 unwrapped_backup_transport.clone(),
2435 persister.clone(),
2436 backup_encryption_key.to_priv().to_bytes(),
2437 legacy_backup_encryption_key.to_priv().to_bytes(),
2438 );
2439
2440 let breez_server = Arc::new(
2442 BreezServer::new(self.config.breezserver.clone(), self.config.api_key.clone())
2443 .map_err(|e| ConnectError::ServiceConnectivity {
2444 err: format!("Failed to create BreezServer: {e}"),
2445 })?,
2446 );
2447
2448 let cloned_breez_server = breez_server.clone();
2450 tokio::spawn(async move {
2451 if let Err(e) = cloned_breez_server.ping().await {
2452 error!("Failed to ping breez server: {e}");
2453 }
2454 });
2455
2456 let current_lsp_id = persister.get_lsp_id()?;
2457 if current_lsp_id.is_none() && self.config.default_lsp_id.is_some() {
2458 persister.set_lsp(self.config.default_lsp_id.clone().unwrap(), None)?;
2459 }
2460
2461 let payment_receiver = Arc::new(PaymentReceiver {
2462 config: self.config.clone(),
2463 node_api: unwrapped_node_api.clone(),
2464 lsp: breez_server.clone(),
2465 persister: persister.clone(),
2466 });
2467
2468 let rest_client: Arc<dyn RestClient> = match self.rest_client.clone() {
2469 Some(rest_client) => rest_client,
2470 None => Arc::new(ReqwestRestClient::new()?),
2471 };
2472
2473 let mempoolspace_urls = match self.config.mempoolspace_url.clone() {
2475 None => {
2476 let cached = persister.get_mempoolspace_base_urls()?;
2477 match cached.len() {
2478 0 => {
2480 let fresh_urls = breez_server
2481 .fetch_mempoolspace_urls()
2482 .await
2483 .unwrap_or(vec![DEFAULT_MEMPOOL_SPACE_URL.into()]);
2484 persister.set_mempoolspace_base_urls(fresh_urls.clone())?;
2485 fresh_urls
2486 }
2487 _ => cached,
2489 }
2490 }
2491 Some(mempoolspace_url_from_config) => vec![mempoolspace_url_from_config],
2492 };
2493 let chain_service = Arc::new(RedundantChainService::from_base_urls(
2494 rest_client.clone(),
2495 mempoolspace_urls,
2496 ));
2497
2498 let btc_receive_swapper = Arc::new(BTCReceiveSwap::new(BTCReceiveSwapParameters {
2499 chain_service: chain_service.clone(),
2500 payment_storage: persister.clone(),
2501 network: self.config.network.into(),
2502 node_api: unwrapped_node_api.clone(),
2503 node_state_storage: persister.clone(),
2504 payment_receiver: payment_receiver.clone(),
2505 segwit_swapper_api: self
2506 .swapper_api
2507 .clone()
2508 .unwrap_or_else(|| breez_server.clone()),
2509 swap_storage: persister.clone(),
2510 taproot_swapper_api: self
2511 .taproot_swapper_api
2512 .clone()
2513 .unwrap_or_else(|| breez_server.clone()),
2514 }));
2515
2516 let btc_send_swapper = Arc::new(BTCSendSwap::new(
2517 self.config.clone(),
2518 self.reverse_swapper_api
2519 .clone()
2520 .unwrap_or_else(|| breez_server.clone()),
2521 self.reverse_swap_service_api
2522 .clone()
2523 .unwrap_or_else(|| Arc::new(BoltzApi::new(rest_client.clone()))),
2524 persister.clone(),
2525 chain_service.clone(),
2526 unwrapped_node_api.clone(),
2527 ));
2528
2529 let (shutdown_sender, _shutdown_receiver) = watch::channel::<()>(());
2531
2532 let buy_bitcoin_api = self
2533 .buy_bitcoin_api
2534 .clone()
2535 .unwrap_or_else(|| Arc::new(BuyBitcoinService::new(breez_server.clone())));
2536
2537 let breez_services = Arc::new(BreezServices {
2539 config: self.config.clone(),
2540 started: Mutex::new(false),
2541 node_api: unwrapped_node_api.clone(),
2542 lsp_api: self.lsp_api.clone().unwrap_or_else(|| breez_server.clone()),
2543 fiat_api: self
2544 .fiat_api
2545 .clone()
2546 .unwrap_or_else(|| breez_server.clone()),
2547 support_api: self
2548 .support_api
2549 .clone()
2550 .unwrap_or_else(|| breez_server.clone()),
2551 buy_bitcoin_api,
2552 chain_service,
2553 persister: persister.clone(),
2554 rest_client,
2555 btc_receive_swapper,
2556 btc_send_swapper,
2557 payment_receiver,
2558 event_listener,
2559 backup_watcher: Arc::new(backup_watcher),
2560 shutdown_sender,
2561 });
2562
2563 Ok(breez_services)
2564 }
2565}
2566
2567pub fn mnemonic_to_seed(phrase: String) -> Result<Vec<u8>> {
2571 let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
2572 let seed = Seed::new(&mnemonic, "");
2573 Ok(seed.as_bytes().to_vec())
2574}
2575
2576pub struct OpenChannelParams {
2577 pub payer_amount_msat: u64,
2578 pub opening_fee_params: models::OpeningFeeParams,
2579}
2580
2581#[tonic::async_trait]
2582pub trait Receiver: Send + Sync {
2583 fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError>;
2584 async fn receive_payment(
2585 &self,
2586 req: ReceivePaymentRequest,
2587 ) -> Result<ReceivePaymentResponse, ReceivePaymentError>;
2588 async fn wrap_node_invoice(
2589 &self,
2590 invoice: &str,
2591 params: Option<OpenChannelParams>,
2592 lsp_info: Option<LspInformation>,
2593 ) -> Result<String, ReceivePaymentError>;
2594}
2595
2596pub(crate) struct PaymentReceiver {
2597 config: Config,
2598 node_api: Arc<dyn NodeAPI>,
2599 lsp: Arc<dyn LspAPI>,
2600 persister: Arc<SqliteStorage>,
2601}
2602
2603#[tonic::async_trait]
2604impl Receiver for PaymentReceiver {
2605 fn open_channel_needed(&self, amount_msat: u64) -> Result<bool, ReceivePaymentError> {
2606 let node_state = self
2607 .persister
2608 .get_node_state()?
2609 .ok_or(ReceivePaymentError::Generic {
2610 err: "Node info not found".into(),
2611 })?;
2612 Ok(node_state.max_receivable_single_payment_amount_msat < amount_msat)
2613 }
2614
2615 async fn receive_payment(
2616 &self,
2617 req: ReceivePaymentRequest,
2618 ) -> Result<ReceivePaymentResponse, ReceivePaymentError> {
2619 let lsp_info = get_lsp(self.persister.clone(), self.lsp.clone()).await?;
2620 let expiry = req.expiry.unwrap_or(INVOICE_PAYMENT_FEE_EXPIRY_SECONDS);
2621
2622 ensure_sdk!(
2623 req.amount_msat > 0,
2624 ReceivePaymentError::InvalidAmount {
2625 err: "Receive amount must be more than 0".into()
2626 }
2627 );
2628
2629 let mut destination_invoice_amount_msat = req.amount_msat;
2630 let mut channel_opening_fee_params = None;
2631 let mut channel_fees_msat = None;
2632
2633 let open_channel_needed = self.open_channel_needed(req.amount_msat)?;
2635 if open_channel_needed {
2636 info!("We need to open a channel");
2637
2638 let ofp = match req.opening_fee_params {
2640 Some(fee_params) => fee_params,
2641 None => lsp_info.cheapest_open_channel_fee(expiry)?.clone(),
2642 };
2643
2644 channel_opening_fee_params = Some(ofp.clone());
2645 channel_fees_msat = Some(ofp.get_channel_fees_msat_for(req.amount_msat));
2646 if let Some(channel_fees_msat) = channel_fees_msat {
2647 info!("zero-conf fee calculation option: lsp fee rate (proportional): {}: (minimum {}), total fees for channel: {}",
2648 ofp.proportional, ofp.min_msat, channel_fees_msat);
2649
2650 if req.amount_msat < channel_fees_msat + 1000 {
2651 return Err(
2652 ReceivePaymentError::InvalidAmount{err: format!(
2653 "Amount should be more than the minimum fees {channel_fees_msat} msat, but is {} msat",
2654 req.amount_msat
2655 )}
2656 );
2657 }
2658 destination_invoice_amount_msat = req.amount_msat - channel_fees_msat;
2660 }
2661 }
2662
2663 info!("Creating invoice on NodeAPI");
2664 let invoice = self
2665 .node_api
2666 .create_invoice(CreateInvoiceRequest {
2667 amount_msat: destination_invoice_amount_msat,
2668 description: req.description,
2669 payer_amount_msat: match open_channel_needed {
2670 true => Some(req.amount_msat),
2671 false => None,
2672 },
2673 preimage: req.preimage,
2674 use_description_hash: req.use_description_hash,
2675 expiry: Some(expiry),
2676 cltv: Some(req.cltv.unwrap_or(144)),
2677 })
2678 .await?;
2679 info!("Invoice created {}", invoice);
2680
2681 let open_channel_params = match open_channel_needed {
2682 true => Some(OpenChannelParams {
2683 payer_amount_msat: req.amount_msat,
2684 opening_fee_params: channel_opening_fee_params.clone().ok_or(
2685 ReceivePaymentError::Generic {
2686 err: "We need to open a channel, but no channel opening fee params found"
2687 .into(),
2688 },
2689 )?,
2690 }),
2691 false => None,
2692 };
2693
2694 let invoice = self
2695 .wrap_node_invoice(&invoice, open_channel_params, Some(lsp_info))
2696 .await?;
2697 let parsed_invoice = parse_invoice(&invoice)?;
2698
2699 Ok(ReceivePaymentResponse {
2701 ln_invoice: parsed_invoice,
2702 opening_fee_params: channel_opening_fee_params,
2703 opening_fee_msat: channel_fees_msat,
2704 })
2705 }
2706
2707 async fn wrap_node_invoice(
2708 &self,
2709 invoice: &str,
2710 params: Option<OpenChannelParams>,
2711 lsp_info: Option<LspInformation>,
2712 ) -> Result<String, ReceivePaymentError> {
2713 let lsp_info = match lsp_info {
2714 Some(lsp_info) => lsp_info,
2715 None => get_lsp(self.persister.clone(), self.lsp.clone()).await?,
2716 };
2717
2718 match params {
2719 Some(params) => {
2720 self.wrap_open_channel_invoice(invoice, params, &lsp_info)
2721 .await
2722 }
2723 None => self.ensure_hint(invoice, &lsp_info).await,
2724 }
2725 }
2726}
2727
2728impl PaymentReceiver {
2729 async fn ensure_hint(
2730 &self,
2731 invoice: &str,
2732 lsp_info: &LspInformation,
2733 ) -> Result<String, ReceivePaymentError> {
2734 info!("Getting routing hints from node");
2735 let (mut hints, has_public_channel) = self.node_api.get_routing_hints(lsp_info).await?;
2736 if !has_public_channel && hints.is_empty() {
2737 return Err(ReceivePaymentError::InvoiceNoRoutingHints {
2738 err: "Must have at least one active channel".into(),
2739 });
2740 }
2741
2742 let parsed_invoice = parse_invoice(invoice)?;
2743
2744 info!("Existing routing hints {:?}", parsed_invoice.routing_hints);
2746
2747 if let Some(lsp_hint) = Self::limit_and_extract_lsp_hint(&mut hints, lsp_info) {
2749 if parsed_invoice.contains_hint_for_node(lsp_info.pubkey.as_str()) {
2750 return Ok(String::from(invoice));
2751 }
2752
2753 info!("Adding lsp hint: {lsp_hint:?}");
2754 let modified =
2755 add_routing_hints(invoice, true, &vec![lsp_hint], parsed_invoice.amount_msat)?;
2756
2757 let invoice = self.node_api.sign_invoice(modified).await?;
2758 info!("Signed invoice with hint = {invoice}");
2759 return Ok(invoice);
2760 }
2761
2762 if parsed_invoice.routing_hints.is_empty() {
2763 info!("Adding custom hints: {hints:?}");
2764 let modified = add_routing_hints(invoice, false, &hints, parsed_invoice.amount_msat)?;
2765 let invoice = self.node_api.sign_invoice(modified).await?;
2766 info!("Signed invoice with hints = {invoice}");
2767 return Ok(invoice);
2768 }
2769
2770 Ok(String::from(invoice))
2771 }
2772
2773 async fn wrap_open_channel_invoice(
2774 &self,
2775 invoice: &str,
2776 params: OpenChannelParams,
2777 lsp_info: &LspInformation,
2778 ) -> Result<String, ReceivePaymentError> {
2779 let parsed_invoice = parse_invoice(invoice)?;
2780 let open_channel_hint = RouteHint {
2781 hops: vec![RouteHintHop {
2782 src_node_id: lsp_info.pubkey.clone(),
2783 short_channel_id: "1x0x0".to_string(),
2784 fees_base_msat: lsp_info.base_fee_msat as u32,
2785 fees_proportional_millionths: (lsp_info.fee_rate * 1000000.0) as u32,
2786 cltv_expiry_delta: lsp_info.time_lock_delta as u64,
2787 htlc_minimum_msat: Some(lsp_info.min_htlc_msat as u64),
2788 htlc_maximum_msat: None,
2789 }],
2790 };
2791 info!("Adding open channel hint: {open_channel_hint:?}");
2792 let invoice_with_hint = add_routing_hints(
2793 invoice,
2794 false,
2795 &vec![open_channel_hint],
2796 Some(params.payer_amount_msat),
2797 )?;
2798 let signed_invoice = self.node_api.sign_invoice(invoice_with_hint).await?;
2799
2800 info!("Registering payment with LSP");
2801 let api_key = self.config.api_key.clone().unwrap_or_default();
2802 let api_key_hash = sha256::Hash::hash(api_key.as_bytes()).to_hex();
2803
2804 self.lsp
2805 .register_payment(
2806 lsp_info.id.clone(),
2807 lsp_info.lsp_pubkey.clone(),
2808 grpc::PaymentInformation {
2809 payment_hash: hex::decode(parsed_invoice.payment_hash.clone())
2810 .map_err(|e| anyhow!("Failed to decode hex payment hash: {e}"))?,
2811 payment_secret: parsed_invoice.payment_secret.clone(),
2812 destination: hex::decode(parsed_invoice.payee_pubkey.clone())
2813 .map_err(|e| anyhow!("Failed to decode hex payee pubkey: {e}"))?,
2814 incoming_amount_msat: params.payer_amount_msat as i64,
2815 outgoing_amount_msat: parsed_invoice
2816 .amount_msat
2817 .ok_or(anyhow!("Open channel invoice must have an amount"))?
2818 as i64,
2819 tag: json!({ "apiKeyHash": api_key_hash }).to_string(),
2820 opening_fee_params: Some(params.opening_fee_params.into()),
2821 },
2822 )
2823 .await?;
2824 self.persister.insert_open_channel_payment_info(
2826 &parsed_invoice.payment_hash,
2827 params.payer_amount_msat,
2828 &signed_invoice,
2829 )?;
2830
2831 Ok(signed_invoice)
2832 }
2833
2834 fn limit_and_extract_lsp_hint(
2835 routing_hints: &mut Vec<RouteHint>,
2836 lsp_info: &LspInformation,
2837 ) -> Option<RouteHint> {
2838 let mut lsp_hint: Option<RouteHint> = None;
2839 if let Some(lsp_index) = routing_hints.iter().position(|r| {
2840 r.hops
2841 .iter()
2842 .any(|h| h.src_node_id == lsp_info.pubkey.clone())
2843 }) {
2844 lsp_hint = Some(routing_hints.remove(lsp_index));
2845 }
2846 if routing_hints.len() > 3 {
2847 routing_hints.drain(3..);
2848 }
2849 lsp_hint
2850 }
2851}
2852
2853async fn get_lsp(
2855 persister: Arc<SqliteStorage>,
2856 lsp_api: Arc<dyn LspAPI>,
2857) -> SdkResult<LspInformation> {
2858 let lsp_id = persister
2859 .get_lsp_id()?
2860 .ok_or(SdkError::generic("No LSP ID found"))?;
2861
2862 get_lsp_by_id(persister, lsp_api, lsp_id.as_str())
2863 .await?
2864 .ok_or_else(|| SdkError::Generic {
2865 err: format!("No LSP found for id {lsp_id}"),
2866 })
2867}
2868
2869async fn get_lsps(
2870 persister: Arc<SqliteStorage>,
2871 lsp_api: Arc<dyn LspAPI>,
2872) -> SdkResult<Vec<LspInformation>> {
2873 let node_pubkey = persister
2874 .get_node_state()?
2875 .ok_or(SdkError::generic("Node info not found"))?
2876 .id;
2877
2878 lsp_api.list_lsps(node_pubkey).await
2879}
2880
2881async fn get_lsp_by_id(
2882 persister: Arc<SqliteStorage>,
2883 lsp_api: Arc<dyn LspAPI>,
2884 lsp_id: &str,
2885) -> SdkResult<Option<LspInformation>> {
2886 Ok(get_lsps(persister, lsp_api)
2887 .await?
2888 .into_iter()
2889 .find(|lsp| lsp.id.as_str() == lsp_id))
2890}
2891
2892async fn get_notification_lsps(
2895 persister: Arc<SqliteStorage>,
2896 lsp_api: Arc<dyn LspAPI>,
2897 node_api: Arc<dyn NodeAPI>,
2898) -> SdkResult<Vec<LspInformation>> {
2899 let node_pubkey = persister
2900 .get_node_state()?
2901 .ok_or(SdkError::generic("Node info not found"))?
2902 .id;
2903 let mut open_peers = None;
2904
2905 let mut notification_lsps = vec![];
2906 for lsp in lsp_api.list_used_lsps(node_pubkey).await? {
2907 match !lsp.opening_fee_params_list.values.is_empty() {
2908 true => {
2909 notification_lsps.push(lsp);
2912 }
2913 false => {
2914 let lsp_pubkey = hex::decode(&lsp.pubkey)
2916 .map_err(|e| anyhow!("Failed decode lsp pubkey: {e}"))?;
2917 let open_peers = match &open_peers {
2918 Some(open_peers) => open_peers,
2919 None => {
2920 open_peers = Some(node_api.get_open_peers().await?);
2921 open_peers.as_ref().unwrap()
2922 }
2923 };
2924 let has_active_channel_to_lsp = open_peers.contains(&lsp_pubkey);
2925 if has_active_channel_to_lsp {
2926 notification_lsps.push(lsp);
2927 }
2928 }
2929 }
2930 }
2931 Ok(notification_lsps)
2932}
2933
2934#[cfg(test)]
2935pub(crate) mod tests {
2936 use std::collections::HashMap;
2937 use std::sync::Arc;
2938
2939 use anyhow::{anyhow, Result};
2940 use regex::Regex;
2941 use reqwest::Url;
2942 use sdk_common::prelude::Rate;
2943
2944 use crate::breez_services::{BreezServices, BreezServicesBuilder};
2945 use crate::models::{LnPaymentDetails, NodeState, Payment, PaymentDetails, PaymentTypeFilter};
2946 use crate::node_api::NodeAPI;
2947 use crate::persist::cache::NodeStateStorage;
2948 use crate::persist::swap::SwapStorage;
2949 use crate::test_utils::*;
2950 use crate::*;
2951
2952 use super::{PaymentReceiver, Receiver};
2953
2954 #[tokio::test]
2955 async fn test_node_state() -> Result<()> {
2956 let dummy_node_state = get_dummy_node_state();
2960
2961 let lnurl_metadata = "{'key': 'sample-metadata-val'}";
2962 let test_ln_address = "test@ln-address.com";
2963 let test_lnurl_withdraw_endpoint = "https://test.endpoint.lnurl-w";
2964 let sa = SuccessActionProcessed::Message {
2965 data: MessageSuccessActionData {
2966 message: "test message".into(),
2967 },
2968 };
2969
2970 let payment_hash_lnurl_withdraw = "2222";
2971 let payment_hash_with_lnurl_success_action = "3333";
2972 let payment_hash_swap: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
2973 let swap_info = SwapInfo {
2974 bitcoin_address: "123".to_string(),
2975 created_at: 12345678,
2976 lock_height: 654321,
2977 payment_hash: payment_hash_swap.clone(),
2978 preimage: vec![],
2979 private_key: vec![],
2980 public_key: vec![],
2981 swapper_public_key: vec![],
2982 script: vec![],
2983 bolt11: Some("312".into()),
2984 paid_msat: 1000,
2985 confirmed_sats: 1,
2986 unconfirmed_sats: 0,
2987 total_incoming_txs: 1,
2988 status: SwapStatus::Refundable,
2989 refund_tx_ids: vec![],
2990 unconfirmed_tx_ids: vec![],
2991 confirmed_tx_ids: vec![],
2992 min_allowed_deposit: 5_000,
2993 max_allowed_deposit: 1_000_000,
2994 max_swapper_payable: 2_000_000,
2995 last_redeem_error: None,
2996 channel_opening_fees: Some(OpeningFeeParams {
2997 min_msat: 5_000_000,
2998 proportional: 50,
2999 valid_until: "date".to_string(),
3000 max_idle_time: 12345,
3001 max_client_to_self_delay: 234,
3002 promise: "promise".to_string(),
3003 }),
3004 confirmed_at: Some(555),
3005 };
3006 let payment_hash_rev_swap: Vec<u8> = vec![8, 7, 6, 5, 4, 3, 2, 1];
3007 let preimage_rev_swap: Vec<u8> = vec![6, 6, 6, 6];
3008 let full_ref_swap_info = FullReverseSwapInfo {
3009 id: "rev_swap_id".to_string(),
3010 created_at_block_height: 0,
3011 preimage: preimage_rev_swap.clone(),
3012 private_key: vec![],
3013 claim_pubkey: "claim_pubkey".to_string(),
3014 timeout_block_height: 600_000,
3015 invoice: "645".to_string(),
3016 redeem_script: "redeem_script".to_string(),
3017 onchain_amount_sat: 250,
3018 sat_per_vbyte: Some(50),
3019 receive_amount_sat: None,
3020 cache: ReverseSwapInfoCached {
3021 status: ReverseSwapStatus::CompletedConfirmed,
3022 lockup_txid: Some("lockup_txid".to_string()),
3023 claim_txid: Some("claim_txid".to_string()),
3024 },
3025 };
3026 let rev_swap_info = ReverseSwapInfo {
3027 id: "rev_swap_id".to_string(),
3028 claim_pubkey: "claim_pubkey".to_string(),
3029 lockup_txid: Some("lockup_txid".to_string()),
3030 claim_txid: Some("claim_txid".to_string()),
3031 onchain_amount_sat: 250,
3032 status: ReverseSwapStatus::CompletedConfirmed,
3033 };
3034 let dummy_transactions = vec![
3035 Payment {
3036 id: "1111".to_string(),
3037 payment_type: PaymentType::Received,
3038 payment_time: 100000,
3039 amount_msat: 10,
3040 fee_msat: 0,
3041 status: PaymentStatus::Complete,
3042 error: None,
3043 description: Some("test receive".to_string()),
3044 details: PaymentDetails::Ln {
3045 data: LnPaymentDetails {
3046 payment_hash: "1111".to_string(),
3047 label: "".to_string(),
3048 destination_pubkey: "1111".to_string(),
3049 payment_preimage: "2222".to_string(),
3050 keysend: false,
3051 bolt11: "1111".to_string(),
3052 lnurl_success_action: None,
3053 lnurl_pay_domain: None,
3054 lnurl_pay_comment: None,
3055 lnurl_metadata: None,
3056 ln_address: None,
3057 lnurl_withdraw_endpoint: None,
3058 swap_info: None,
3059 reverse_swap_info: None,
3060 pending_expiration_block: None,
3061 open_channel_bolt11: None,
3062 },
3063 },
3064 metadata: None,
3065 },
3066 Payment {
3067 id: payment_hash_lnurl_withdraw.to_string(),
3068 payment_type: PaymentType::Received,
3069 payment_time: 150000,
3070 amount_msat: 10,
3071 fee_msat: 0,
3072 status: PaymentStatus::Complete,
3073 error: None,
3074 description: Some("test lnurl-withdraw receive".to_string()),
3075 details: PaymentDetails::Ln {
3076 data: LnPaymentDetails {
3077 payment_hash: payment_hash_lnurl_withdraw.to_string(),
3078 label: "".to_string(),
3079 destination_pubkey: "1111".to_string(),
3080 payment_preimage: "3333".to_string(),
3081 keysend: false,
3082 bolt11: "1111".to_string(),
3083 lnurl_success_action: None,
3084 lnurl_pay_domain: None,
3085 lnurl_pay_comment: None,
3086 lnurl_metadata: None,
3087 ln_address: None,
3088 lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3089 swap_info: None,
3090 reverse_swap_info: None,
3091 pending_expiration_block: None,
3092 open_channel_bolt11: None,
3093 },
3094 },
3095 metadata: None,
3096 },
3097 Payment {
3098 id: payment_hash_with_lnurl_success_action.to_string(),
3099 payment_type: PaymentType::Sent,
3100 payment_time: 200000,
3101 amount_msat: 8,
3102 fee_msat: 2,
3103 status: PaymentStatus::Complete,
3104 error: None,
3105 description: Some("test payment".to_string()),
3106 details: PaymentDetails::Ln {
3107 data: LnPaymentDetails {
3108 payment_hash: payment_hash_with_lnurl_success_action.to_string(),
3109 label: "".to_string(),
3110 destination_pubkey: "123".to_string(),
3111 payment_preimage: "4444".to_string(),
3112 keysend: false,
3113 bolt11: "123".to_string(),
3114 lnurl_success_action: Some(sa.clone()),
3115 lnurl_pay_domain: None,
3116 lnurl_pay_comment: None,
3117 lnurl_metadata: Some(lnurl_metadata.to_string()),
3118 ln_address: Some(test_ln_address.to_string()),
3119 lnurl_withdraw_endpoint: None,
3120 swap_info: None,
3121 reverse_swap_info: None,
3122 pending_expiration_block: None,
3123 open_channel_bolt11: None,
3124 },
3125 },
3126 metadata: None,
3127 },
3128 Payment {
3129 id: hex::encode(payment_hash_swap.clone()),
3130 payment_type: PaymentType::Received,
3131 payment_time: 250000,
3132 amount_msat: 1_000,
3133 fee_msat: 0,
3134 status: PaymentStatus::Complete,
3135 error: None,
3136 description: Some("test receive".to_string()),
3137 details: PaymentDetails::Ln {
3138 data: LnPaymentDetails {
3139 payment_hash: hex::encode(payment_hash_swap),
3140 label: "".to_string(),
3141 destination_pubkey: "321".to_string(),
3142 payment_preimage: "5555".to_string(),
3143 keysend: false,
3144 bolt11: "312".to_string(),
3145 lnurl_success_action: None,
3146 lnurl_pay_domain: None,
3147 lnurl_pay_comment: None,
3148 lnurl_metadata: None,
3149 ln_address: None,
3150 lnurl_withdraw_endpoint: None,
3151 swap_info: Some(swap_info.clone()),
3152 reverse_swap_info: None,
3153 pending_expiration_block: None,
3154 open_channel_bolt11: None,
3155 },
3156 },
3157 metadata: None,
3158 },
3159 Payment {
3160 id: hex::encode(payment_hash_rev_swap.clone()),
3161 payment_type: PaymentType::Sent,
3162 payment_time: 300000,
3163 amount_msat: 50_000_000,
3164 fee_msat: 2_000,
3165 status: PaymentStatus::Complete,
3166 error: None,
3167 description: Some("test send onchain".to_string()),
3168 details: PaymentDetails::Ln {
3169 data: LnPaymentDetails {
3170 payment_hash: hex::encode(payment_hash_rev_swap),
3171 label: "".to_string(),
3172 destination_pubkey: "321".to_string(),
3173 payment_preimage: hex::encode(preimage_rev_swap),
3174 keysend: false,
3175 bolt11: "312".to_string(),
3176 lnurl_success_action: None,
3177 lnurl_metadata: None,
3178 lnurl_pay_domain: None,
3179 lnurl_pay_comment: None,
3180 ln_address: None,
3181 lnurl_withdraw_endpoint: None,
3182 swap_info: None,
3183 reverse_swap_info: Some(rev_swap_info.clone()),
3184 pending_expiration_block: None,
3185 open_channel_bolt11: None,
3186 },
3187 },
3188 metadata: None,
3189 },
3190 ];
3191 let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3192
3193 let test_config = create_test_config();
3194 let persister = Arc::new(create_test_persister(test_config.clone()));
3195 persister.init()?;
3196 persister.insert_or_update_payments(&dummy_transactions, false)?;
3197 persister.insert_payment_external_info(
3198 payment_hash_with_lnurl_success_action,
3199 PaymentExternalInfo {
3200 lnurl_pay_success_action: Some(sa.clone()),
3201 lnurl_pay_domain: None,
3202 lnurl_pay_comment: None,
3203 lnurl_metadata: Some(lnurl_metadata.to_string()),
3204 ln_address: Some(test_ln_address.to_string()),
3205 lnurl_withdraw_endpoint: None,
3206 attempted_amount_msat: None,
3207 attempted_error: None,
3208 },
3209 )?;
3210 persister.insert_payment_external_info(
3211 payment_hash_lnurl_withdraw,
3212 PaymentExternalInfo {
3213 lnurl_pay_success_action: None,
3214 lnurl_pay_domain: None,
3215 lnurl_pay_comment: None,
3216 lnurl_metadata: None,
3217 ln_address: None,
3218 lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
3219 attempted_amount_msat: None,
3220 attempted_error: None,
3221 },
3222 )?;
3223 persister.insert_swap(&swap_info)?;
3224 persister.update_swap_bolt11(
3225 swap_info.bitcoin_address.clone(),
3226 swap_info.bolt11.clone().unwrap(),
3227 )?;
3228 persister.insert_reverse_swap(&full_ref_swap_info)?;
3229 persister
3230 .update_reverse_swap_status("rev_swap_id", &ReverseSwapStatus::CompletedConfirmed)?;
3231 persister
3232 .update_reverse_swap_lockup_txid("rev_swap_id", Some("lockup_txid".to_string()))?;
3233 persister.update_reverse_swap_claim_txid("rev_swap_id", Some("claim_txid".to_string()))?;
3234
3235 let mut builder = BreezServicesBuilder::new(test_config.clone());
3236 let breez_services = builder
3237 .lsp_api(Arc::new(MockBreezServer {}))
3238 .fiat_api(Arc::new(MockBreezServer {}))
3239 .node_api(node_api)
3240 .persister(persister)
3241 .backup_transport(Arc::new(MockBackupTransport::new()))
3242 .build(None, None)
3243 .await?;
3244
3245 breez_services.sync().await?;
3246 let fetched_state = breez_services.node_info()?;
3247 assert_eq!(fetched_state, dummy_node_state);
3248
3249 let all = breez_services
3250 .list_payments(ListPaymentsRequest::default())
3251 .await?;
3252 let mut cloned = all.clone();
3253
3254 cloned.reverse();
3256 assert_eq!(dummy_transactions, cloned);
3257
3258 let received = breez_services
3259 .list_payments(ListPaymentsRequest {
3260 filters: Some(vec![PaymentTypeFilter::Received]),
3261 ..Default::default()
3262 })
3263 .await?;
3264 assert_eq!(
3265 received,
3266 vec![cloned[3].clone(), cloned[1].clone(), cloned[0].clone()]
3267 );
3268
3269 let sent = breez_services
3270 .list_payments(ListPaymentsRequest {
3271 filters: Some(vec![
3272 PaymentTypeFilter::Sent,
3273 PaymentTypeFilter::ClosedChannel,
3274 ]),
3275 ..Default::default()
3276 })
3277 .await?;
3278 assert_eq!(sent, vec![cloned[4].clone(), cloned[2].clone()]);
3279 assert!(matches!(
3280 &sent[1].details,
3281 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_success_action, ..}}
3282 if lnurl_success_action == &Some(sa)));
3283 assert!(matches!(
3284 &sent[1].details,
3285 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_pay_domain, ln_address, ..}}
3286 if lnurl_pay_domain.is_none() && ln_address == &Some(test_ln_address.to_string())));
3287 assert!(matches!(
3288 &received[1].details,
3289 PaymentDetails::Ln {data: LnPaymentDetails {lnurl_withdraw_endpoint, ..}}
3290 if lnurl_withdraw_endpoint == &Some(test_lnurl_withdraw_endpoint.to_string())));
3291 assert!(matches!(
3292 &received[0].details,
3293 PaymentDetails::Ln {data: LnPaymentDetails {swap_info: swap, ..}}
3294 if swap == &Some(swap_info)));
3295 assert!(matches!(
3296 &sent[0].details,
3297 PaymentDetails::Ln {data: LnPaymentDetails {reverse_swap_info: rev_swap, ..}}
3298 if rev_swap == &Some(rev_swap_info)));
3299
3300 Ok(())
3301 }
3302
3303 #[tokio::test]
3304 async fn test_receive_with_open_channel() -> Result<()> {
3305 let config = create_test_config();
3306 let persister = Arc::new(create_test_persister(config.clone()));
3307 persister.init().unwrap();
3308
3309 let dummy_node_state = get_dummy_node_state();
3310
3311 let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));
3312
3313 let breez_server = Arc::new(MockBreezServer {});
3314 persister.set_lsp(breez_server.lsp_id(), None).unwrap();
3315 persister.set_node_state(&dummy_node_state).unwrap();
3316
3317 let receiver: Arc<dyn Receiver> = Arc::new(PaymentReceiver {
3318 config,
3319 node_api,
3320 persister,
3321 lsp: breez_server.clone(),
3322 });
3323 let ln_invoice = receiver
3324 .receive_payment(ReceivePaymentRequest {
3325 amount_msat: 3_000_000,
3326 description: "should populate lsp hints".to_string(),
3327 use_description_hash: Some(false),
3328 ..Default::default()
3329 })
3330 .await?
3331 .ln_invoice;
3332 assert_eq!(ln_invoice.routing_hints[0].hops.len(), 1);
3333 let lsp_hop = &ln_invoice.routing_hints[0].hops[0];
3334 assert_eq!(lsp_hop.src_node_id, breez_server.clone().lsp_pub_key());
3335 assert_eq!(lsp_hop.short_channel_id, "1x0x0");
3336 Ok(())
3337 }
3338
3339 #[tokio::test]
3340 async fn test_list_lsps() -> Result<()> {
3341 let storage_path = format!("{}/storage.sql", get_test_working_dir());
3342 std::fs::remove_file(storage_path).ok();
3343
3344 let breez_services = breez_services()
3345 .await
3346 .map_err(|e| anyhow!("Failed to get the BreezServices: {e}"))?;
3347 breez_services.sync().await?;
3348
3349 let node_pubkey = breez_services.node_info()?.id;
3350 let lsps = breez_services.lsp_api.list_lsps(node_pubkey).await?;
3351 assert_eq!(lsps.len(), 1);
3352
3353 Ok(())
3354 }
3355
3356 #[tokio::test]
3357 async fn test_fetch_rates() -> Result<(), Box<dyn std::error::Error>> {
3358 let breez_services = breez_services().await?;
3359 breez_services.sync().await?;
3360
3361 let rates = breez_services.fiat_api.fetch_fiat_rates().await?;
3362 assert_eq!(rates.len(), 1);
3363 assert_eq!(
3364 rates[0],
3365 Rate {
3366 coin: "USD".to_string(),
3367 value: 20_000.00,
3368 }
3369 );
3370
3371 Ok(())
3372 }
3373
3374 #[tokio::test]
3375 async fn test_buy_bitcoin_with_moonpay() -> Result<(), Box<dyn std::error::Error>> {
3376 let mock_rest_client = MockRestClient::new();
3377 mock_rest_client.add_response(MockResponse::new(200, "800000".to_string()));
3378 let rest_client: Arc<dyn RestClient> = Arc::new(mock_rest_client);
3379
3380 let breez_services = breez_services_with(None, Some(rest_client.clone()), vec![]).await?;
3381 breez_services.sync().await?;
3382
3383 let moonpay_url = breez_services
3384 .buy_bitcoin(BuyBitcoinRequest {
3385 provider: BuyBitcoinProvider::Moonpay,
3386 opening_fee_params: None,
3387 redirect_url: None,
3388 })
3389 .await?
3390 .url;
3391 let parsed = Url::parse(&moonpay_url)?;
3392 let query_pairs = parsed.query_pairs().into_owned().collect::<HashMap<_, _>>();
3393
3394 assert_eq!(parsed.host_str(), Some("mock.moonpay"));
3395 assert_eq!(parsed.path(), "/");
3396
3397 let wallet_address =
3398 parse_with_rest_client(rest_client.as_ref(), query_pairs.get("wa").unwrap(), None)
3399 .await?;
3400 assert!(matches!(wallet_address, InputType::BitcoinAddress { .. }));
3401
3402 let max_amount = query_pairs.get("ma").unwrap();
3403 assert!(Regex::new(r"^\d+\.\d{8}$").unwrap().is_match(max_amount));
3404
3405 Ok(())
3406 }
3407
3408 pub(crate) async fn breez_services() -> Result<Arc<BreezServices>> {
3410 breez_services_with(None, None, vec![]).await
3411 }
3412
3413 pub(crate) async fn breez_services_with(
3415 node_api: Option<Arc<dyn NodeAPI>>,
3416 rest_client: Option<Arc<dyn RestClient>>,
3417 known_payments: Vec<Payment>,
3418 ) -> Result<Arc<BreezServices>> {
3419 let node_api =
3420 node_api.unwrap_or_else(|| Arc::new(MockNodeAPI::new(get_dummy_node_state())));
3421 let rest_client: Arc<dyn RestClient> =
3422 rest_client.unwrap_or_else(|| Arc::new(MockRestClient::new()));
3423
3424 let test_config = create_test_config();
3425 let persister = Arc::new(create_test_persister(test_config.clone()));
3426 persister.init()?;
3427 persister.insert_or_update_payments(&known_payments, false)?;
3428 persister.set_lsp(MockBreezServer {}.lsp_id(), None)?;
3429
3430 let mut builder = BreezServicesBuilder::new(test_config.clone());
3431 let breez_services = builder
3432 .lsp_api(Arc::new(MockBreezServer {}))
3433 .fiat_api(Arc::new(MockBreezServer {}))
3434 .taproot_swapper_api(Arc::new(MockBreezServer {}))
3435 .reverse_swap_service_api(Arc::new(MockReverseSwapperAPI {}))
3436 .buy_bitcoin_api(Arc::new(MockBuyBitcoinService {}))
3437 .persister(persister)
3438 .node_api(node_api)
3439 .rest_client(rest_client)
3440 .backup_transport(Arc::new(MockBackupTransport::new()))
3441 .build(None, None)
3442 .await?;
3443
3444 Ok(breez_services)
3445 }
3446
3447 pub(crate) fn get_dummy_node_state() -> NodeState {
3449 NodeState {
3450 id: "tx1".to_string(),
3451 block_height: 1,
3452 channels_balance_msat: 100,
3453 onchain_balance_msat: 1_000,
3454 pending_onchain_balance_msat: 100,
3455 utxos: vec![],
3456 max_payable_msat: 95,
3457 max_receivable_msat: 4_000_000_000,
3458 max_single_payment_amount_msat: 1_000,
3459 max_chan_reserve_msats: 0,
3460 connected_peers: vec!["1111".to_string()],
3461 max_receivable_single_payment_amount_msat: 2_000,
3462 total_inbound_liquidity_msats: 10_000,
3463 }
3464 }
3465}