1use anyhow::Error;
2use lwk_wollet::secp256k1;
3use sdk_common::{
4 lightning_with_bolt12::offers::parse::Bolt12SemanticError,
5 prelude::{LnUrlAuthError, LnUrlPayError, LnUrlWithdrawError},
6};
7
8use crate::payjoin::error::PayjoinError;
9
10pub type SdkResult<T, E = SdkError> = Result<T, E>;
11
12#[macro_export]
13macro_rules! ensure_sdk {
14 ($cond:expr, $err:expr) => {
15 if !$cond {
16 return Err($err);
17 }
18 };
19}
20
21#[derive(Debug, thiserror::Error)]
23pub enum SdkError {
24 #[error("Liquid SDK instance is already running")]
25 AlreadyStarted,
26
27 #[error("Error: {err}")]
28 Generic { err: String },
29
30 #[error("Network {network} is not currently supported")]
31 NetworkNotSupported { network: String },
32
33 #[error("Liquid SDK instance is not running")]
34 NotStarted,
35
36 #[error("Service connectivity: {err}")]
37 ServiceConnectivity { err: String },
38}
39impl SdkError {
40 pub fn generic<T: AsRef<str>>(err: T) -> Self {
41 Self::Generic {
42 err: err.as_ref().to_string(),
43 }
44 }
45
46 pub(crate) fn network_not_supported<T: ToString>(network: T) -> Self {
47 Self::NetworkNotSupported {
48 network: network.to_string(),
49 }
50 }
51}
52
53impl From<anyhow::Error> for SdkError {
54 fn from(e: Error) -> Self {
55 SdkError::generic(e.to_string())
56 }
57}
58
59impl From<boltz_client::error::Error> for SdkError {
60 fn from(err: boltz_client::error::Error) -> Self {
61 match err {
62 boltz_client::error::Error::HTTP(e) => {
63 SdkError::generic(format!("Could not contact servers: {e:?}"))
64 }
65 boltz_client::error::Error::HTTPStatusNotSuccess(status, body) => {
66 SdkError::generic(format!("Boltz API returned error status {status}: {body}"))
67 }
68 _ => SdkError::generic(format!("{err:?}")),
69 }
70 }
71}
72
73impl From<secp256k1::Error> for SdkError {
74 fn from(err: secp256k1::Error) -> Self {
75 SdkError::generic(format!("{err:?}"))
76 }
77}
78
79#[derive(thiserror::Error, Debug)]
80pub enum PaymentError {
81 #[error("The specified funds have already been claimed")]
82 AlreadyClaimed,
83
84 #[error("The specified funds have already been sent")]
85 AlreadyPaid,
86
87 #[error("The payment is already in progress")]
88 PaymentInProgress,
89
90 #[error("Amount must be between {min} and {max}")]
91 AmountOutOfRange { min: u64, max: u64 },
92
93 #[error("Amount is missing: {err}")]
94 AmountMissing { err: String },
95
96 #[error("Asset error: {err}")]
97 AssetError { err: String },
98
99 #[error("Invalid network: {err}")]
100 InvalidNetwork { err: String },
101
102 #[error("Generic error: {err}")]
103 Generic { err: String },
104
105 #[error("The provided fees have expired")]
106 InvalidOrExpiredFees,
107
108 #[error("Cannot pay: not enough funds")]
109 InsufficientFunds,
110
111 #[error("Invalid description: {err}")]
112 InvalidDescription { err: String },
113
114 #[error("The specified invoice is not valid: {err}")]
115 InvalidInvoice { err: String },
116
117 #[error("The generated preimage is not valid")]
118 InvalidPreimage,
119
120 #[error("Boltz did not return any pairs from the request")]
121 PairsNotFound,
122
123 #[error("Payment start could not be verified within the configured timeout")]
124 PaymentTimeout,
125
126 #[error("Could not store the swap details locally")]
127 PersistError,
128
129 #[error("Could not process the Receive Payment: {err}")]
130 ReceiveError { err: String },
131
132 #[error("The payment has been refunded. Reason for failure: {err}")]
133 Refunded { err: String, refund_tx_id: String },
134
135 #[error("The payment is a self-transfer, which is not supported")]
136 SelfTransferNotSupported,
137
138 #[error("Could not process the Send Payment: {err}")]
139 SendError { err: String },
140
141 #[error("Could not sign the transaction: {err}")]
142 SignerError { err: String },
143}
144impl PaymentError {
145 pub(crate) fn asset_error<S: AsRef<str>>(err: S) -> Self {
146 Self::AssetError {
147 err: err.as_ref().to_string(),
148 }
149 }
150
151 pub(crate) fn generic<S: AsRef<str>>(err: S) -> Self {
152 Self::Generic {
153 err: err.as_ref().to_string(),
154 }
155 }
156
157 pub(crate) fn invalid_invoice<S: AsRef<str>>(err: S) -> Self {
158 Self::InvalidInvoice {
159 err: err.as_ref().to_string(),
160 }
161 }
162
163 pub(crate) fn invalid_network<S: AsRef<str>>(err: S) -> Self {
164 Self::InvalidNetwork {
165 err: err.as_ref().to_string(),
166 }
167 }
168
169 pub(crate) fn receive_error<S: AsRef<str>>(err: S) -> Self {
170 Self::ReceiveError {
171 err: err.as_ref().to_string(),
172 }
173 }
174
175 pub(crate) fn amount_missing<S: AsRef<str>>(err: S) -> Self {
176 Self::AmountMissing {
177 err: err.as_ref().to_string(),
178 }
179 }
180}
181
182impl From<Bolt12SemanticError> for PaymentError {
183 fn from(err: Bolt12SemanticError) -> Self {
184 PaymentError::Generic {
185 err: format!("Failed to create BOLT12 invoice: {err:?}"),
186 }
187 }
188}
189
190impl From<boltz_client::error::Error> for PaymentError {
191 fn from(err: boltz_client::error::Error) -> Self {
192 match err {
193 boltz_client::error::Error::HTTP(e) => PaymentError::Generic {
194 err: format!("Could not contact servers: {e:?}"),
195 },
196 boltz_client::error::Error::HTTPStatusNotSuccess(status, body) => {
197 PaymentError::Generic {
198 err: format!("Boltz API returned error status {status}: {body}"),
199 }
200 }
201 _ => PaymentError::Generic {
202 err: format!("{err:?}"),
203 },
204 }
205 }
206}
207
208impl From<boltz_client::bitcoin::hex::HexToArrayError> for PaymentError {
209 fn from(err: boltz_client::bitcoin::hex::HexToArrayError) -> Self {
210 PaymentError::Generic {
211 err: format!("{err:?}"),
212 }
213 }
214}
215
216impl From<lwk_wollet::Error> for PaymentError {
217 fn from(err: lwk_wollet::Error) -> Self {
218 match err {
219 lwk_wollet::Error::InsufficientFunds { .. } => PaymentError::InsufficientFunds,
220 lwk_wollet::Error::TooManyInputs(count) => PaymentError::Generic {
221 err: format!(
222 "Transaction would require {count} inputs, which exceeds the maximum of 256 \
223 supported by the Liquid surjection proof. Consolidate UTXOs and try again."
224 ),
225 },
226 _ => PaymentError::Generic {
227 err: format!("{err:?}"),
228 },
229 }
230 }
231}
232
233#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
234impl From<lwk_wollet::UrlError> for PaymentError {
235 fn from(err: lwk_wollet::UrlError) -> Self {
236 PaymentError::Generic {
237 err: format!("{err:?}"),
238 }
239 }
240}
241
242impl From<lwk_signer::SignerError> for PaymentError {
243 fn from(err: lwk_signer::SignerError) -> Self {
244 PaymentError::SignerError {
245 err: format!("{err:?}"),
246 }
247 }
248}
249
250impl From<anyhow::Error> for PaymentError {
251 fn from(err: anyhow::Error) -> Self {
252 Self::Generic {
253 err: err.to_string(),
254 }
255 }
256}
257
258impl From<PayjoinError> for PaymentError {
259 fn from(err: PayjoinError) -> Self {
260 match err {
261 PayjoinError::InsufficientFunds => PaymentError::InsufficientFunds,
262 _ => PaymentError::Generic {
263 err: format!("{err:?}"),
264 },
265 }
266 }
267}
268
269impl From<rusqlite::Error> for PaymentError {
270 fn from(err: rusqlite::Error) -> Self {
271 log::error!("Persister returned error: {err:?}");
272 Self::PersistError
273 }
274}
275
276impl From<SdkError> for PaymentError {
277 fn from(err: SdkError) -> Self {
278 Self::Generic {
279 err: err.to_string(),
280 }
281 }
282}
283
284impl From<sdk_common::bitcoin::util::bip32::Error> for PaymentError {
285 fn from(err: sdk_common::bitcoin::util::bip32::Error) -> Self {
286 Self::SignerError {
287 err: err.to_string(),
288 }
289 }
290}
291
292impl From<secp256k1::Error> for PaymentError {
293 fn from(err: secp256k1::Error) -> Self {
294 Self::Generic {
295 err: err.to_string(),
296 }
297 }
298}
299
300impl From<PaymentError> for LnUrlAuthError {
301 fn from(err: PaymentError) -> Self {
302 Self::Generic {
303 err: err.to_string(),
304 }
305 }
306}
307
308impl From<PaymentError> for LnUrlPayError {
309 fn from(err: PaymentError) -> Self {
310 match err {
311 PaymentError::AlreadyPaid => Self::AlreadyPaid,
312 PaymentError::AmountOutOfRange { min, max } => Self::InvalidAmount {
313 err: format!("Amount must be between {min} and {max}"),
314 },
315 PaymentError::AmountMissing { err } => Self::InvalidAmount {
316 err: format!("Amount is missing: {err}"),
317 },
318 PaymentError::InvalidNetwork { err } => Self::InvalidNetwork { err },
319 PaymentError::InsufficientFunds => Self::InsufficientBalance { err: String::new() },
320 PaymentError::InvalidInvoice { err } => Self::InvalidInvoice { err },
321 PaymentError::PaymentTimeout => Self::PaymentTimeout { err: String::new() },
322 _ => Self::Generic {
323 err: err.to_string(),
324 },
325 }
326 }
327}
328
329impl From<PaymentError> for LnUrlWithdrawError {
330 fn from(err: PaymentError) -> Self {
331 Self::Generic {
332 err: err.to_string(),
333 }
334 }
335}
336
337pub(crate) fn is_txn_mempool_conflict_error(err: &Error) -> bool {
338 err.to_string().contains("txn-mempool-conflict")
339}