1#![cfg_attr(
2 all(target_family = "wasm", target_os = "unknown"),
3 allow(clippy::arc_with_non_send_sync)
4)]
5use std::sync::Arc;
6
7use breez_sdk_common::{
8 breez_server::{BreezServer, PRODUCTION_BREEZSERVER_URL},
9 rest::ReqwestRestClient as CommonRequestRestClient,
10};
11use spark_wallet::{DefaultSigner, Signer};
12use tokio::sync::watch;
13use tracing::debug;
14
15use crate::{
16 Credentials, EventEmitter, FiatService, FiatServiceWrapper, KeySetType, Network, RestClient,
17 RestClientWrapper, Seed,
18 chain::{
19 BitcoinChainService,
20 rest_client::{BasicAuth, RestClientChainService},
21 },
22 error::SdkError,
23 lnurl::{LnurlServerClient, ReqwestLnurlServerClient},
24 models::Config,
25 payment_observer::{PaymentObserver, SparkTransferObserver},
26 persist::Storage,
27 realtime_sync::{RealTimeSyncParams, init_and_start_real_time_sync},
28 sdk::{BreezSdk, BreezSdkParams},
29 sync_storage::SyncStorage,
30};
31
32#[derive(Clone)]
34pub struct SdkBuilder {
35 config: Config,
36 seed: Seed,
37 storage_dir: Option<String>,
38 storage: Option<Arc<dyn Storage>>,
39 chain_service: Option<Arc<dyn BitcoinChainService>>,
40 fiat_service: Option<Arc<dyn FiatService>>,
41 lnurl_client: Option<Arc<dyn RestClient>>,
42 lnurl_server_client: Option<Arc<dyn LnurlServerClient>>,
43 payment_observer: Option<Arc<dyn PaymentObserver>>,
44 key_set_type: KeySetType,
45 use_address_index: bool,
46 account_number: Option<u32>,
47 sync_storage: Option<Arc<dyn SyncStorage>>,
48}
49
50impl SdkBuilder {
51 pub fn new(config: Config, seed: Seed) -> Self {
56 SdkBuilder {
57 config,
58 seed,
59 storage_dir: None,
60 storage: None,
61 chain_service: None,
62 fiat_service: None,
63 lnurl_client: None,
64 lnurl_server_client: None,
65 payment_observer: None,
66 key_set_type: KeySetType::Default,
67 use_address_index: false,
68 account_number: None,
69 sync_storage: None,
70 }
71 }
72
73 #[must_use]
74 pub fn with_default_storage(mut self, storage_dir: String) -> Self {
80 self.storage_dir = Some(storage_dir);
81 self
82 }
83
84 #[must_use]
85 pub fn with_storage(mut self, storage: Arc<dyn Storage>) -> Self {
89 self.storage = Some(storage);
90 self
91 }
92
93 #[must_use]
94 pub fn with_real_time_sync_storage(mut self, storage: Arc<dyn SyncStorage>) -> Self {
98 self.sync_storage = Some(storage);
99 self
100 }
101
102 #[must_use]
107 pub fn with_key_set(
108 mut self,
109 key_set_type: KeySetType,
110 use_address_index: bool,
111 account_number: Option<u32>,
112 ) -> Self {
113 self.key_set_type = key_set_type;
114 self.use_address_index = use_address_index;
115 self.account_number = account_number;
116 self
117 }
118
119 #[must_use]
123 pub fn with_chain_service(mut self, chain_service: Arc<dyn BitcoinChainService>) -> Self {
124 self.chain_service = Some(chain_service);
125 self
126 }
127
128 #[must_use]
133 pub fn with_rest_chain_service(
134 mut self,
135 url: String,
136 credentials: Option<Credentials>,
137 ) -> Self {
138 self.chain_service = Some(Arc::new(RestClientChainService::new(
139 url,
140 self.config.network,
141 5,
142 Box::new(CommonRequestRestClient::new().unwrap()),
143 credentials.map(|c| BasicAuth::new(c.username, c.password)),
144 )));
145 self
146 }
147
148 #[must_use]
152 pub fn with_fiat_service(mut self, fiat_service: Arc<dyn FiatService>) -> Self {
153 self.fiat_service = Some(fiat_service);
154 self
155 }
156
157 #[must_use]
158 pub fn with_lnurl_client(mut self, lnurl_client: Arc<dyn RestClient>) -> Self {
159 self.lnurl_client = Some(lnurl_client);
160 self
161 }
162
163 #[must_use]
164 #[allow(unused)]
165 pub fn with_lnurl_server_client(
166 mut self,
167 lnurl_serverclient: Arc<dyn LnurlServerClient>,
168 ) -> Self {
169 self.lnurl_server_client = Some(lnurl_serverclient);
170 self
171 }
172
173 #[must_use]
178 #[allow(unused)]
179 pub fn with_payment_observer(mut self, payment_observer: Arc<dyn PaymentObserver>) -> Self {
180 self.payment_observer = Some(payment_observer);
181 self
182 }
183
184 #[allow(clippy::too_many_lines)]
186 pub async fn build(self) -> Result<BreezSdk, SdkError> {
187 let (storage, sync_storage) = match (self.storage, self.storage_dir) {
188 (Some(storage), _) => (storage, self.sync_storage),
190 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
192 (None, Some(storage_dir)) => {
193 let storage = default_storage(&storage_dir, self.config.network, &self.seed)?;
194 let sync_storage = match (self.sync_storage, &self.config.real_time_sync_server_url)
195 {
196 (Some(sync_storage), _) => Some(sync_storage),
198 (None, Some(_)) => Some(default_sync_storage(
201 &storage_dir,
202 self.config.network,
203 &self.seed,
204 )?),
205 _ => None,
206 };
207 (storage, sync_storage)
208 }
209 _ => {
210 return Err(SdkError::Generic(
211 "Either storage or storage_dir must be set before building the SDK".to_string(),
212 ));
213 }
214 };
215 let seed = match self.seed {
217 Seed::Mnemonic {
218 mnemonic,
219 passphrase,
220 } => {
221 let mnemonic = bip39::Mnemonic::parse(&mnemonic)
222 .map_err(|e| SdkError::Generic(e.to_string()))?;
223
224 mnemonic
225 .to_seed(passphrase.as_deref().unwrap_or(""))
226 .to_vec()
227 }
228 Seed::Entropy(entropy) => entropy,
229 };
230
231 let signer: Arc<dyn Signer> = Arc::new(
232 DefaultSigner::with_keyset_type(
233 &seed,
234 self.config.network.into(),
235 self.key_set_type.into(),
236 self.use_address_index,
237 self.account_number,
238 )
239 .map_err(|e| SdkError::Generic(e.to_string()))?,
240 );
241 let chain_service = if let Some(service) = self.chain_service {
242 service
243 } else {
244 let inner_client =
245 CommonRequestRestClient::new().map_err(|e| SdkError::Generic(e.to_string()))?;
246 match self.config.network {
247 Network::Mainnet => Arc::new(RestClientChainService::new(
248 "https://blockstream.info/api".to_string(),
249 self.config.network,
250 5,
251 Box::new(inner_client),
252 None,
253 )),
254 Network::Regtest => Arc::new(RestClientChainService::new(
255 "https://regtest-mempool.us-west-2.sparkinfra.net/api".to_string(),
256 self.config.network,
257 5,
258 Box::new(inner_client),
259 match (
260 std::env::var("CHAIN_SERVICE_USERNAME"),
261 std::env::var("CHAIN_SERVICE_PASSWORD"),
262 ) {
263 (Ok(username), Ok(password)) => Some(BasicAuth::new(username, password)),
264 _ => Some(BasicAuth::new(
265 "spark-sdk".to_string(),
266 "mCMk1JqlBNtetUNy".to_string(),
267 )),
268 },
269 )),
270 }
271 };
272
273 let fiat_service: Arc<dyn breez_sdk_common::fiat::FiatService> = match self.fiat_service {
274 Some(service) => Arc::new(FiatServiceWrapper::new(service)),
275 None => Arc::new(
276 BreezServer::new(PRODUCTION_BREEZSERVER_URL, None)
277 .map_err(|e| SdkError::Generic(e.to_string()))?,
278 ),
279 };
280
281 let lnurl_client: Arc<dyn breez_sdk_common::rest::RestClient> = match self.lnurl_client {
282 Some(client) => Arc::new(RestClientWrapper::new(client)),
283 None => Arc::new(
284 CommonRequestRestClient::new().map_err(|e| SdkError::Generic(e.to_string()))?,
285 ),
286 };
287 let spark_wallet_config =
288 spark_wallet::SparkWalletConfig::default_config(self.config.network.into());
289
290 let mut wallet_builder =
291 spark_wallet::WalletBuilder::new(spark_wallet_config, Arc::clone(&signer));
292 if let Some(observer) = self.payment_observer {
293 let observer: Arc<dyn spark_wallet::TransferObserver> =
294 Arc::new(SparkTransferObserver::new(observer));
295 wallet_builder = wallet_builder.with_transfer_observer(observer);
296 }
297 let spark_wallet = Arc::new(wallet_builder.build().await?);
298
299 let lnurl_server_client: Option<Arc<dyn LnurlServerClient>> = match self.lnurl_server_client
300 {
301 Some(client) => Some(client),
302 None => match &self.config.lnurl_domain {
303 Some(domain) => {
304 Some(Arc::new(ReqwestLnurlServerClient::new(
306 domain.clone(),
307 self.config.api_key.clone(),
308 Arc::clone(&spark_wallet),
309 )?))
310 }
311 None => None,
312 },
313 };
314 let shutdown_sender = watch::channel::<()>(()).0;
315
316 let event_emitter = Arc::new(EventEmitter::new());
317 let storage = if let Some(server_url) = &self.config.real_time_sync_server_url {
318 let Some(sync_storage) = sync_storage else {
319 return Err(SdkError::Generic(
320 "Real-time sync is enabled, but no sync storage is supplied".to_string(),
321 ));
322 };
323 init_and_start_real_time_sync(RealTimeSyncParams {
324 server_url: server_url.clone(),
325 api_key: self.config.api_key.clone(),
326 network: self.config.network,
327 seed,
328 storage: Arc::clone(&storage),
329 sync_storage,
330 shutdown_receiver: shutdown_sender.subscribe(),
331 event_emitter: Arc::clone(&event_emitter),
332 })
333 .await?
334 } else {
335 storage
336 };
337
338 let sdk = BreezSdk::init_and_start(BreezSdkParams {
340 config: self.config,
341 storage,
342 chain_service,
343 fiat_service,
344 lnurl_client,
345 lnurl_server_client,
346 shutdown_sender,
347 spark_wallet,
348 event_emitter,
349 })?;
350 debug!("Initialized and started breez sdk.");
351
352 Ok(sdk)
353 }
354}
355
356#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
357fn default_storage(
358 data_dir: &str,
359 network: Network,
360 seed: &Seed,
361) -> Result<Arc<dyn Storage>, SdkError> {
362 let db_path = crate::default_storage_path(data_dir, &network, seed)?;
363 let storage = Arc::new(crate::SqliteStorage::new(&db_path)?);
364 Ok(storage)
365}
366
367#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
368fn default_sync_storage(
369 data_dir: &str,
370 network: Network,
371 seed: &Seed,
372) -> Result<Arc<dyn SyncStorage>, SdkError> {
373 let db_path = crate::default_storage_path(data_dir, &network, seed)?;
374 let storage = Arc::new(crate::SqliteStorage::new(&db_path)?);
375 Ok(storage)
376}