Skip to main content

fedimint_core/
amount.rs

1use std::num::ParseIntError;
2use std::str::FromStr;
3
4use bitcoin::Denomination;
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::encoding::{Decodable, Encodable};
9
10pub const SATS_PER_BITCOIN: u64 = 100_000_000;
11
12/// Shorthand for [`Amount::from_msats`]
13pub fn msats(msats: u64) -> Amount {
14    Amount::from_msats(msats)
15}
16
17/// Shorthand for [`Amount::from_sats`]
18pub fn sats(amount: u64) -> Amount {
19    Amount::from_sats(amount)
20}
21
22/// Represents an amount of BTC. The base denomination is millisatoshis, which
23/// is why the `Amount` type from rust-bitcoin isn't used instead.
24#[derive(
25    Clone,
26    Copy,
27    Eq,
28    PartialEq,
29    Ord,
30    PartialOrd,
31    Hash,
32    Deserialize,
33    Serialize,
34    Encodable,
35    Decodable,
36    Default,
37)]
38#[serde(transparent)]
39pub struct Amount {
40    // TODO: rename to `units`, with backward compat for the serialization?
41    pub msats: u64,
42}
43
44#[cfg(feature = "uniffi")]
45uniffi::custom_type!(Amount, u64, {
46    lower: |a| a.msats,
47    try_lift: |msats| Ok(Amount { msats }),
48});
49
50impl Amount {
51    pub const ZERO: Self = Self { msats: 0 };
52
53    /// Create an amount from a number of millisatoshis.
54    pub const fn from_msats(msats: u64) -> Self {
55        Self { msats }
56    }
57
58    pub const fn from_units(units: u64) -> Self {
59        Self { msats: units }
60    }
61
62    /// Create an amount from a number of satoshis.
63    pub const fn from_sats(sats: u64) -> Self {
64        Self::from_msats(sats * 1000)
65    }
66
67    /// Create an amount from a number of whole bitcoins.
68    pub const fn from_bitcoins(bitcoins: u64) -> Self {
69        Self::from_sats(bitcoins * SATS_PER_BITCOIN)
70    }
71
72    /// Parse a decimal string as a value in the given denomination.
73    ///
74    /// Note: This only parses the value string.  If you want to parse a value
75    /// with denomination, use [`FromStr`].
76    pub fn from_str_in(s: &str, denom: Denomination) -> Result<Self, ParseAmountError> {
77        if denom == Denomination::MilliSatoshi {
78            return Ok(Self::from_msats(s.parse()?));
79        }
80        let btc_amt = bitcoin::amount::Amount::from_str_in(s, denom)?;
81        Ok(Self::from(btc_amt))
82    }
83
84    pub fn saturating_sub(self, other: Self) -> Self {
85        Self {
86            msats: self.msats.saturating_sub(other.msats),
87        }
88    }
89
90    pub fn mul_u64(self, other: u64) -> Self {
91        Self {
92            msats: self.msats * other,
93        }
94    }
95
96    /// Returns an error if the amount is more precise than satoshis (i.e. if it
97    /// has a milli-satoshi remainder). Otherwise, returns `Ok(())`.
98    pub fn ensure_sats_precision(&self) -> Result<(), AmountConversionError> {
99        if !self.msats.is_multiple_of(1000) {
100            return Err(AmountConversionError::SubSatoshiPrecision { msats: self.msats });
101        }
102        Ok(())
103    }
104
105    pub fn try_into_sats(&self) -> Result<u64, AmountConversionError> {
106        self.ensure_sats_precision()?;
107        Ok(self.msats / 1000)
108    }
109
110    pub const fn sats_round_down(&self) -> u64 {
111        self.msats / 1000
112    }
113
114    pub fn sats_f64(&self) -> f64 {
115        self.msats as f64 / 1000.0
116    }
117
118    pub fn checked_sub(self, other: Self) -> Option<Self> {
119        Some(Self {
120            msats: self.msats.checked_sub(other.msats)?,
121        })
122    }
123
124    pub fn checked_add(self, other: Self) -> Option<Self> {
125        Some(Self {
126            msats: self.msats.checked_add(other.msats)?,
127        })
128    }
129
130    pub fn checked_mul(self, other: u64) -> Option<Self> {
131        Some(Self {
132            msats: self.msats.checked_mul(other)?,
133        })
134    }
135}
136
137impl std::fmt::Display for Amount {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{} msat", self.msats)
140    }
141}
142
143impl std::fmt::Debug for Amount {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        // Note: lack of space is intentional: in large Debug outputs extra space just
146        // make it harder to tell where fields being and end.
147        write!(f, "{}msat", self.msats)
148    }
149}
150
151impl std::ops::Rem for Amount {
152    type Output = Self;
153
154    fn rem(self, rhs: Self) -> Self::Output {
155        Self {
156            msats: self.msats % rhs.msats,
157        }
158    }
159}
160
161impl std::ops::RemAssign for Amount {
162    fn rem_assign(&mut self, rhs: Self) {
163        self.msats %= rhs.msats;
164    }
165}
166
167impl std::ops::Div for Amount {
168    type Output = u64;
169
170    fn div(self, rhs: Self) -> Self::Output {
171        self.msats / rhs.msats
172    }
173}
174
175impl std::ops::SubAssign for Amount {
176    fn sub_assign(&mut self, rhs: Self) {
177        self.msats -= rhs.msats;
178    }
179}
180
181impl std::ops::Mul<u64> for Amount {
182    type Output = Self;
183
184    fn mul(self, rhs: u64) -> Self::Output {
185        Self {
186            msats: self.msats * rhs,
187        }
188    }
189}
190
191impl std::ops::Mul<Amount> for u64 {
192    type Output = Amount;
193
194    fn mul(self, rhs: Amount) -> Self::Output {
195        Amount {
196            msats: self * rhs.msats,
197        }
198    }
199}
200
201impl std::ops::Add for Amount {
202    type Output = Self;
203
204    fn add(self, rhs: Self) -> Self::Output {
205        Self {
206            msats: self.msats + rhs.msats,
207        }
208    }
209}
210
211impl std::ops::Sub for Amount {
212    type Output = Self;
213
214    fn sub(self, rhs: Self) -> Self::Output {
215        Self {
216            msats: self.msats - rhs.msats,
217        }
218    }
219}
220
221impl std::ops::AddAssign for Amount {
222    fn add_assign(&mut self, rhs: Self) {
223        *self = *self + rhs;
224    }
225}
226
227impl std::iter::Sum for Amount {
228    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
229        Self {
230            msats: iter.map(|amt| amt.msats).sum::<u64>(),
231        }
232    }
233}
234
235impl FromStr for Amount {
236    type Err = ParseAmountError;
237
238    fn from_str(s: &str) -> Result<Self, Self::Err> {
239        if let Some(i) = s.find(char::is_alphabetic) {
240            let (amt, denom) = s.split_at(i);
241            Self::from_str_in(amt.trim(), denom.trim().parse()?)
242        } else {
243            // default to millisatoshi
244            Self::from_str_in(s.trim(), Denomination::MilliSatoshi)
245        }
246    }
247}
248
249impl From<bitcoin::Amount> for Amount {
250    fn from(amt: bitcoin::Amount) -> Self {
251        assert!(amt.to_sat() <= 2_100_000_000_000_000);
252        Self {
253            msats: amt.to_sat() * 1000,
254        }
255    }
256}
257
258impl TryFrom<Amount> for bitcoin::Amount {
259    type Error = AmountConversionError;
260
261    fn try_from(value: Amount) -> Result<Self, Self::Error> {
262        value.try_into_sats().map(Self::from_sat)
263    }
264}
265
266/// Failure to convert an [`Amount`] to a coarser unit.
267#[derive(Debug, Error, Clone, Eq, PartialEq)]
268#[non_exhaustive]
269pub enum AmountConversionError {
270    /// The amount has a milli-satoshi remainder and cannot be expressed in
271    /// satoshis.
272    #[error("Amount {msats} msat is more precise than a satoshi, cannot convert to satoshis")]
273    SubSatoshiPrecision { msats: u64 },
274}
275
276#[derive(Error, Debug)]
277pub enum ParseAmountError {
278    #[error("Error parsing string as integer: {0}")]
279    NotANumber(#[from] ParseIntError),
280    #[error("Error parsing string as a bitcoin amount: {0}")]
281    WrongBitcoinAmount(#[from] bitcoin::amount::ParseAmountError),
282    #[error("Error parsing string as a bitcoin denomination: {0}")]
283    WrongBitcoinDenomination(#[from] bitcoin_units::amount::ParseDenominationError),
284}
285
286#[cfg(test)]
287mod tests;