lexe_common/ln/balance.rs
1use serde::{Deserialize, Serialize};
2
3use crate::ln::amount::Amount;
4
5/// Basically `bdk::Balance`, so that `lexe-common` doesn't need to depend on
6/// `bdk`.
7///
8/// Partitions a wallet balance into different categories.
9#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
10pub struct OnchainBalance {
11 /// All coinbase outputs not yet matured
12 pub immature: bitcoin::Amount,
13 /// Unconfirmed UTXOs generated by a wallet tx
14 pub trusted_pending: bitcoin::Amount,
15 /// Unconfirmed UTXOs received from an external wallet
16 pub untrusted_pending: bitcoin::Amount,
17 /// Confirmed and immediately spendable balance
18 pub confirmed: bitcoin::Amount,
19}
20
21/// Classify the lightning channel balances into different categories.
22#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
23pub struct LightningBalance {
24 /// The sum channel value that is usable.
25 ///
26 /// For example, if we have an open channel with a disconnected node, then
27 /// we can't send over it; it would not be counted here.
28 pub usable: Amount,
29
30 /// Our most accurate estimation of how much we can currently send if we
31 /// made a MPP over all usable channels to an unspecified recipient.
32 ///
33 /// It's the sum of [`next_outbound_htlc_limit`] over all channels adjusted
34 /// to account for an estimate of the total fees paid to Lexe's LSP and the
35 /// broader Lightning Network.
36 ///
37 /// It's intended to be a conservative estimate, so the user should
38 /// *usually* be able to send this amount. A precise sendable upper bound
39 /// is not computable without knowledge of the recipient because routing
40 /// fees vary by path.
41 ///
42 /// This value is bounded above by [`Self::max_sendable`].
43 ///
44 /// [`next_outbound_htlc_limit`]: crate::ln::channel::LxChannelDetails::next_outbound_htlc_limit
45 pub sendable: Amount,
46
47 /// The maximum amount that we could possibly send in a 'typical' payment,
48 /// i.e. a multi-hop payment to anyone who is not a direct counterparty.
49 ///
50 /// This is computed as the sum of [`next_outbound_htlc_limit`] over all
51 /// usable channels, then adjusted to account for the minimum fees Lexe's
52 /// LSP could charge us.
53 ///
54 /// - It should not be possible to send more than this amount over any
55 /// multi-hop payment.
56 /// - Exactly this amount may be sendable only in very specific scenarios,
57 /// such as paying another Lexe user.
58 /// - Technically, it is possible to send [`sum(next_outbound_htlc_limit)`]
59 /// to our direct channel counterparties, but since User <-> LSP payments
60 /// are a special case, we'll handle those flows differently.
61 ///
62 /// [`next_outbound_htlc_limit`]: crate::ln::channel::LxChannelDetails::next_outbound_htlc_limit
63 // TODO(max): We've confirmed we can send `next_outbound_htlc_limit`
64 // between User <-> LSP in a single path single hop payment, but
65 // sending up to `sum(next_outbound_htlc_limit)` hasn't yet been
66 // confirmed for multi-path single hop payments.
67 pub max_sendable: Amount,
68
69 /// The sum channel value that isn't currently usable.
70 ///
71 /// The channel may be (1) opening and await confirmation, (2) shutting
72 /// down, or (3) confirmed but the peer is disconnected.
73 // TODO(phlip9): split these out
74 pub pending: Amount,
75}
76
77// ---- impl OnchainBalance ---- //
78
79impl OnchainBalance {
80 pub const ZERO: Self = Self {
81 immature: bitcoin::Amount::ZERO,
82 trusted_pending: bitcoin::Amount::ZERO,
83 untrusted_pending: bitcoin::Amount::ZERO,
84 confirmed: bitcoin::Amount::ZERO,
85 };
86
87 /// The maximum amount that we could spend in a transaction right now.
88 ///
89 /// - This is generally what you should use, as it determines the total
90 /// actionable balance.
91 /// - If it is necessary to partition funds by whether they are confirmed,
92 /// use [`Self::confirmed`] and [`Self::unconfirmed`].
93 /// - "confirmed + unconfirmed = spendable"
94 /// - "spendable + immature = total"
95 pub fn spendable(&self) -> bitcoin::Amount {
96 // We *can* in fact spend `untrusted_pending` outputs.
97 // This is tested in a smoketest.
98 self.confirmed + self.trusted_pending + self.untrusted_pending
99 }
100
101 /// The sum of mature but unconfirmed outputs sent to our wallet.
102 ///
103 /// - Includes trusted outputs from our own wallet (e.g. change outputs).
104 /// Use [`Self::untrusted_pending`] to get the sum of unconfirmed outputs
105 /// from external wallets.
106 /// - "immature + unconfirmed + confirmed = total"
107 pub fn unconfirmed(&self) -> bitcoin::Amount {
108 self.trusted_pending + self.untrusted_pending
109 }
110
111 /// The maximum amount we can spend without risk of our transaction being
112 /// cancelled by a counterparty double-spending one of our outputs.
113 ///
114 /// - Equivalent to BDK's `trusted_spendable`.
115 /// - "trusted_spendable + untrusted_pending = spendable"
116 pub fn trusted_spendable(&self) -> bitcoin::Amount {
117 self.confirmed + self.trusted_pending
118 }
119
120 /// Sum of all unconfirmed + immature coins.
121 ///
122 /// - "pending + confirmed = total"
123 pub fn pending(&self) -> bitcoin::Amount {
124 self.immature + self.trusted_pending + self.untrusted_pending
125 }
126
127 /// The total balance visible to the wallet.
128 ///
129 /// NOTE: Generally use `spendable` instead of this, as immature outputs
130 /// are not spendable (even though we should never see them in practice)
131 pub fn total(&self) -> bitcoin::Amount {
132 self.confirmed
133 + self.trusted_pending
134 + self.untrusted_pending
135 + self.immature
136 }
137}
138
139impl std::ops::Add for OnchainBalance {
140 type Output = Self;
141
142 fn add(self, other: Self) -> Self {
143 Self {
144 immature: self.immature + other.immature,
145 trusted_pending: self.trusted_pending + other.trusted_pending,
146 untrusted_pending: self.untrusted_pending + other.untrusted_pending,
147 confirmed: self.confirmed + other.confirmed,
148 }
149 }
150}
151
152impl std::iter::Sum for OnchainBalance {
153 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
154 iter.fold(Self::ZERO, |a, b| a + b)
155 }
156}
157
158impl Default for OnchainBalance {
159 fn default() -> Self {
160 Self::ZERO
161 }
162}
163
164// ---- impl LightningBalance ---- //
165
166impl LightningBalance {
167 pub const ZERO: Self = Self {
168 usable: Amount::ZERO,
169 sendable: Amount::ZERO,
170 max_sendable: Amount::ZERO,
171 pending: Amount::ZERO,
172 };
173
174 /// Get the sum total of all our known channel balances.
175 pub fn total(&self) -> Amount {
176 self.usable + self.pending
177 }
178}
179
180impl Default for LightningBalance {
181 fn default() -> Self {
182 Self::ZERO
183 }
184}