ord/subcommand/wallet/
balance.rs

1use super::*;
2
3#[derive(Serialize, Deserialize, Debug, PartialEq)]
4pub struct Output {
5  pub cardinal: u64,
6  pub ordinal: u64,
7  #[serde(default, skip_serializing_if = "Option::is_none")]
8  pub runes: Option<BTreeMap<SpacedRune, Decimal>>,
9  #[serde(default, skip_serializing_if = "Option::is_none")]
10  pub runic: Option<u64>,
11  pub total: u64,
12}
13
14pub(crate) fn run(wallet: Wallet) -> SubcommandResult {
15  let unspent_outputs = wallet.utxos();
16
17  let inscription_outputs = wallet
18    .inscriptions()
19    .keys()
20    .map(|satpoint| satpoint.outpoint)
21    .collect::<BTreeSet<OutPoint>>();
22
23  let mut cardinal = 0;
24  let mut ordinal = 0;
25  let mut runes = BTreeMap::new();
26  let mut runic = 0;
27
28  for (output, txout) in unspent_outputs {
29    let rune_balances = wallet.get_runes_balances_for_output(output)?;
30
31    let is_ordinal = inscription_outputs.contains(output);
32    let is_runic = !rune_balances.is_empty();
33
34    if is_ordinal {
35      ordinal += txout.value;
36    }
37
38    if is_runic {
39      for (spaced_rune, pile) in rune_balances {
40        runes
41          .entry(spaced_rune)
42          .and_modify(|decimal: &mut Decimal| {
43            assert_eq!(decimal.scale, pile.divisibility);
44            decimal.value += pile.amount;
45          })
46          .or_insert(Decimal {
47            value: pile.amount,
48            scale: pile.divisibility,
49          });
50      }
51      runic += txout.value;
52    }
53
54    if !is_ordinal && !is_runic {
55      cardinal += txout.value;
56    }
57
58    if is_ordinal && is_runic {
59      eprintln!("warning: output {output} contains both inscriptions and runes");
60    }
61  }
62
63  Ok(Some(Box::new(Output {
64    cardinal,
65    ordinal,
66    runes: wallet.has_rune_index().then_some(runes),
67    runic: wallet.has_rune_index().then_some(runic),
68    total: cardinal + ordinal + runic,
69  })))
70}
71
72#[cfg(test)]
73mod tests {
74  use super::*;
75
76  #[test]
77  fn runes_and_runic_fields_are_not_present_if_none() {
78    assert_eq!(
79      serde_json::to_string(&Output {
80        cardinal: 0,
81        ordinal: 0,
82        runes: None,
83        runic: None,
84        total: 0
85      })
86      .unwrap(),
87      r#"{"cardinal":0,"ordinal":0,"total":0}"#
88    );
89  }
90}