1use bitcoin::secp256k1::{PublicKey, ecdsa::Signature};
2use std::str::FromStr;
3use tracing::{debug, 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 if self.shutdown_sender.send(()).is_err() {
64 debug!("No shutdown receivers; SDK has no background tasks to stop");
70 return Ok(());
71 }
72
73 self.shutdown_sender.closed().await;
74 info!("Breez SDK disconnected");
75 Ok(())
76 }
77
78 pub async fn parse(&self, input: &str) -> Result<InputType, SdkError> {
79 parse_input(input, Some(self.external_input_parsers.clone())).await
80 }
81
82 #[allow(unused_variables)]
84 pub async fn get_info(&self, request: GetInfoRequest) -> Result<GetInfoResponse, SdkError> {
85 self.runtime.get_info(self, request).await
86 }
87
88 pub async fn list_fiat_currencies(&self) -> Result<ListFiatCurrenciesResponse, SdkError> {
91 let currencies = self
92 .fiat_service
93 .fetch_fiat_currencies()
94 .await?
95 .into_iter()
96 .map(From::from)
97 .collect();
98 Ok(ListFiatCurrenciesResponse { currencies })
99 }
100
101 pub async fn list_fiat_rates(&self) -> Result<ListFiatRatesResponse, SdkError> {
103 let rates = self
104 .fiat_service
105 .fetch_fiat_rates()
106 .await?
107 .into_iter()
108 .map(From::from)
109 .collect();
110 Ok(ListFiatRatesResponse { rates })
111 }
112
113 pub async fn recommended_fees(&self) -> Result<RecommendedFees, SdkError> {
115 Ok(self.chain_service.recommended_fees().await?)
116 }
117
118 pub async fn get_tokens_metadata(
125 &self,
126 request: GetTokensMetadataRequest,
127 ) -> Result<GetTokensMetadataResponse, SdkError> {
128 let metadata = get_tokens_metadata_cached_or_query(
129 &self.spark_wallet,
130 &ObjectCacheRepository::new(self.storage.clone()),
131 &request
132 .token_identifiers
133 .iter()
134 .map(String::as_str)
135 .collect::<Vec<_>>(),
136 )
137 .await?;
138 Ok(GetTokensMetadataResponse {
139 tokens_metadata: metadata,
140 })
141 }
142
143 pub async fn sign_message(
147 &self,
148 request: SignMessageRequest,
149 ) -> Result<SignMessageResponse, SdkError> {
150 use bitcoin::hex::DisplayHex;
151
152 let pubkey = self.spark_wallet.get_identity_public_key().to_string();
153 let signature = self.spark_wallet.sign_message(&request.message).await?;
154 let signature_hex = if request.compact {
155 signature.serialize_compact().to_lower_hex_string()
156 } else {
157 signature.serialize_der().to_lower_hex_string()
158 };
159
160 Ok(SignMessageResponse {
161 pubkey,
162 signature: signature_hex,
163 })
164 }
165
166 pub async fn check_message(
170 &self,
171 request: CheckMessageRequest,
172 ) -> Result<CheckMessageResponse, SdkError> {
173 let pubkey = PublicKey::from_str(&request.pubkey)
174 .map_err(|_| SdkError::InvalidInput("Invalid public key".to_string()))?;
175 let signature_bytes = hex::decode(&request.signature)
176 .map_err(|_| SdkError::InvalidInput("Not a valid hex encoded signature".to_string()))?;
177 let signature = Signature::from_der(&signature_bytes)
178 .or_else(|_| Signature::from_compact(&signature_bytes))
179 .map_err(|_| {
180 SdkError::InvalidInput("Not a valid DER or compact encoded signature".to_string())
181 })?;
182
183 let is_valid = self
184 .spark_wallet
185 .verify_message(&request.message, &signature, &pubkey)
186 .await
187 .is_ok();
188 Ok(CheckMessageResponse { is_valid })
189 }
190
191 pub async fn get_user_settings(&self) -> Result<UserSettings, SdkError> {
195 self.maybe_ensure_spark_private_mode_initialized().await?;
197
198 let spark_user_settings = self.spark_wallet.query_wallet_settings().await?;
199
200 let stable_balance_active_label = match &self.stable_balance {
201 Some(sb) => sb.get_active_label().await,
202 None => None,
203 };
204
205 Ok(UserSettings {
206 spark_private_mode_enabled: spark_user_settings.private_enabled,
207 stable_balance_active_label,
208 })
209 }
210
211 pub async fn update_user_settings(
215 &self,
216 request: UpdateUserSettingsRequest,
217 ) -> Result<(), SdkError> {
218 if let Some(spark_private_mode_enabled) = request.spark_private_mode_enabled {
219 self.spark_wallet
220 .update_wallet_settings(spark_private_mode_enabled)
221 .await?;
222 }
223
224 if let Some(active_label) = request.stable_balance_active_label {
225 let sb = self
226 .stable_balance
227 .as_ref()
228 .ok_or_else(|| SdkError::Generic("Stable balance is not configured".to_string()))?;
229 let label = if let StableBalanceActiveLabel::Set { label } = active_label {
230 Some(label)
231 } else {
232 None
233 };
234 sb.set_active_token(label).await?;
235 }
236
237 Ok(())
238 }
239
240 pub fn get_token_issuer(&self) -> TokenIssuer {
242 TokenIssuer::new(self.spark_wallet.clone(), self.storage.clone())
243 }
244
245 pub async fn start_leaf_optimization(&self) {
251 self.spark_wallet.start_leaf_optimization().await;
252 }
253
254 pub async fn cancel_leaf_optimization(&self) -> Result<(), SdkError> {
263 self.spark_wallet.cancel_leaf_optimization().await?;
264 Ok(())
265 }
266
267 pub fn get_leaf_optimization_progress(&self) -> OptimizationProgress {
269 self.spark_wallet.get_leaf_optimization_progress().into()
270 }
271
272 pub async fn register_webhook(
286 &self,
287 request: RegisterWebhookRequest,
288 ) -> Result<RegisterWebhookResponse, SdkError> {
289 let event_types = request.event_types.into_iter().map(Into::into).collect();
290 let webhook_id = self
291 .spark_wallet
292 .register_wallet_webhook(&request.url, &request.secret, event_types)
293 .await
294 .map_err(|e| SdkError::Generic(format!("Failed to register webhook: {e}")))?;
295 Ok(RegisterWebhookResponse { webhook_id })
296 }
297
298 pub async fn unregister_webhook(
307 &self,
308 request: UnregisterWebhookRequest,
309 ) -> Result<(), SdkError> {
310 self.spark_wallet
311 .delete_wallet_webhook(&request.webhook_id)
312 .await
313 .map_err(|e| SdkError::Generic(format!("Failed to unregister webhook: {e}")))?;
314 Ok(())
315 }
316
317 pub async fn list_webhooks(&self) -> Result<Vec<Webhook>, SdkError> {
323 let webhooks = self
324 .spark_wallet
325 .list_wallet_webhooks()
326 .await
327 .map_err(|e| SdkError::Generic(format!("Failed to list webhooks: {e}")))?;
328 Ok(webhooks.into_iter().map(Into::into).collect())
329 }
330
331 pub async fn buy_bitcoin(
339 &self,
340 request: BuyBitcoinRequest,
341 ) -> Result<BuyBitcoinResponse, SdkError> {
342 let url = match request {
343 BuyBitcoinRequest::Moonpay {
344 locked_amount_sat,
345 redirect_url,
346 } => {
347 let address = get_deposit_address(&self.spark_wallet, true).await?;
348 self.buy_bitcoin_provider
349 .buy_bitcoin(address, locked_amount_sat, redirect_url)
350 .await
351 .map_err(|e| {
352 SdkError::Generic(format!("Failed to create buy bitcoin URL: {e}"))
353 })?
354 }
355 BuyBitcoinRequest::CashApp { amount_sats } => {
356 if !matches!(self.config.network, Network::Mainnet) {
357 return Err(SdkError::Generic(
358 "CashApp is only available on mainnet".to_string(),
359 ));
360 }
361 if amount_sats == 0 {
362 return Err(SdkError::Generic(
363 "CashApp requires a non-zero amount".to_string(),
364 ));
365 }
366 let receive_response = self
367 .receive_bolt11_invoice(
368 "Buy Bitcoin via CashApp".to_string(),
369 Some(amount_sats),
370 None,
371 None,
372 )
373 .await?;
374 CashAppProvider::build_url(&receive_response.payment_request)
375 }
376 };
377
378 Ok(BuyBitcoinResponse { url })
379 }
380}