breez_sdk_core/
breez_services.rs

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