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