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