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