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