use bitcoin::Amount as BitcoinAmount;
use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Amount(u64);
impl fmt::Debug for Amount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{} milli-satoshis", self.0)
}
}
const MAX_MSATS: u64 = 21_000_000_0000_0000_000;
impl Amount {
pub const MAX: Amount = Amount(MAX_MSATS);
pub const ZERO: Amount = Amount(0);
#[inline]
pub const fn milli_sats(&self) -> u64 {
self.0
}
#[inline]
pub const fn sats(&self) -> Result<u64, ()> {
if self.0 % 1000 == 0 {
Ok(self.0 / 1000)
} else {
Err(())
}
}
#[inline]
pub const fn sats_rounding_up(&self) -> u64 {
(self.0 + 999) / 1000
}
#[inline]
pub const fn from_milli_sats(msats: u64) -> Result<Self, ()> {
if msats > MAX_MSATS {
Err(())
} else {
Ok(Amount(msats))
}
}
#[inline]
pub const fn from_sats(sats: u64) -> Result<Self, ()> {
Self::from_milli_sats(sats.saturating_mul(1000))
}
pub(crate) const fn from_sats_panicy(sats: u64) -> Self {
let amt = sats.saturating_mul(1000);
if amt > MAX_MSATS {
panic!("Sats value greater than 21 million Bitcoin");
} else {
Amount(amt)
}
}
#[inline]
pub const fn saturating_add(self, rhs: Amount) -> Amount {
match self.0.checked_add(rhs.0) {
Some(amt) if amt <= MAX_MSATS => Amount(amt),
_ => Amount(MAX_MSATS),
}
}
#[inline]
pub const fn saturating_sub(self, rhs: Amount) -> Amount {
Amount(self.0.saturating_sub(rhs.0))
}
#[inline]
pub fn btc_decimal_rounding_up_to_sats(self) -> FormattedAmount {
FormattedAmount(self)
}
}
#[derive(Clone, Copy)]
pub struct FormattedAmount(Amount);
impl fmt::Display for FormattedAmount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let total_sats = self.0.sats_rounding_up();
let btc = total_sats / 1_0000_0000;
let mut sats = total_sats % 1_0000_0000;
write!(f, "{}", btc)?;
if sats != 0 {
let mut digits = 8;
while sats % 10 == 0 {
digits -= 1;
sats /= 10;
}
write!(f, ".{:0digits$}", sats, digits = digits)?;
}
Ok(())
}
}
impl From<BitcoinAmount> for Amount {
fn from(amt: BitcoinAmount) -> Amount {
Amount(amt.to_sat() * 1000)
}
}
#[cfg(test)]
mod test {
use super::Amount;
use alloc::string::ToString;
#[test]
#[rustfmt::skip]
fn test_display() {
assert_eq!(Amount::from_milli_sats(0).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0");
assert_eq!(Amount::from_milli_sats(1).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00000001");
assert_eq!(Amount::from_sats(1).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00000001");
assert_eq!(Amount::from_sats(10).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.0000001");
assert_eq!(Amount::from_sats(15).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00000015");
assert_eq!(Amount::from_sats(1_0000).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.0001");
assert_eq!(Amount::from_sats(1_2345).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00012345");
assert_eq!(Amount::from_sats(1_2345_6789).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "1.23456789");
assert_eq!(Amount::from_sats(1_0000_0000).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "1");
assert_eq!(Amount::from_sats(5_0000_0000).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "5");
}
}