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