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