1mod api;
2mod contacts;
3mod deposits;
4mod helpers;
5mod init;
6mod lightning_address;
7mod lnurl;
8mod payments;
9mod sync;
10mod sync_coordinator;
11
12pub(crate) use sync_coordinator::SyncCoordinator;
13
14use bitflags::bitflags;
15use breez_sdk_common::{buy::moonpay::MoonpayProvider, fiat::FiatService};
16use platform_utils::HttpClient;
17use platform_utils::tokio;
18use spark_wallet::SparkWallet;
19use std::sync::Arc;
20use tokio::sync::{Mutex, OnceCell, oneshot, watch};
21
22use crate::{
23 BitcoinChainService, ExternalInputParser, InputType, Logger, Network, OptimizationConfig,
24 error::SdkError, events::EventEmitter, lnurl::LnurlServerClient, logger, models::Config,
25 persist::Storage, signer::lnurl_auth::LnurlAuthSignerAdapter, stable_balance::StableBalance,
26 token_conversion::TokenConverter,
27};
28
29#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
30const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
31
32#[cfg(all(target_family = "wasm", target_os = "unknown"))]
33const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology:442";
34
35pub(crate) const CLAIM_TX_SIZE_VBYTES: u64 = 99;
36pub(crate) const SYNC_PAGING_LIMIT: u32 = 100;
37
38bitflags! {
39 #[derive(Clone, Debug, PartialEq, Eq)]
40 pub(crate) struct SyncType: u32 {
41 const Wallet = 1 << 0;
42 const WalletState = 1 << 1;
43 const Deposits = 1 << 2;
44 const LnurlMetadata = 1 << 3;
45 const Full = Self::Wallet.0.0
46 | Self::WalletState.0.0
47 | Self::Deposits.0.0
48 | Self::LnurlMetadata.0.0;
49 }
50}
51
52#[derive(Clone, Debug)]
53pub(crate) struct SyncRequest {
54 pub(crate) sync_type: SyncType,
55 #[allow(clippy::type_complexity)]
56 pub(crate) reply: Arc<Mutex<Option<oneshot::Sender<Result<(), SdkError>>>>>,
57 pub(crate) force: bool,
60}
61
62impl SyncRequest {
63 pub(crate) async fn reply(&self, error: Option<SdkError>) {
64 if let Some(reply) = self.reply.lock().await.take() {
65 let _ = match error {
66 Some(e) => reply.send(Err(e)),
67 None => reply.send(Ok(())),
68 };
69 }
70 }
71}
72
73#[derive(Clone)]
76#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
77pub struct BreezSdk {
78 pub(crate) config: Config,
79 pub(crate) spark_wallet: Arc<SparkWallet>,
80 pub(crate) storage: Arc<dyn Storage>,
81 pub(crate) chain_service: Arc<dyn BitcoinChainService>,
82 pub(crate) fiat_service: Arc<dyn FiatService>,
83 pub(crate) lnurl_client: Arc<dyn HttpClient>,
84 pub(crate) lnurl_server_client: Option<Arc<dyn LnurlServerClient>>,
85 pub(crate) lnurl_auth_signer: Arc<LnurlAuthSignerAdapter>,
86 pub(crate) event_emitter: Arc<EventEmitter>,
87 pub(crate) shutdown_sender: watch::Sender<()>,
88 pub(crate) sync_coordinator: SyncCoordinator,
90 pub(crate) initial_synced_watcher: watch::Receiver<bool>,
91 pub(crate) external_input_parsers: Vec<ExternalInputParser>,
92 pub(crate) spark_private_mode_initialized: Arc<OnceCell<()>>,
93 pub(crate) token_converter: Arc<dyn TokenConverter>,
94 pub(crate) stable_balance: Option<Arc<StableBalance>>,
95 pub(crate) buy_bitcoin_provider: Arc<MoonpayProvider>,
96}
97
98pub(crate) struct BreezSdkParams {
99 pub config: Config,
100 pub storage: Arc<dyn Storage>,
101 pub chain_service: Arc<dyn BitcoinChainService>,
102 pub fiat_service: Arc<dyn FiatService>,
103 pub lnurl_client: Arc<dyn HttpClient>,
104 pub lnurl_server_client: Option<Arc<dyn LnurlServerClient>>,
105 pub lnurl_auth_signer: Arc<LnurlAuthSignerAdapter>,
106 pub shutdown_sender: watch::Sender<()>,
107 pub spark_wallet: Arc<SparkWallet>,
108 pub event_emitter: Arc<EventEmitter>,
109 pub buy_bitcoin_provider: Arc<MoonpayProvider>,
110 pub token_converter: Arc<dyn TokenConverter>,
111 pub stable_balance: Option<Arc<StableBalance>>,
112 pub sync_coordinator: SyncCoordinator,
113}
114
115pub async fn parse_input(
116 input: &str,
117 external_input_parsers: Option<Vec<ExternalInputParser>>,
118) -> Result<InputType, SdkError> {
119 Ok(breez_sdk_common::input::parse(
120 input,
121 external_input_parsers.map(|parsers| parsers.into_iter().map(From::from).collect()),
122 )
123 .await?
124 .into())
125}
126
127#[cfg_attr(feature = "uniffi", uniffi::export)]
128pub fn init_logging(
129 log_dir: Option<String>,
130 app_logger: Option<Box<dyn Logger>>,
131 log_filter: Option<String>,
132) -> Result<(), SdkError> {
133 logger::init_logging(log_dir, app_logger, log_filter)
134}
135
136#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
146#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
147pub async fn connect(request: crate::ConnectRequest) -> Result<BreezSdk, SdkError> {
148 let builder = super::sdk_builder::SdkBuilder::new(request.config, request.seed)
149 .with_default_storage(request.storage_dir);
150 let sdk = builder.build().await?;
151 Ok(sdk)
152}
153
154#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
167#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
168pub async fn connect_with_signer(
169 request: crate::ConnectWithSignerRequest,
170) -> Result<BreezSdk, SdkError> {
171 let builder = super::sdk_builder::SdkBuilder::new_with_signer(request.config, request.signer)
172 .with_default_storage(request.storage_dir);
173 let sdk = builder.build().await?;
174 Ok(sdk)
175}
176
177#[cfg_attr(feature = "uniffi", uniffi::export)]
178pub fn default_config(network: Network) -> Config {
179 let lnurl_domain = match network {
180 Network::Mainnet => Some("breez.tips".to_string()),
181 Network::Regtest => None,
182 };
183 Config {
184 api_key: None,
185 network,
186 sync_interval_secs: 60, max_deposit_claim_fee: Some(crate::MaxFee::Rate { sat_per_vbyte: 1 }),
188 lnurl_domain,
189 prefer_spark_over_lightning: false,
190 external_input_parsers: None,
191 use_default_external_input_parsers: true,
192 real_time_sync_server_url: Some(BREEZ_SYNC_SERVICE_URL.to_string()),
193 private_enabled_default: true,
194 optimization_config: OptimizationConfig {
195 auto_enabled: true,
196 multiplicity: 1,
197 },
198 stable_balance_config: None,
199 max_concurrent_claims: 4,
200 spark_config: None,
201 }
202}
203
204#[cfg_attr(feature = "uniffi", uniffi::export)]
220pub fn default_external_signer(
221 mnemonic: String,
222 passphrase: Option<String>,
223 network: Network,
224 key_set_config: Option<crate::models::KeySetConfig>,
225) -> Result<Arc<dyn crate::signer::ExternalSigner>, SdkError> {
226 use crate::signer::DefaultExternalSigner;
227
228 let config = key_set_config.unwrap_or_default();
229 let signer = DefaultExternalSigner::new(
230 mnemonic,
231 passphrase,
232 network,
233 config.key_set_type,
234 config.use_address_index,
235 config.account_number,
236 )?;
237
238 Ok(Arc::new(signer))
239}
240
241#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
246pub async fn get_spark_status() -> Result<crate::SparkStatus, SdkError> {
247 use chrono::DateTime;
248 use platform_utils::DefaultHttpClient;
249
250 #[derive(serde::Deserialize)]
251 struct StatusApiResponse {
252 services: Vec<StatusApiService>,
253 #[serde(rename = "lastUpdated")]
254 last_updated: String,
255 }
256
257 #[derive(serde::Deserialize)]
258 struct StatusApiService {
259 name: String,
260 status: String,
261 }
262
263 fn parse_service_status(s: &str) -> crate::ServiceStatus {
264 match s {
265 "operational" => crate::ServiceStatus::Operational,
266 "degraded" => crate::ServiceStatus::Degraded,
267 "partial" => crate::ServiceStatus::Partial,
268 "major" => crate::ServiceStatus::Major,
269 _ => {
270 tracing::warn!("Unknown service status: {s}");
271 crate::ServiceStatus::Unknown
272 }
273 }
274 }
275
276 let http_client = DefaultHttpClient::default();
277
278 let response = http_client
279 .get("https://spark.money/api/v1/status".to_string(), None)
280 .await
281 .map_err(|e| SdkError::NetworkError(e.to_string()))?;
282
283 let api_response: StatusApiResponse = response
284 .json()
285 .map_err(|e| SdkError::Generic(format!("Failed to parse status response: {e}")))?;
286
287 let status = api_response
288 .services
289 .iter()
290 .filter(|s| s.name == "Spark Operators" || s.name == "SSP")
291 .map(|s| parse_service_status(&s.status))
292 .max()
293 .unwrap_or(crate::ServiceStatus::Unknown);
294
295 let last_updated = DateTime::parse_from_rfc3339(&api_response.last_updated)
296 .map(|dt| dt.timestamp().cast_unsigned())
297 .map_err(|e| SdkError::Generic(format!("Failed to parse lastUpdated timestamp: {e}")))?;
298
299 Ok(crate::SparkStatus {
300 status,
301 last_updated,
302 })
303}