bitcoin_payment_instructions/
amount.rs1use bitcoin::Amount as BitcoinAmount;
8
9use core::fmt;
10
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18pub struct Amount(u64);
19
20impl fmt::Debug for Amount {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
22 write!(f, "{} milli-satoshis", self.0)
23 }
24}
25
26const MAX_MSATS: u64 = 21_000_000_0000_0000_000;
27
28impl Amount {
29 pub const MAX: Amount = Amount(MAX_MSATS);
31
32 pub const ZERO: Amount = Amount(0);
34
35 #[inline]
37 pub const fn milli_sats(&self) -> u64 {
38 self.0
39 }
40
41 #[inline]
43 pub const fn sats(&self) -> Result<u64, ()> {
44 if self.0 % 1000 == 0 {
45 Ok(self.0 / 1000)
46 } else {
47 Err(())
48 }
49 }
50
51 #[inline]
53 pub const fn sats_rounding_up(&self) -> u64 {
54 (self.0 + 999) / 1000
55 }
56
57 #[inline]
61 pub const fn from_milli_sats(msats: u64) -> Result<Self, ()> {
62 if msats > MAX_MSATS {
63 Err(())
64 } else {
65 Ok(Amount(msats))
66 }
67 }
68
69 #[inline]
73 pub const fn from_sats(sats: u64) -> Result<Self, ()> {
74 Self::from_milli_sats(sats.saturating_mul(1000))
75 }
76
77 pub(crate) const fn from_sats_panicy(sats: u64) -> Self {
80 let amt = sats.saturating_mul(1000);
81 if amt > MAX_MSATS {
82 panic!("Sats value greater than 21 million Bitcoin");
83 } else {
84 Amount(amt)
85 }
86 }
87
88 #[inline]
90 pub const fn saturating_add(self, rhs: Amount) -> Amount {
91 match self.0.checked_add(rhs.0) {
92 Some(amt) if amt <= MAX_MSATS => Amount(amt),
93 _ => Amount(MAX_MSATS),
94 }
95 }
96
97 #[inline]
99 pub const fn saturating_sub(self, rhs: Amount) -> Amount {
100 Amount(self.0.saturating_sub(rhs.0))
101 }
102
103 #[inline]
107 pub fn btc_decimal_rounding_up_to_sats(self) -> FormattedAmount {
108 FormattedAmount(self)
109 }
110}
111
112#[derive(Clone, Copy)]
113pub struct FormattedAmount(Amount);
116
117impl fmt::Display for FormattedAmount {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
119 let total_sats = self.0.sats_rounding_up();
120 let btc = total_sats / 1_0000_0000;
121 let mut sats = total_sats % 1_0000_0000;
122 write!(f, "{}", btc)?;
123 if sats != 0 {
124 let mut digits = 8;
125 while sats % 10 == 0 {
126 digits -= 1;
127 sats /= 10;
128 }
129 write!(f, ".{:0digits$}", sats, digits = digits)?;
130 }
131 Ok(())
132 }
133}
134
135impl From<BitcoinAmount> for Amount {
136 fn from(amt: BitcoinAmount) -> Amount {
137 Amount(amt.to_sat() * 1000)
138 }
139}
140
141#[cfg(test)]
142mod test {
143 use super::Amount;
144
145 use alloc::string::ToString;
146
147 #[test]
148 #[rustfmt::skip]
149 fn test_display() {
150 assert_eq!(Amount::from_milli_sats(0).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0");
151 assert_eq!(Amount::from_milli_sats(1).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00000001");
152 assert_eq!(Amount::from_sats(1).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00000001");
153 assert_eq!(Amount::from_sats(10).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.0000001");
154 assert_eq!(Amount::from_sats(15).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00000015");
155 assert_eq!(Amount::from_sats(1_0000).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.0001");
156 assert_eq!(Amount::from_sats(1_2345).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "0.00012345");
157 assert_eq!(Amount::from_sats(1_2345_6789).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "1.23456789");
158 assert_eq!(Amount::from_sats(1_0000_0000).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "1");
159 assert_eq!(Amount::from_sats(5_0000_0000).unwrap().btc_decimal_rounding_up_to_sats().to_string(), "5");
160 }
161}