1use std::path::PathBuf;
4
5use cdk_common::CurrencyUnit;
6use thiserror::Error;
7use uuid::Uuid;
8
9#[derive(Debug, Error)]
11pub enum Error {
12 #[error("Fee estimation failed: {0}")]
14 FeeEstimationFailed(String),
15 #[error("Fee estimation overflow")]
17 FeeEstimationOverflow,
18 #[error("Fee estimation unavailable")]
20 FeeEstimationUnavailable,
21 #[error("No spendable UTXOs available for onchain payment quote")]
23 NoSpendableUtxos,
24 #[error("Start called but background tasks are already running")]
26 AlreadyStarted,
27
28 #[error("Invalid configuration: {0}")]
30 InvalidConfig(String),
31
32 #[error("Unsupported payment type for onchain backend")]
34 UnsupportedOnchain,
35
36 #[error("unknown fee_index {0}; expected one of the configured BDK fee options")]
38 UnknownFeeIndex(u32),
39
40 #[error("JSON error: {0}")]
42 Json(#[from] serde_json::Error),
43
44 #[error("Amount conversion error: {0}")]
46 AmountConversion(#[from] cdk_common::amount::Error),
47
48 #[error("Amount unit {actual} does not match payment unit {expected}")]
50 AmountUnitMismatch {
51 expected: CurrencyUnit,
53 actual: CurrencyUnit,
55 },
56
57 #[error("Onchain payment amount {amount_msat} msat is not a whole number of satoshis")]
59 FractionalSatoshiAmount {
60 amount_msat: u64,
62 },
63
64 #[error("Database error: {0}")]
66 Database(#[from] bdk_wallet::rusqlite::Error),
67
68 #[error("Persisted BDK wallet database is missing at {path}")]
70 ExistingWalletMissing {
71 path: PathBuf,
73 },
74
75 #[error("Persisted BDK wallet database at {path} does not contain an initialized wallet")]
77 ExistingWalletNotInitialized {
78 path: PathBuf,
80 },
81
82 #[error("Wallet error: {0}")]
84 Wallet(String),
85
86 #[cfg(feature = "bitcoin-rpc")]
88 #[error("Bitcoin RPC error: {0}")]
89 BitcoinRpc(#[from] bdk_bitcoind_rpc::bitcoincore_rpc::Error),
90
91 #[cfg(feature = "bitcoin-rpc")]
93 #[error("Failed to determine the Bitcoin Core chain tip for a fresh wallet: {source}")]
94 ChainTipFetchFailed {
95 #[source]
97 source: bdk_bitcoind_rpc::bitcoincore_rpc::Error,
98 },
99
100 #[error("Wallet rescan height {requested} is above the current chain tip {tip}")]
102 WalletRescanHeightTooHigh {
103 requested: u32,
105 tip: u32,
107 },
108
109 #[error("Esplora error: {0}")]
111 Esplora(String),
112
113 #[cfg(feature = "electrum")]
115 #[error("Electrum error: {0}")]
116 Electrum(String),
117
118 #[error("Bip32 key derivation error: {0}")]
120 Bip32(#[from] bdk_wallet::bitcoin::bip32::Error),
121
122 #[error("Key derivation error: {0}")]
124 KeyDerivation(#[from] bdk_wallet::keys::KeyError),
125
126 #[error("Could not sign transaction")]
128 CouldNotSign,
129
130 #[error("Path error")]
132 Path,
133
134 #[error("IO error: {0}")]
136 Io(#[from] std::io::Error),
137
138 #[error("KV Store error: {0}")]
140 KvStore(#[from] cdk_common::database::Error),
141
142 #[error("Could not find matching output vout in transaction")]
144 VoutNotFound,
145
146 #[error("Send intent not found: {0}")]
148 SendIntentNotFound(Uuid),
149
150 #[error("Send batch not found: {0}")]
152 SendBatchNotFound(Uuid),
153
154 #[error("Send intent already exists for quote id: {0}")]
156 DuplicateQuoteId(String),
157
158 #[error("Batch fee {actual_fee} exceeds combined max fee {max_fee}")]
160 BatchFeeTooHigh {
161 actual_fee: u64,
163 max_fee: u64,
165 },
166
167 #[error("Estimated fee {estimated_fee} exceeds max fee {max_fee}")]
169 EstimatedFeeTooHigh {
170 estimated_fee: u64,
172 max_fee: u64,
174 },
175
176 #[error("No valid fee allocation for batch")]
178 NoValidFeeAllocation,
179
180 #[error("Requested output amount {amount} sats is below dust limit {dust_limit} sats")]
182 DustOutput {
183 amount: u64,
185 dust_limit: u64,
187 },
188
189 #[error("Requested send amount {amount} sats is below minimum {min} sats")]
191 AmountBelowMinimumSend {
192 amount: u64,
194 min: u64,
196 },
197
198 #[error("Batch {batch_id} is missing an output assignment for intent {intent_id}")]
203 BatchAssignmentMissing {
204 batch_id: Uuid,
206 intent_id: Uuid,
208 },
209
210 #[error("Receive intent not found: {0}")]
212 ReceiveIntentNotFound(Uuid),
213
214 #[error("Receive address not found: {0}")]
216 ReceiveAddressNotFound(String),
217
218 #[error("Could not reserve a fresh receive address after {attempts} attempts")]
220 ReceiveAddressReservationExhausted {
221 attempts: usize,
223 },
224
225 #[error("Database error")]
227 BdkPersist,
228}
229
230impl From<Error> for cdk_common::payment::Error {
231 fn from(e: Error) -> Self {
232 Self::Onchain(Box::new(e))
233 }
234}
235
236impl Error {
237 pub fn is_transient(&self) -> bool {
244 match self {
245 #[cfg(feature = "bitcoin-rpc")]
251 Self::BitcoinRpc(_) => true,
252 #[cfg(feature = "electrum")]
253 Self::Electrum(_) => true,
254 Self::Esplora(_) => true,
255 Self::Io(e) => matches!(
256 e.kind(),
257 std::io::ErrorKind::TimedOut
258 | std::io::ErrorKind::ConnectionRefused
259 | std::io::ErrorKind::ConnectionReset
260 | std::io::ErrorKind::ConnectionAborted
261 | std::io::ErrorKind::NotConnected
262 | std::io::ErrorKind::BrokenPipe
263 | std::io::ErrorKind::Interrupted
264 | std::io::ErrorKind::UnexpectedEof
265 | std::io::ErrorKind::WouldBlock
266 ),
267 _ => false,
268 }
269 }
270}