use crate::builder::ZincWallet;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct InscriptionDetails {
pub id: String,
pub number: i64,
pub content_type: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct TxItem {
pub txid: String,
pub amount_sats: i64,
pub fee_sats: u64,
pub confirmation_time: Option<u64>,
pub tx_type: String, #[serde(default)]
pub inscriptions: Vec<InscriptionDetails>,
#[serde(default)]
pub parent_txids: Vec<String>,
pub index: usize,
}
impl ZincWallet {
pub fn get_transactions(&self, limit: usize) -> Vec<TxItem> {
let mut items = Vec::new();
self.collect_txs_from_wallet(&self.vault_wallet, &mut items);
if let Some(payment_wallet) = &self.payment_wallet {
self.collect_txs_from_wallet(payment_wallet, &mut items);
}
let mut combined: std::collections::HashMap<String, TxItem> =
std::collections::HashMap::new();
for item in items {
combined
.entry(item.txid.clone())
.and_modify(|existing| {
existing.amount_sats += item.amount_sats;
if item.confirmation_time > existing.confirmation_time {
existing.confirmation_time = item.confirmation_time;
}
if item.index > existing.index {
existing.index = item.index;
}
for new_ins in &item.inscriptions {
if !existing.inscriptions.iter().any(|e| e.id == new_ins.id) {
existing.inscriptions.push(new_ins.clone());
}
}
let mut merged_parent_txids: BTreeSet<String> =
existing.parent_txids.iter().cloned().collect();
merged_parent_txids.extend(item.parent_txids.iter().cloned());
existing.parent_txids = merged_parent_txids.into_iter().collect();
})
.or_insert(item);
}
let mut final_items: Vec<TxItem> = combined.into_values().collect();
final_items.sort_by(|a, b| {
let a_pending = a.confirmation_time.is_none();
let b_pending = b.confirmation_time.is_none();
if a_pending != b_pending {
return if a_pending {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
};
}
match (a.confirmation_time, b.confirmation_time) {
(Some(ta), Some(tb)) if ta != tb => tb.cmp(&ta), _ => {
let idx_order = b.index.cmp(&a.index);
if idx_order == std::cmp::Ordering::Equal {
b.txid.cmp(&a.txid)
} else {
idx_order
}
}
}
});
final_items.into_iter().take(limit).collect()
}
fn collect_txs_from_wallet(&self, wallet: &bdk_wallet::Wallet, items: &mut Vec<TxItem>) {
for (i, tx) in wallet.transactions().enumerate() {
let (sent, received) = wallet.sent_and_received(&tx.tx_node.tx);
#[allow(clippy::cast_possible_wrap)]
let amount_sats = received.to_sat() as i64 - sent.to_sat() as i64;
let fee_sats = wallet
.calculate_fee(&tx.tx_node.tx)
.map(bitcoin::Amount::to_sat)
.unwrap_or(0);
let confirmation_time = match tx.chain_position {
bdk_chain::ChainPosition::Confirmed { anchor, .. } => {
Some(anchor.confirmation_time)
}
bdk_chain::ChainPosition::Unconfirmed { .. } => None,
};
let inscriptions = self.get_inscription_details(&tx.tx_node.tx, tx.tx_node.txid);
let parent_txids = tx
.tx_node
.tx
.input
.iter()
.map(|input| input.previous_output.txid.to_string())
.collect::<BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>();
items.push(TxItem {
txid: tx.tx_node.txid.to_string(),
amount_sats,
fee_sats,
confirmation_time,
tx_type: if amount_sats >= 0 {
"receive".to_string()
} else {
"send".to_string()
},
inscriptions,
parent_txids,
index: i,
});
}
}
fn get_inscription_details(
&self,
tx: &bitcoin::Transaction,
txid: bitcoin::Txid,
) -> Vec<InscriptionDetails> {
let mut results = Vec::new();
for (i, _) in tx.output.iter().enumerate() {
let vout = match u32::try_from(i) {
Ok(v) => v,
Err(_) => continue,
};
let outpoint = bitcoin::OutPoint::new(txid, vout);
if let Some(ins) = self
.inscriptions
.iter()
.find(|ins| ins.satpoint.outpoint == outpoint)
{
results.push(InscriptionDetails {
id: ins.id.clone(),
number: ins.number,
content_type: ins.content_type.clone(),
});
}
}
results
}
}
#[cfg(test)]
mod tests {
use crate::builder::{Seed64, WalletBuilder};
use bitcoin::Network;
#[test]
fn test_get_transactions_empty() {
let seed = [0u8; 64];
let wallet = WalletBuilder::from_seed(Network::Regtest, Seed64::from_array(seed))
.build()
.unwrap();
let txs = wallet.get_transactions(50);
assert_eq!(txs.len(), 0);
}
}