1use bitcoin::secp256k1::{PublicKey, ecdsa::Signature};
2use std::str::FromStr;
3use tracing::info;
4
5use breez_sdk_common::buy::cashapp::CashAppProvider;
6
7use crate::{
8 BuyBitcoinRequest, BuyBitcoinResponse, CheckMessageRequest, CheckMessageResponse,
9 GetTokensMetadataRequest, GetTokensMetadataResponse, InputType, ListFiatCurrenciesResponse,
10 ListFiatRatesResponse, Network, OptimizationProgress, RegisterWebhookRequest,
11 RegisterWebhookResponse, SignMessageRequest, SignMessageResponse, UnregisterWebhookRequest,
12 UpdateUserSettingsRequest, UserSettings, Webhook,
13 chain::RecommendedFees,
14 error::SdkError,
15 events::EventListener,
16 issuer::TokenIssuer,
17 models::{GetInfoRequest, GetInfoResponse, StableBalanceActiveLabel},
18 persist::ObjectCacheRepository,
19 utils::token::get_tokens_metadata_cached_or_query,
20};
21
22use super::{BreezSdk, helpers::get_deposit_address, parse_input};
23
24#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
25#[allow(clippy::needless_pass_by_value)]
26impl BreezSdk {
27 pub async fn add_event_listener(&self, listener: Box<dyn EventListener>) -> String {
37 self.event_emitter.add_external_listener(listener).await
38 }
39
40 pub async fn remove_event_listener(&self, id: &str) -> bool {
50 self.event_emitter.remove_external_listener(id).await
51 }
52
53 pub async fn disconnect(&self) -> Result<(), SdkError> {
62 info!("Disconnecting Breez SDK");
63 self.shutdown_sender
64 .send(())
65 .map_err(|_| SdkError::Generic("Failed to send shutdown signal".to_string()))?;
66
67 self.shutdown_sender.closed().await;
68 info!("Breez SDK disconnected");
69 Ok(())
70 }
71
72 pub async fn parse(&self, input: &str) -> Result<InputType, SdkError> {
73 parse_input(input, Some(self.external_input_parsers.clone())).await
74 }
75
76 #[allow(unused_variables)]
78 pub async fn get_info(&self, request: GetInfoRequest) -> Result<GetInfoResponse, SdkError> {
79 if request.ensure_synced.unwrap_or_default() {
80 self.initial_synced_watcher
81 .clone()
82 .changed()
83 .await
84 .map_err(|_| {
85 SdkError::Generic("Failed to receive initial synced signal".to_string())
86 })?;
87 }
88 let object_repository = ObjectCacheRepository::new(self.storage.clone());
89 let account_info = object_repository
90 .fetch_account_info()
91 .await?
92 .unwrap_or_default();
93 Ok(GetInfoResponse {
94 identity_pubkey: self.spark_wallet.get_identity_public_key().to_string(),
95 balance_sats: account_info.balance_sats,
96 token_balances: account_info.token_balances,
97 })
98 }
99
100 pub async fn list_fiat_currencies(&self) -> Result<ListFiatCurrenciesResponse, SdkError> {
103 let currencies = self
104 .fiat_service
105 .fetch_fiat_currencies()
106 .await?
107 .into_iter()
108 .map(From::from)
109 .collect();
110 Ok(ListFiatCurrenciesResponse { currencies })
111 }
112
113 pub async fn list_fiat_rates(&self) -> Result<ListFiatRatesResponse, SdkError> {
115 let rates = self
116 .fiat_service
117 .fetch_fiat_rates()
118 .await?
119 .into_iter()
120 .map(From::from)
121 .collect();
122 Ok(ListFiatRatesResponse { rates })
123 }
124
125 pub async fn recommended_fees(&self) -> Result<RecommendedFees, SdkError> {
127 Ok(self.chain_service.recommended_fees().await?)
128 }
129
130 pub async fn get_tokens_metadata(
137 &self,
138 request: GetTokensMetadataRequest,
139 ) -> Result<GetTokensMetadataResponse, SdkError> {
140 let metadata = get_tokens_metadata_cached_or_query(
141 &self.spark_wallet,
142 &ObjectCacheRepository::new(self.storage.clone()),
143 &request
144 .token_identifiers
145 .iter()
146 .map(String::as_str)
147 .collect::<Vec<_>>(),
148 )
149 .await?;
150 Ok(GetTokensMetadataResponse {
151 tokens_metadata: metadata,
152 })
153 }
154
155 pub async fn sign_message(
159 &self,
160 request: SignMessageRequest,
161 ) -> Result<SignMessageResponse, SdkError> {
162 use bitcoin::hex::DisplayHex;
163
164 let pubkey = self.spark_wallet.get_identity_public_key().to_string();
165 let signature = self.spark_wallet.sign_message(&request.message).await?;
166 let signature_hex = if request.compact {
167 signature.serialize_compact().to_lower_hex_string()
168 } else {
169 signature.serialize_der().to_lower_hex_string()
170 };
171
172 Ok(SignMessageResponse {
173 pubkey,
174 signature: signature_hex,
175 })
176 }
177
178 pub async fn check_message(
182 &self,
183 request: CheckMessageRequest,
184 ) -> Result<CheckMessageResponse, SdkError> {
185 let pubkey = PublicKey::from_str(&request.pubkey)
186 .map_err(|_| SdkError::InvalidInput("Invalid public key".to_string()))?;
187 let signature_bytes = hex::decode(&request.signature)
188 .map_err(|_| SdkError::InvalidInput("Not a valid hex encoded signature".to_string()))?;
189 let signature = Signature::from_der(&signature_bytes)
190 .or_else(|_| Signature::from_compact(&signature_bytes))
191 .map_err(|_| {
192 SdkError::InvalidInput("Not a valid DER or compact encoded signature".to_string())
193 })?;
194
195 let is_valid = self
196 .spark_wallet
197 .verify_message(&request.message, &signature, &pubkey)
198 .await
199 .is_ok();
200 Ok(CheckMessageResponse { is_valid })
201 }
202
203 pub async fn get_user_settings(&self) -> Result<UserSettings, SdkError> {
207 self.ensure_spark_private_mode_initialized().await?;
209
210 let spark_user_settings = self.spark_wallet.query_wallet_settings().await?;
211
212 let stable_balance_active_label = match &self.stable_balance {
213 Some(sb) => sb.get_active_label().await,
214 None => None,
215 };
216
217 Ok(UserSettings {
218 spark_private_mode_enabled: spark_user_settings.private_enabled,
219 stable_balance_active_label,
220 })
221 }
222
223 pub async fn update_user_settings(
227 &self,
228 request: UpdateUserSettingsRequest,
229 ) -> Result<(), SdkError> {
230 if let Some(spark_private_mode_enabled) = request.spark_private_mode_enabled {
231 self.spark_wallet
232 .update_wallet_settings(spark_private_mode_enabled)
233 .await?;
234 }
235
236 if let Some(active_label) = request.stable_balance_active_label {
237 let sb = self
238 .stable_balance
239 .as_ref()
240 .ok_or_else(|| SdkError::Generic("Stable balance is not configured".to_string()))?;
241 let label = if let StableBalanceActiveLabel::Set { label } = active_label {
242 Some(label)
243 } else {
244 None
245 };
246 sb.set_active_token(label).await?;
247 }
248
249 Ok(())
250 }
251
252 pub fn get_token_issuer(&self) -> TokenIssuer {
254 TokenIssuer::new(self.spark_wallet.clone(), self.storage.clone())
255 }
256
257 pub async fn start_leaf_optimization(&self) {
263 self.spark_wallet.start_leaf_optimization().await;
264 }
265
266 pub async fn cancel_leaf_optimization(&self) -> Result<(), SdkError> {
275 self.spark_wallet.cancel_leaf_optimization().await?;
276 Ok(())
277 }
278
279 pub fn get_leaf_optimization_progress(&self) -> OptimizationProgress {
281 self.spark_wallet.get_leaf_optimization_progress().into()
282 }
283
284 pub async fn register_webhook(
298 &self,
299 request: RegisterWebhookRequest,
300 ) -> Result<RegisterWebhookResponse, SdkError> {
301 let event_types = request.event_types.into_iter().map(Into::into).collect();
302 let webhook_id = self
303 .spark_wallet
304 .register_wallet_webhook(&request.url, &request.secret, event_types)
305 .await
306 .map_err(|e| SdkError::Generic(format!("Failed to register webhook: {e}")))?;
307 Ok(RegisterWebhookResponse { webhook_id })
308 }
309
310 pub async fn unregister_webhook(
319 &self,
320 request: UnregisterWebhookRequest,
321 ) -> Result<(), SdkError> {
322 self.spark_wallet
323 .delete_wallet_webhook(&request.webhook_id)
324 .await
325 .map_err(|e| SdkError::Generic(format!("Failed to unregister webhook: {e}")))?;
326 Ok(())
327 }
328
329 pub async fn list_webhooks(&self) -> Result<Vec<Webhook>, SdkError> {
335 let webhooks = self
336 .spark_wallet
337 .list_wallet_webhooks()
338 .await
339 .map_err(|e| SdkError::Generic(format!("Failed to list webhooks: {e}")))?;
340 Ok(webhooks.into_iter().map(Into::into).collect())
341 }
342
343 pub async fn buy_bitcoin(
351 &self,
352 request: BuyBitcoinRequest,
353 ) -> Result<BuyBitcoinResponse, SdkError> {
354 let url = match request {
355 BuyBitcoinRequest::Moonpay {
356 locked_amount_sat,
357 redirect_url,
358 } => {
359 let address = get_deposit_address(&self.spark_wallet, true).await?;
360 self.buy_bitcoin_provider
361 .buy_bitcoin(address, locked_amount_sat, redirect_url)
362 .await
363 .map_err(|e| {
364 SdkError::Generic(format!("Failed to create buy bitcoin URL: {e}"))
365 })?
366 }
367 BuyBitcoinRequest::CashApp { amount_sats } => {
368 if !matches!(self.config.network, Network::Mainnet) {
369 return Err(SdkError::Generic(
370 "CashApp is only available on mainnet".to_string(),
371 ));
372 }
373 let receive_response = self
374 .receive_bolt11_invoice(
375 "Buy Bitcoin via CashApp".to_string(),
376 amount_sats,
377 None,
378 None,
379 )
380 .await?;
381 CashAppProvider::build_url(&receive_response.payment_request)
382 }
383 };
384
385 Ok(BuyBitcoinResponse { url })
386 }
387}