Skip to main content

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}