1use crate::{
2 Fee,
3 lnurl::LnurlServerError,
4 persist::{self},
5};
6use bitcoin::consensus::encode::FromHexError;
7use breez_sdk_common::error::ServiceConnectivityError;
8use platform_utils::time::SystemTimeError;
9use serde::{Deserialize, Serialize};
10use spark_wallet::SparkWalletError;
11use std::{convert::Infallible, num::TryFromIntError};
12use thiserror::Error;
13use tracing_subscriber::util::TryInitError;
14
15#[derive(Debug, Error, Clone)]
17#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
18pub enum SdkError {
19 #[error("SparkSdkError: {0}")]
20 SparkError(String),
21
22 #[error("Insufficient funds{}", .token_identifier.as_deref().map(|t| format!(" for token {t}")).unwrap_or_default())]
23 InsufficientFunds {
24 token_identifier: Option<String>,
27 },
28
29 #[error("Invalid UUID: {0}")]
30 InvalidUuid(String),
31
32 #[error("Invalid input: {0}")]
34 InvalidInput(String),
35
36 #[error("Network error: {0}")]
38 NetworkError(String),
39
40 #[error("Storage error: {0}")]
42 StorageError(String),
43
44 #[error("Chain service error: {0}")]
45 ChainServiceError(String),
46
47 #[error(
48 "Max deposit claim fee exceeded for utxo: {tx}:{vout} with max fee: {max_fee:?} and required fee: {required_fee_sats} sats or {required_fee_rate_sat_per_vbyte} sats/vbyte"
49 )]
50 MaxDepositClaimFeeExceeded {
51 tx: String,
52 vout: u32,
53 max_fee: Option<Fee>,
54 required_fee_sats: u64,
55 required_fee_rate_sat_per_vbyte: u64,
56 },
57
58 #[error("Missing utxo: {tx}:{vout}")]
59 MissingUtxo { tx: String, vout: u32 },
60
61 #[error("Lnurl error: {0}")]
62 LnurlError(String),
63
64 #[error("Signer error: {0}")]
65 Signer(String),
66
67 #[error("Optimization is already in progress")]
70 OptimizationAlreadyRunning,
71
72 #[error("Optimization was cancelled by the SDK to free leaves")]
75 OptimizationCancelled,
76
77 #[error("Insufficient CPFP funding: need at least {required_sat} sats")]
79 InsufficientCpfpFunds { required_sat: u64 },
80
81 #[error("Funding UTXO {txid}:{vout} was spent by an unrelated transaction")]
84 FundingUtxoConflict { txid: String, vout: u32 },
85
86 #[error("Error: {0}")]
87 Generic(String),
88}
89
90impl From<crate::chain::ChainServiceError> for SdkError {
91 fn from(e: crate::chain::ChainServiceError) -> Self {
92 SdkError::ChainServiceError(e.to_string())
93 }
94}
95
96impl From<breez_sdk_common::lnurl::error::LnurlError> for SdkError {
97 fn from(e: breez_sdk_common::lnurl::error::LnurlError) -> Self {
98 SdkError::LnurlError(e.to_string())
99 }
100}
101
102impl From<breez_sdk_common::input::ParseError> for SdkError {
103 fn from(e: breez_sdk_common::input::ParseError) -> Self {
104 SdkError::InvalidInput(e.to_string())
105 }
106}
107
108impl From<bitcoin::address::ParseError> for SdkError {
109 fn from(e: bitcoin::address::ParseError) -> Self {
110 SdkError::InvalidInput(e.to_string())
111 }
112}
113
114impl From<flashnet::FlashnetError> for SdkError {
115 fn from(e: flashnet::FlashnetError) -> Self {
116 match e {
117 flashnet::FlashnetError::Network { reason, code } => {
118 let code = match code {
119 Some(c) => format!(" (code: {c})"),
120 None => String::new(),
121 };
122 SdkError::NetworkError(format!("{reason}{code}"))
123 }
124 _ => SdkError::Generic(e.to_string()),
125 }
126 }
127}
128
129impl From<boltz_client::BoltzError> for SdkError {
130 fn from(e: boltz_client::BoltzError) -> Self {
131 use boltz_client::BoltzError;
132 match e {
133 BoltzError::Api { reason, code } => {
134 let code = match code {
135 Some(c) => format!(" (code: {c})"),
136 None => String::new(),
137 };
138 SdkError::NetworkError(format!("Boltz API: {reason}{code}"))
139 }
140 BoltzError::WebSocket(s) => SdkError::NetworkError(format!("Boltz WebSocket: {s}")),
141 BoltzError::Store(s) => SdkError::StorageError(format!("Boltz store: {s}")),
142 BoltzError::AmountOutOfRange { .. }
143 | BoltzError::QuoteExpired
144 | BoltzError::InvalidQuote(_)
145 | BoltzError::QuoteDegradedBeyondSlippage { .. }
146 | BoltzError::DuplicatePreimage => SdkError::InvalidInput(e.to_string()),
147 _ => SdkError::Generic(format!("Boltz: {e}")),
148 }
149 }
150}
151
152impl From<crate::token_conversion::ConversionError> for SdkError {
153 fn from(e: crate::token_conversion::ConversionError) -> Self {
154 use crate::token_conversion::ConversionError;
155 match e {
156 ConversionError::NoPoolsAvailable => {
157 SdkError::Generic("No conversion pools available".to_string())
158 }
159 ConversionError::ConversionFailed(msg)
160 | ConversionError::ValidationFailed(msg)
161 | ConversionError::RefundFailed(msg) => SdkError::Generic(msg),
162 ConversionError::DuplicateTransfer => {
163 SdkError::Generic("Duplicate transfer: conversion already handled".to_string())
164 }
165 ConversionError::Sdk(e) => e,
166 ConversionError::Storage(e) => SdkError::StorageError(e.to_string()),
167 ConversionError::Wallet(e) => SdkError::SparkError(e.to_string()),
168 }
169 }
170}
171
172impl From<persist::StorageError> for SdkError {
173 fn from(e: persist::StorageError) -> Self {
174 match e {
175 persist::StorageError::NotFound => SdkError::InvalidInput("Not found".to_string()),
176 _ => SdkError::StorageError(e.to_string()),
177 }
178 }
179}
180
181impl From<Infallible> for SdkError {
182 fn from(value: Infallible) -> Self {
183 SdkError::Generic(value.to_string())
184 }
185}
186
187impl From<String> for SdkError {
188 fn from(s: String) -> Self {
189 Self::Generic(s)
190 }
191}
192
193impl From<&str> for SdkError {
194 fn from(s: &str) -> Self {
195 Self::Generic(s.to_string())
196 }
197}
198
199impl From<SystemTimeError> for SdkError {
200 fn from(e: SystemTimeError) -> Self {
201 SdkError::Generic(e.to_string())
202 }
203}
204
205impl From<TryFromIntError> for SdkError {
206 fn from(e: TryFromIntError) -> Self {
207 SdkError::Generic(e.to_string())
208 }
209}
210
211impl From<serde_json::Error> for SdkError {
212 fn from(e: serde_json::Error) -> Self {
213 SdkError::Generic(e.to_string())
214 }
215}
216
217impl From<SparkWalletError> for SdkError {
218 fn from(e: SparkWalletError) -> Self {
219 match e {
220 SparkWalletError::InsufficientFunds => SdkError::InsufficientFunds {
221 token_identifier: None,
222 },
223 SparkWalletError::TokenOutputServiceError(
224 spark_wallet::TokenOutputServiceError::InsufficientFunds { token_identifier },
225 ) => SdkError::InsufficientFunds { token_identifier },
226 SparkWalletError::ServiceError(spark_wallet::ServiceError::InvalidInput(msg)) => {
227 SdkError::InvalidInput(msg)
228 }
229 SparkWalletError::ServiceError(
230 spark_wallet::ServiceError::InsufficientCpfpBudget { required_sat },
231 ) => SdkError::InsufficientCpfpFunds { required_sat },
232 SparkWalletError::ServiceError(spark_wallet::ServiceError::FundingUtxoConflict {
233 txid,
234 vout,
235 }) => SdkError::FundingUtxoConflict { txid, vout },
236 _ => SdkError::SparkError(e.to_string()),
237 }
238 }
239}
240
241impl From<spark_wallet::OptimizationError> for SdkError {
242 fn from(e: spark_wallet::OptimizationError) -> Self {
243 match e {
244 spark_wallet::OptimizationError::AlreadyRunning => SdkError::OptimizationAlreadyRunning,
245 spark_wallet::OptimizationError::Cancelled => SdkError::OptimizationCancelled,
246 spark_wallet::OptimizationError::Tree(inner) => SdkError::SparkError(inner.to_string()),
247 }
248 }
249}
250
251impl From<FromHexError> for SdkError {
252 fn from(e: FromHexError) -> Self {
253 SdkError::Generic(e.to_string())
254 }
255}
256
257impl From<uuid::Error> for SdkError {
258 fn from(e: uuid::Error) -> Self {
259 SdkError::InvalidUuid(e.to_string())
260 }
261}
262
263impl From<ServiceConnectivityError> for SdkError {
264 fn from(value: ServiceConnectivityError) -> Self {
265 SdkError::NetworkError(value.to_string())
266 }
267}
268
269impl From<LnurlServerError> for SdkError {
270 fn from(value: LnurlServerError) -> Self {
271 match value {
272 LnurlServerError::InvalidApiKey => {
273 SdkError::InvalidInput("Invalid api key".to_string())
274 }
275 LnurlServerError::Network {
276 statuscode,
277 message,
278 } => SdkError::NetworkError(format!(
279 "network request failed with status {statuscode}: {}",
280 message.unwrap_or(String::new())
281 )),
282 LnurlServerError::RequestFailure(e) => SdkError::NetworkError(e),
283 LnurlServerError::SigningError(e) => {
284 SdkError::Generic(format!("Failed to sign message: {e}"))
285 }
286 }
287 }
288}
289
290impl From<TryInitError> for SdkError {
291 fn from(_value: TryInitError) -> Self {
292 SdkError::Generic("Logging can only be initialized once".to_string())
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize, Error, PartialEq)]
297#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
298pub enum DepositClaimError {
299 #[error(
300 "Max deposit claim fee exceeded for utxo: {tx}:{vout} with max fee: {max_fee:?} and required fee: {required_fee_sats} sats or {required_fee_rate_sat_per_vbyte} sats/vbyte"
301 )]
302 MaxDepositClaimFeeExceeded {
303 tx: String,
304 vout: u32,
305 max_fee: Option<Fee>,
306 required_fee_sats: u64,
307 required_fee_rate_sat_per_vbyte: u64,
308 },
309
310 #[error("Missing utxo: {tx}:{vout}")]
311 MissingUtxo { tx: String, vout: u32 },
312
313 #[error("Generic error: {message}")]
314 Generic { message: String },
315}
316
317impl From<SdkError> for DepositClaimError {
318 fn from(value: SdkError) -> Self {
319 match value {
320 SdkError::MaxDepositClaimFeeExceeded {
321 tx,
322 vout,
323 max_fee,
324 required_fee_sats,
325 required_fee_rate_sat_per_vbyte,
326 } => DepositClaimError::MaxDepositClaimFeeExceeded {
327 tx,
328 vout,
329 max_fee,
330 required_fee_sats,
331 required_fee_rate_sat_per_vbyte,
332 },
333 SdkError::MissingUtxo { tx, vout } => DepositClaimError::MissingUtxo { tx, vout },
334 SdkError::Generic(e) => DepositClaimError::Generic { message: e },
335 _ => DepositClaimError::Generic {
336 message: value.to_string(),
337 },
338 }
339 }
340}
341
342#[derive(Debug, Error, Clone)]
344#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
345pub enum SignerError {
346 #[error("Key derivation error: {0}")]
347 KeyDerivation(String),
348
349 #[error("Signing error: {0}")]
350 Signing(String),
351
352 #[error("Encryption error: {0}")]
353 Encryption(String),
354
355 #[error("Decryption error: {0}")]
356 Decryption(String),
357
358 #[error("Encryption unavailable: {0}")]
359 EncryptionUnavailable(String),
360
361 #[error("FROST error: {0}")]
362 Frost(String),
363
364 #[error("Invalid input: {0}")]
365 InvalidInput(String),
366
367 #[error("Generic signer error: {0}")]
368 Generic(String),
369}
370
371impl From<String> for SignerError {
372 fn from(s: String) -> Self {
373 SignerError::Generic(s)
374 }
375}
376
377impl From<&str> for SignerError {
378 fn from(s: &str) -> Self {
379 SignerError::Generic(s.to_string())
380 }
381}