|
| 1 | +# Amount |
| 2 | + |
| 3 | +In this section, we will demonstrate different ways of working with Bitcoin amounts using the `Amount` type. The examples in this section will: |
| 4 | + |
| 5 | +- Justify the MAX 21 million decision |
| 6 | +- Demonstrate parsing and formatting strings |
| 7 | +- Show basic best practices for `NumOpResult` |
| 8 | + |
| 9 | +**The 21 Million Bitcoin Limit** |
| 10 | + |
| 11 | +Only 21 million bitcoins will ever be in circulation. This number is hardcoded in the Bitcoin protocol. |
| 12 | +The logic behind this decision is to create scarcity and protect Bitcoin against inflation as the digital gold. |
| 13 | +Bitcoin is distributed as a result of mining a block and after every 210,000 blocks, the reward is halved and the complexity increases. The first reward was 50 BTC, so: |
| 14 | + |
| 15 | +50 + 25 + 12.5 + 6.25 + 3.125 + 1.5625 + … + 0.000000001 ≈ 100 (almost) |
| 16 | + |
| 17 | +210,000 blocks × 100 BTC (sum of geometric series) = 21,000,000 BTC |
| 18 | + |
| 19 | +The last bitcoin should be mined at around year 2140. |
| 20 | + |
| 21 | +Bitcoin further scales down to smaller units like satoshis (sats). |
| 22 | +1 BTC = 100,000,000 sats. |
| 23 | +This makes micro-transactions easy despite the high price of a single coin. |
| 24 | + |
| 25 | +**Setup** |
| 26 | + |
| 27 | +If using `rust-bitcoin`, `Amount` is exported: |
| 28 | +```rust |
| 29 | +use bitcoin::Amount; |
| 30 | +``` |
| 31 | + |
| 32 | +Or use the units crate directly: |
| 33 | +```bash |
| 34 | +cargo add bitcoin-units |
| 35 | +``` |
| 36 | +```rust |
| 37 | +use bitcoin_units::Amount; |
| 38 | +``` |
| 39 | + |
| 40 | +For this example, we are going to need this import: |
| 41 | +```rust |
| 42 | +use bitcoin_units::{amount::Denomination, Amount}; |
| 43 | +``` |
| 44 | +Everything else goes into the main function. |
| 45 | +```rust |
| 46 | +fn main() { |
| 47 | + // The 21 million cap. |
| 48 | + let max = Amount::MAX; |
| 49 | + println!("Maximum amount: {} satoshis", max.to_sat()); |
| 50 | + println!("Maximum amount: {}", max.display_in(Denomination::Bitcoin).show_denomination()); |
| 51 | + |
| 52 | + // Exceeding the cap returns an error. |
| 53 | + let too_big = Amount::from_sat(Amount::MAX.to_sat() + 1); |
| 54 | + println!("Exceeding MAX: {:?}", too_big); // `Err(OutOfRangeError)`. |
| 55 | + |
| 56 | + // Handling constants - no result handling needed. |
| 57 | + let one_btc = Amount::ONE_BTC; |
| 58 | + println!("One BTC = {} satoshis", one_btc.to_sat()); |
| 59 | + |
| 60 | + let zero = Amount::ZERO; |
| 61 | + println!("Zero amount: {} satoshis", zero.to_sat()); |
| 62 | + |
| 63 | + // The `Amount` constructor errors if input value is too large. |
| 64 | + let large = Amount::from_sat(100_000_000).expect("valid amount"); |
| 65 | + println!("Large Amount = {}", large); |
| 66 | + |
| 67 | + // Error free construction for amounts under approx 42 BTC ([u32::MAX]). |
| 68 | + let small = Amount::from_sat_u32(50_000); |
| 69 | + println!("Small Amount = {}", small); |
| 70 | + |
| 71 | + // Parsing string type to `Amount` - result handling needed for potential error. |
| 72 | + let amount1: Amount = "0.1 BTC".parse().expect("valid amount"); |
| 73 | + println!("Amount1 parsed: {}", amount1); |
| 74 | + let amount2 = "100 sat".parse::<Amount>().expect("valid"); |
| 75 | + println!("Amount2 parsed: {}", amount2); |
| 76 | + |
| 77 | + // Formatting with display_in (works without alloc). |
| 78 | + println!("Display in BTC: {}", Amount::ONE_BTC.display_in(Denomination::Bitcoin)); |
| 79 | + println!("Display in satoshis: {}", Amount::ONE_SAT.display_in(Denomination::Satoshi)); |
| 80 | + println!( |
| 81 | + "Display in BTC with denomination: {}", |
| 82 | + Amount::ONE_BTC.display_in(Denomination::Bitcoin).show_denomination() |
| 83 | + ); |
| 84 | + println!( |
| 85 | + "Display in satoshis with denomination: {}", |
| 86 | + Amount::ONE_SAT.display_in(Denomination::Satoshi).show_denomination() |
| 87 | + ); |
| 88 | + |
| 89 | + // `display_dynamic` automatically selects denomination. |
| 90 | + println!("Display dynamic: {}", Amount::ONE_SAT.display_dynamic()); // shows in satoshis. |
| 91 | + println!("Display dynamic: {}", Amount::ONE_BTC.display_dynamic()); // shows in BTC. |
| 92 | + |
| 93 | + // `to_string_in` and `to_string_with_denomination` both require the `alloc` feature. |
| 94 | + #[cfg(feature = "alloc")] |
| 95 | + { |
| 96 | + println!("to_string_in: {}", Amount::ONE_BTC.to_string_in(Denomination::Bitcoin)); |
| 97 | + println!( |
| 98 | + "to_string_with_denomination: {}", |
| 99 | + Amount::ONE_SAT.to_string_with_denomination(Denomination::Satoshi) |
| 100 | + ); |
| 101 | + } |
| 102 | + |
| 103 | + // Arithmetic operations return `NumOpResult`. |
| 104 | + let a = Amount::from_sat(1000).expect("valid"); |
| 105 | + let b = Amount::from_sat(500).expect("valid"); |
| 106 | + |
| 107 | + let sum = a + b; // Returns `NumOpResult<Amount>`. |
| 108 | + println!("Sum = {:?}", sum); |
| 109 | + |
| 110 | + // Extract the value using `.unwrap()`. |
| 111 | + let sum_amount = (a + b).unwrap(); |
| 112 | + println!("Sum amount: {} satoshis", sum_amount.to_sat()); |
| 113 | + |
| 114 | + // Error in case of a negative result. |
| 115 | + let tiny = Amount::from_sat(100).expect("valid"); |
| 116 | + let big = Amount::from_sat(1000).expect("valid"); |
| 117 | + let difference = tiny - big; |
| 118 | + println!("Underflow result: {:?}", difference); |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +**Creating Amounts** |
| 123 | + |
| 124 | +There are different ways of creating and representing amounts. |
| 125 | +The 21 million cap is represented using the `MAX` constant. |
| 126 | +This constant is used to validate inputs, set logic boundaries, and implement |
| 127 | +sanity checks when testing. It is also more readable compared to hardcoding |
| 128 | +the full 21 million amount. |
| 129 | + |
| 130 | +`from_sat_u32` accepts a `u32`, which is small enough to always be within the |
| 131 | +valid range, so result handling is not necessary. `from_sat` accepts a `u64`, which can exceed the 21 million cap, hence the `Result`. |
| 132 | + |
| 133 | +The `Denomination` enum specifies which unit to display the value in. When you |
| 134 | +call `show_denomination()`, it prints the unit alongside the value. When the |
| 135 | +amount exceeds 21 million, it throws an out-of-range error. Other constants used to represent Bitcoin amounts include `ONE_SAT`, `ONE_BTC`, |
| 136 | +`FIFTY_BTC`, and `ZERO`. |
| 137 | + |
| 138 | +**Parsing and Formatting** |
| 139 | + |
| 140 | +We can parse `Amount` from strings using `parse()` as long as it has a denomination like `"0.1 BTC"` or `"100 sat"` , unless it's zero. |
| 141 | +Result handling is always necessary since the input could be invalid. |
| 142 | +We use `.expect()` for simplicity in these examples. |
| 143 | + |
| 144 | +When formatting outputs, the preferred method is `.display_in()`, to output a number. |
| 145 | +For a `String` output, we use `.to_string_in()` to output a plain string or |
| 146 | +`.to_string_with_denomination()` to include the denomination. |
| 147 | +These are convenience wrappers |
| 148 | +around `.display_in()` and they require `alloc`. |
| 149 | + |
| 150 | +Alternatively, `.display_dynamic()` automatically selects the denomination — |
| 151 | +displaying in BTC for amounts greater than or equal to 1 BTC, and otherwise in satoshis. |
| 152 | + |
| 153 | +Note that the exact formatting behaviour of `.display_in()` may change between |
| 154 | +versions, though it guarantees accurate human-readable output that round-trips |
| 155 | +with `parse`. |
| 156 | + |
| 157 | +**NumOpResult** |
| 158 | + |
| 159 | +Performing arithmetic operations produces a `NumOpResult`, which we discuss in more detail [here]. |
| 160 | +All we are highlighting here is that arithmetic on `Amount` values does not panic in case of errors — instead, it returns `Valid(Amount)` on success and `Error` on failure. |
| 161 | + |
| 162 | +We therefore explicitly extract the result using `.unwrap()` or other proper error handling options. |
0 commit comments