Skip to main content

fedimint_core/
amount.rs

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