1use std::cmp::Ordering;
24use std::fmt::{self, Display, Formatter, LowerHex};
25use std::num::ParseIntError;
26use std::str::FromStr;
27
28use amplify::hex;
29use amplify::hex::FromHex;
30use bpstd::{
31 Address, BlockHash, BlockHeader, BlockHeight, DerivedAddr, Keychain, LockTime, NormalIndex,
32 Outpoint, Sats, ScriptPubkey, SeqNo, SigScript, Terminal, TxVer, Txid, Witness,
33};
34use psbt::{Prevout, Utxo};
35
36#[cfg_attr(
37 feature = "serde",
38 derive(serde::Serialize, serde::Deserialize),
39 serde(crate = "serde_crate", rename_all = "camelCase")
40)]
41#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
42pub struct BlockInfo {
43 pub mined: MiningInfo,
44 pub header: BlockHeader,
45 pub difficulty: u8,
46 pub tx_count: u32,
47 pub size: u32,
48 pub weight: u32,
49 pub mediantime: u32,
50}
51
52impl Ord for BlockInfo {
53 fn cmp(&self, other: &Self) -> Ordering { self.mined.cmp(&other.mined) }
54}
55
56impl PartialOrd for BlockInfo {
57 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
58}
59
60#[cfg_attr(
61 feature = "serde",
62 derive(serde::Serialize, serde::Deserialize),
63 serde(crate = "serde_crate", rename_all = "camelCase")
64)]
65#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
66pub struct MiningInfo {
67 pub height: BlockHeight,
68 pub time: u64,
69 pub block_hash: BlockHash,
70}
71
72impl Ord for MiningInfo {
73 fn cmp(&self, other: &Self) -> Ordering { self.height.cmp(&other.height) }
74}
75
76impl PartialOrd for MiningInfo {
77 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
78}
79
80impl MiningInfo {
81 pub fn genesis() -> Self {
82 MiningInfo {
83 height: BlockHeight::MIN,
84 time: 1231006505,
85 block_hash: BlockHash::from_hex(
86 "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
87 )
88 .unwrap(),
89 }
90 }
91}
92
93#[cfg_attr(
94 feature = "serde",
95 derive(serde::Serialize, serde::Deserialize),
96 serde(crate = "serde_crate", rename_all = "camelCase")
97)]
98#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
99pub enum TxStatus<T = MiningInfo> {
100 Unknown,
101 Mempool,
102 Channel,
103 Mined(T),
104}
105
106impl<T> TxStatus<T> {
107 pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> TxStatus<U> {
108 match self {
109 TxStatus::Mined(info) => TxStatus::Mined(f(info)),
110 TxStatus::Mempool => TxStatus::Mempool,
111 TxStatus::Channel => TxStatus::Channel,
112 TxStatus::Unknown => TxStatus::Unknown,
113 }
114 }
115
116 pub fn is_mined(&self) -> bool { matches!(self, Self::Mined(_)) }
117}
118
119impl<T> Display for TxStatus<T>
120where T: Display
121{
122 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123 match self {
124 TxStatus::Mined(info) => Display::fmt(info, f),
125 TxStatus::Mempool => f.write_str("mempool"),
126 TxStatus::Channel => f.write_str("channel"),
127 TxStatus::Unknown => f.write_str("unknown"),
128 }
129 }
130}
131
132#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)]
133#[cfg_attr(
134 feature = "serde",
135 derive(serde::Serialize, serde::Deserialize),
136 serde(crate = "serde_crate", rename_all = "camelCase")
137)]
138#[display("{txid}.{vin}")]
139pub struct Inpoint {
140 pub txid: Txid,
141 pub vin: u32,
142}
143
144impl Inpoint {
145 #[inline]
146 pub fn new(txid: Txid, vin: u32) -> Self { Inpoint { txid, vin } }
147}
148
149impl From<Outpoint> for Inpoint {
150 fn from(outpoint: Outpoint) -> Self {
151 Inpoint {
152 txid: outpoint.txid,
153 vin: outpoint.vout.into_u32(),
154 }
155 }
156}
157
158#[derive(Clone, Eq, PartialEq, Debug, Display, From, Error)]
159#[display(doc_comments)]
160pub enum InpointParseError {
161 MalformedSeparator(String),
164
165 #[from]
167 InvalidVout(ParseIntError),
168
169 #[from]
171 InvalidTxid(hex::Error),
172}
173
174impl FromStr for Inpoint {
175 type Err = InpointParseError;
176
177 fn from_str(s: &str) -> Result<Self, Self::Err> {
178 let (txid, vin) =
179 s.split_once('.').ok_or_else(|| InpointParseError::MalformedSeparator(s.to_owned()))?;
180 Ok(Inpoint::new(txid.parse()?, u32::from_str(vin)?))
181 }
182}
183
184#[cfg_attr(
185 feature = "serde",
186 derive(serde::Serialize, serde::Deserialize),
187 serde(crate = "serde_crate", rename_all = "camelCase")
188)]
189#[derive(Clone, Eq, PartialEq, Hash, Debug)]
190pub struct WalletTx {
191 pub txid: Txid,
192 pub status: TxStatus,
193 pub inputs: Vec<TxCredit>,
194 pub outputs: Vec<TxDebit>,
195 pub fee: Sats,
196 pub size: u32,
197 pub weight: u32,
198 pub version: TxVer,
199 pub locktime: LockTime,
200}
201
202impl WalletTx {
203 pub fn credits(&self) -> impl Iterator<Item = &TxCredit> {
204 self.inputs.iter().filter(|c| c.is_external())
205 }
206
207 pub fn debits(&self) -> impl Iterator<Item = &TxDebit> {
208 self.outputs.iter().filter(|d| d.is_external())
209 }
210
211 pub fn total_moved(&self) -> Sats { self.inputs.iter().map(|vin| vin.value).sum::<Sats>() }
212
213 pub fn credit_sum(&self) -> Sats { self.credits().map(|vin| vin.value).sum::<Sats>() }
214
215 pub fn debit_sum(&self) -> Sats { self.debits().map(|vout| vout.value).sum::<Sats>() }
216
217 pub fn credited_debited(&self) -> (Sats, Sats) { (self.credit_sum(), self.debit_sum()) }
218
219 pub fn balance_change(&self) -> i64 {
220 let credit = self.credit_sum().sats_i64();
221 let debit = self.debit_sum().sats_i64();
222 debit - credit
223 }
224}
225
226#[derive(Clone, Eq, PartialEq, Hash, Debug, From)]
227#[cfg_attr(
228 feature = "serde",
229 derive(serde::Serialize, serde::Deserialize),
230 serde(crate = "serde_crate", rename_all = "camelCase")
231)]
232pub enum Party {
233 Subsidy,
234
235 #[from]
236 Counterparty(Address),
237
238 #[from]
239 Unknown(ScriptPubkey),
240
241 #[from]
242 Wallet(DerivedAddr),
243}
244
245impl Party {
246 pub fn is_ourself(&self) -> bool { matches!(self, Party::Wallet(_)) }
247 pub fn is_external(&self) -> bool { !self.is_ourself() }
248 pub fn is_unknown(&self) -> bool { matches!(self, Party::Unknown(_)) }
249 pub fn derived_addr(&self) -> Option<DerivedAddr> {
250 match self {
251 Party::Wallet(addr) => Some(*addr),
252 _ => None,
253 }
254 }
255 pub fn from_wallet_addr<T>(wallet_addr: &WalletAddr<T>) -> Self {
256 Party::Wallet(DerivedAddr {
257 addr: wallet_addr.addr,
258 terminal: wallet_addr.terminal,
259 })
260 }
261 pub fn script_pubkey(&self) -> Option<ScriptPubkey> {
262 match self {
263 Party::Subsidy => None,
264 Party::Counterparty(addr) => Some(addr.script_pubkey()),
265 Party::Unknown(script) => Some(script.clone()),
266 Party::Wallet(derived_addr) => Some(derived_addr.addr.script_pubkey()),
267 }
268 }
269}
270
271impl Display for Party {
272 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
273 match self {
274 Party::Subsidy => f.write_str("coinbase"),
275 Party::Counterparty(addr) => Display::fmt(addr, f),
276 Party::Unknown(script) => LowerHex::fmt(script, f),
277 Party::Wallet(term) => Display::fmt(term, f),
278 }
279 }
280}
281
282impl FromStr for Party {
283 type Err = String;
284
285 fn from_str(s: &str) -> Result<Self, Self::Err> {
286 if s == "coinbase" {
287 return Ok(Party::Subsidy);
288 }
289 Address::from_str(s)
290 .map(Self::from)
291 .or_else(|_| DerivedAddr::from_str(s).map(Self::from))
292 .or_else(|_| ScriptPubkey::from_hex(s).map(Self::from))
293 .map_err(|_| s.to_owned())
294 }
295}
296
297#[cfg_attr(
298 feature = "serde",
299 derive(serde::Serialize, serde::Deserialize),
300 serde(crate = "serde_crate", rename_all = "camelCase")
301)]
302#[derive(Clone, Eq, PartialEq, Hash, Debug)]
303pub struct TxCredit {
304 pub outpoint: Outpoint,
305 pub payer: Party,
306 pub sequence: SeqNo,
307 pub coinbase: bool,
308 pub script_sig: SigScript,
309 pub witness: Witness,
310 pub value: Sats,
311}
312
313impl TxCredit {
314 pub fn is_ourself(&self) -> bool { self.payer.is_ourself() }
315 pub fn is_external(&self) -> bool { !self.is_ourself() }
316 pub fn derived_addr(&self) -> Option<DerivedAddr> { self.payer.derived_addr() }
317}
318
319#[cfg_attr(
320 feature = "serde",
321 derive(serde::Serialize, serde::Deserialize),
322 serde(crate = "serde_crate", rename_all = "camelCase")
323)]
324#[derive(Clone, Eq, PartialEq, Hash, Debug)]
325pub struct TxDebit {
326 pub outpoint: Outpoint,
327 pub beneficiary: Party,
328 pub value: Sats,
329 pub spent: Option<Inpoint>,
331}
332
333impl TxDebit {
334 pub fn is_ourself(&self) -> bool { self.beneficiary.is_ourself() }
335 pub fn is_external(&self) -> bool { !self.is_ourself() }
336 pub fn derived_addr(&self) -> Option<DerivedAddr> { self.beneficiary.derived_addr() }
337}
338
339#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
340pub struct WalletUtxo {
341 pub outpoint: Outpoint,
342 pub value: Sats,
343 pub terminal: Terminal,
344 pub status: TxStatus,
345 }
347
348impl WalletUtxo {
349 #[inline]
350 pub fn to_prevout(&self) -> Prevout { Prevout::new(self.outpoint, self.value) }
351 pub fn into_outpoint(self) -> Outpoint { self.outpoint }
352 pub fn into_utxo(self) -> Utxo {
353 Utxo {
354 outpoint: self.outpoint,
355 value: self.value,
356 terminal: self.terminal,
357 }
358 }
359}
360
361#[cfg_attr(
362 feature = "serde",
363 derive(serde::Serialize, serde::Deserialize),
364 serde(crate = "serde_crate", rename_all = "camelCase")
365)]
366#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
367pub struct WalletAddr<T = Sats> {
368 pub terminal: Terminal,
369 pub addr: Address,
370 pub used: u32,
371 pub volume: Sats,
372 pub balance: T,
373}
374
375impl<T> Ord for WalletAddr<T>
376where T: Eq
377{
378 fn cmp(&self, other: &Self) -> Ordering { self.terminal.cmp(&other.terminal) }
379}
380
381impl<T> PartialOrd for WalletAddr<T>
382where T: Eq
383{
384 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
385}
386
387impl<T> From<DerivedAddr> for WalletAddr<T>
388where T: Default
389{
390 fn from(derived: DerivedAddr) -> Self {
391 WalletAddr {
392 addr: derived.addr,
393 terminal: derived.terminal,
394 used: 0,
395 volume: Sats::ZERO,
396 balance: zero!(),
397 }
398 }
399}
400
401impl<T> WalletAddr<T>
402where T: Default
403{
404 pub fn new(addr: Address, keychain: Keychain, index: NormalIndex) -> Self {
405 WalletAddr::<T>::from(DerivedAddr::new(addr, keychain, index))
406 }
407}
408
409impl WalletAddr<i64> {
410 pub fn expect_transmute(self) -> WalletAddr<Sats> {
411 WalletAddr {
412 terminal: self.terminal,
413 addr: self.addr,
414 used: self.used,
415 volume: self.volume,
416 balance: Sats(u64::try_from(self.balance).expect("negative balance")),
417 }
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_inpoint_str_round_trip() {
427 let s = "cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79.1";
428 assert_eq!(Inpoint::from_str(s).unwrap().to_string(), s);
429 }
430
431 #[test]
432 fn test_party_str_round_trip() {
433 fn assert_from_str_to_str(party: Party) {
434 let str = party.to_string();
435 let from_str = Party::from_str(&str).unwrap();
436
437 assert_eq!(party, from_str);
438 }
439
440 assert_from_str_to_str(Party::Subsidy);
441 assert_from_str_to_str(Party::Counterparty(
442 Address::from_str("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq").unwrap(),
443 ));
444 assert_from_str_to_str(Party::Unknown(
445 ScriptPubkey::from_hex("76a91455ae51684c43435da751ac8d2173b2652eb6410588ac").unwrap(),
446 ));
447 assert_from_str_to_str(Party::Wallet(
448 DerivedAddr::from_str("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq&1/1").unwrap(),
449 ));
450 }
451}