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