1use std::collections::HashMap;
4
5use bdk_wallet::bitcoin::Address;
6use bdk_wallet::chain::ChainPosition;
7use bdk_wallet::KeychainKind;
8
9use crate::{CdkBdk, Error};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct WalletBalance {
14 pub network: String,
16 pub synced_height: u32,
18 pub confirmed_sat: u64,
20 pub trusted_pending_sat: u64,
22 pub untrusted_pending_sat: u64,
24 pub immature_sat: u64,
26 pub trusted_spendable_sat: u64,
28 pub total_sat: u64,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct WalletTransaction {
35 pub txid: String,
37 pub inputs: Vec<WalletTransactionInput>,
39 pub outputs: Vec<WalletTransactionOutput>,
41 pub received_sat: u64,
43 pub sent_sat: u64,
45 pub fee_sat: Option<u64>,
47 pub balance_delta_sat: i64,
49 pub confirmation_height: Option<u32>,
51 pub confirmation_time: Option<u64>,
53 pub first_seen: Option<u64>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct WalletTransactionInput {
60 pub txid: String,
62 pub vout: u32,
64 pub amount_sat: Option<u64>,
66 pub address: Option<String>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct WalletTransactionOutput {
73 pub vout: u32,
75 pub address: String,
77 pub amount_sat: u64,
79 pub quote_id: Option<String>,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum WalletKeychain {
86 External,
88 Internal,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct WalletAddress {
95 pub address: String,
97 pub keychain: WalletKeychain,
99 pub derivation_index: u32,
101 pub used: bool,
103 pub balance_sat: u64,
105 pub confirmed_balance_sat: u64,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct WalletPage<T> {
112 pub items: Vec<T>,
114 pub total: u64,
116}
117
118impl CdkBdk {
119 pub async fn create_operator_deposit_address(&self) -> Result<String, Error> {
121 let mut wallet_with_db = self.wallet_with_db.lock().await;
122 let address = wallet_with_db
123 .wallet
124 .reveal_next_address(KeychainKind::External)
125 .address
126 .to_string();
127
128 wallet_with_db.persist().map_err(|err| {
129 tracing::warn!("Could not persist to bdk db: {}", err);
130 Error::BdkPersist
131 })?;
132
133 Ok(address)
134 }
135
136 pub async fn wallet_balance(&self) -> WalletBalance {
138 let wallet_with_db = self.wallet_with_db.lock().await;
139 let balance = wallet_with_db.wallet.balance();
140
141 WalletBalance {
142 network: self.network.to_string(),
143 synced_height: wallet_with_db.wallet.latest_checkpoint().height(),
144 confirmed_sat: balance.confirmed.to_sat(),
145 trusted_pending_sat: balance.trusted_pending.to_sat(),
146 untrusted_pending_sat: balance.untrusted_pending.to_sat(),
147 immature_sat: balance.immature.to_sat(),
148 trusted_spendable_sat: balance.trusted_spendable().to_sat(),
149 total_sat: balance.total().to_sat(),
150 }
151 }
152
153 pub async fn wallet_transactions(
155 &self,
156 offset: usize,
157 limit: usize,
158 ) -> Result<WalletPage<WalletTransaction>, Error> {
159 self.storage.ensure_send_outpoint_quote_id_index().await?;
160 let wallet_with_db = self.wallet_with_db.lock().await;
161 let wallet = &wallet_with_db.wallet;
162 let transactions = wallet.transactions_sort_by(|left, right| {
163 right
164 .chain_position
165 .cmp(&left.chain_position)
166 .then_with(|| right.tx_node.txid.cmp(&left.tx_node.txid))
167 });
168 let total = u64::try_from(transactions.len())
169 .map_err(|_| Error::Wallet("Transaction count exceeds u64".to_string()))?;
170
171 let mut items = transactions
172 .into_iter()
173 .skip(offset)
174 .take(limit)
175 .map(|transaction| {
176 let tx = &transaction.tx_node.tx;
177 let (sent, received) = wallet.sent_and_received(tx);
178 let received_sat = received.to_sat();
179 let sent_sat = sent.to_sat();
180 let received_signed = i64::try_from(received_sat)
181 .map_err(|_| Error::Wallet("Received value exceeds i64".to_string()))?;
182 let sent_signed = i64::try_from(sent_sat)
183 .map_err(|_| Error::Wallet("Sent value exceeds i64".to_string()))?;
184 let is_outgoing = sent_sat > 0;
185 let inputs = tx
186 .input
187 .iter()
188 .map(|input| {
189 let outpoint = input.previous_output;
190 let previous_output = wallet.tx_graph().get_txout(outpoint);
191 let amount_sat = previous_output.map(|output| output.value.to_sat());
192 let address = previous_output
193 .filter(|output| wallet.is_mine(output.script_pubkey.clone()))
194 .and_then(|output| {
195 Address::from_script(output.script_pubkey.as_script(), self.network)
196 .ok()
197 })
198 .map(|address| address.to_string());
199
200 WalletTransactionInput {
201 txid: outpoint.txid.to_string(),
202 vout: outpoint.vout,
203 amount_sat,
204 address,
205 }
206 })
207 .collect();
208 let outputs = tx
209 .output
210 .iter()
211 .enumerate()
212 .filter(|(_, output)| {
213 let is_wallet_output = wallet.is_mine(output.script_pubkey.clone());
214 is_outgoing != is_wallet_output
215 })
216 .filter_map(|(vout, output)| {
217 Address::from_script(output.script_pubkey.as_script(), self.network)
218 .ok()
219 .map(|address| (vout, output, address))
220 })
221 .map(|(vout, output, address)| {
222 let vout = u32::try_from(vout).map_err(|_| {
223 Error::Wallet("Transaction output index exceeds u32".to_string())
224 })?;
225 let address = address.to_string();
226 Ok(WalletTransactionOutput {
227 vout,
228 address,
229 amount_sat: output.value.to_sat(),
230 quote_id: None,
231 })
232 })
233 .collect::<Result<Vec<_>, Error>>()?;
234
235 let (confirmation_height, confirmation_time, first_seen) =
236 match transaction.chain_position {
237 ChainPosition::Confirmed { anchor, .. } => (
238 Some(anchor.block_id.height),
239 Some(anchor.confirmation_time),
240 None,
241 ),
242 ChainPosition::Unconfirmed { first_seen, .. } => (None, None, first_seen),
243 };
244
245 Ok(WalletTransaction {
246 txid: transaction.tx_node.txid.to_string(),
247 inputs,
248 outputs,
249 received_sat,
250 sent_sat,
251 fee_sat: wallet.calculate_fee(tx).ok().map(|fee| fee.to_sat()),
252 balance_delta_sat: received_signed - sent_signed,
253 confirmation_height,
254 confirmation_time,
255 first_seen,
256 })
257 })
258 .collect::<Result<Vec<_>, Error>>()?;
259 drop(wallet_with_db);
260
261 for transaction in &mut items {
262 for output in &mut transaction.outputs {
263 output.quote_id = match transaction.sent_sat > 0 {
264 true => {
265 let outpoint = format!("{}:{}", transaction.txid, output.vout);
266 self.storage
267 .get_quote_id_by_send_outpoint(&outpoint)
268 .await?
269 }
270 false => {
271 self.storage
272 .get_quote_id_by_receive_address(&output.address)
273 .await?
274 }
275 };
276 }
277 }
278
279 Ok(WalletPage { items, total })
280 }
281
282 pub async fn wallet_addresses(
284 &self,
285 offset: usize,
286 limit: usize,
287 ) -> Result<WalletPage<WalletAddress>, Error> {
288 let wallet_with_db = self.wallet_with_db.lock().await;
289 let wallet = &wallet_with_db.wallet;
290 let mut balances = HashMap::<(KeychainKind, u32), (u64, u64)>::new();
291
292 for output in wallet.list_unspent() {
293 let entry = balances
294 .entry((output.keychain, output.derivation_index))
295 .or_default();
296 entry.0 = entry
297 .0
298 .checked_add(output.txout.value.to_sat())
299 .ok_or_else(|| Error::Wallet("Address balance overflow".to_string()))?;
300 if output.chain_position.is_confirmed() {
301 entry.1 = entry
302 .1
303 .checked_add(output.txout.value.to_sat())
304 .ok_or_else(|| Error::Wallet("Address balance overflow".to_string()))?;
305 }
306 }
307
308 let keychains = [
309 (KeychainKind::External, WalletKeychain::External),
310 (KeychainKind::Internal, WalletKeychain::Internal),
311 ];
312 let total = keychains.iter().try_fold(0_u64, |total, (keychain, _)| {
313 let keychain_total =
314 u64::try_from(wallet.spk_index().revealed_keychain_spks(*keychain).count())
315 .map_err(|_| Error::Wallet("Address count exceeds u64".to_string()))?;
316 total
317 .checked_add(keychain_total)
318 .ok_or_else(|| Error::Wallet("Address count exceeds u64".to_string()))
319 })?;
320
321 let items = keychains
322 .into_iter()
323 .flat_map(|(keychain, wallet_keychain)| {
324 wallet.spk_index().revealed_keychain_spks(keychain).map(
325 move |(derivation_index, script)| {
326 (keychain, wallet_keychain, derivation_index, script)
327 },
328 )
329 })
330 .skip(offset)
331 .take(limit)
332 .map(|(keychain, wallet_keychain, derivation_index, script)| {
333 let address = Address::from_script(&script, self.network)
334 .map_err(|err| Error::Wallet(err.to_string()))?;
335 let (balance_sat, confirmed_balance_sat) = balances
336 .get(&(keychain, derivation_index))
337 .copied()
338 .unwrap_or_default();
339
340 Ok(WalletAddress {
341 address: address.to_string(),
342 keychain: wallet_keychain,
343 derivation_index,
344 used: wallet.spk_index().is_used(keychain, derivation_index),
345 balance_sat,
346 confirmed_balance_sat,
347 })
348 })
349 .collect::<Result<Vec<_>, Error>>()?;
350
351 Ok(WalletPage { items, total })
352 }
353}