-
Notifications
You must be signed in to change notification settings - Fork 10
docs: add amount example for units crate #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Zeegaths
wants to merge
1
commit into
rust-bitcoin:master
Choose a base branch
from
Zeegaths:docs-amount-example
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| Bitcoin amounts are usually expressed in BTC or satoshis (sats), | ||
| where 1 BTC = 100,000,000 sats. | ||
| Beyond the satoshi, payment channels might use even smaller units such as millibitcoins (mBTC) to represent more granular amounts. | ||
|
|
||
| The `Amount` type represents a non-negative bitcoin amount, stored internally | ||
| as satoshis — all amounts in `rust-bitcoin` are denominated in satoshi before | ||
| they are converted for display. For cases where we need a negative value, | ||
| `rust-bitcoin` provides the `SignedAmount` type. | ||
|
|
||
| We provide the following examples: | ||
| - [Amount](units/amount.md) | ||
| - [NumOpResult](units/numopresult.md) | ||
| - [Calculating fees](units/fees.md) | ||
| - [Lock times](units/locktimes.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| # Amount | ||
|
|
||
| In this section, we will demonstrate different ways of working with Bitcoin amounts using the `Amount` type. The examples in this section will: | ||
|
|
||
| - Justify the MAX 21 million decision | ||
| - Demonstrate parsing and formatting strings | ||
| - Show basic best practices for `NumOpResult` | ||
|
|
||
| **The 21 Million Bitcoin Limit** | ||
|
|
||
| Only 21 million bitcoins will ever be in circulation. This number is hardcoded in the Bitcoin protocol. | ||
| The logic behind this decision is to create scarcity and protect Bitcoin against inflation as the digital gold. | ||
| 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: | ||
|
|
||
| 50 + 25 + 12.5 + 6.25 + 3.125 + 1.5625 + … + 0.000000001 ≈ 100 (almost) | ||
|
|
||
| 210,000 blocks × 100 BTC (sum of geometric series) = 21,000,000 BTC | ||
|
|
||
| The last bitcoin should be mined at around year 2140. | ||
|
|
||
| Bitcoin further scales down to smaller units like satoshis (sats). | ||
| 1 BTC = 100,000,000 sats. | ||
| This makes micro-transactions easy despite the high price of a single coin. | ||
|
|
||
| **Setup** | ||
|
|
||
| If using `rust-bitcoin`, `Amount` is exported: | ||
| ```rust | ||
| use bitcoin::Amount; | ||
| ``` | ||
|
|
||
| Or use the units crate directly: | ||
| ```bash | ||
| cargo add bitcoin-units | ||
| ``` | ||
| ```rust | ||
| use bitcoin_units::Amount; | ||
| ``` | ||
|
|
||
| For this example, we are going to need this import: | ||
| ```rust | ||
| use bitcoin_units::{amount::Denomination, Amount}; | ||
| ``` | ||
| Everything else goes into the main function. | ||
| ```rust | ||
| fn main() { | ||
| // The 21 million cap. | ||
| let max = Amount::MAX; | ||
| println!("Maximum amount: {} satoshis", max.to_sat()); | ||
| println!("Maximum amount: {}", max.display_in(Denomination::Bitcoin).show_denomination()); | ||
|
|
||
| // Exceeding the cap returns an error. | ||
| let too_big = Amount::from_sat(Amount::MAX.to_sat() + 1); | ||
| println!("Exceeding MAX: {:?}", too_big); // `Err(OutOfRangeError)`. | ||
|
|
||
| // Handling constants - no result handling needed. | ||
| let one_btc = Amount::ONE_BTC; | ||
| println!("One BTC = {} satoshis", one_btc.to_sat()); | ||
|
|
||
| let zero = Amount::ZERO; | ||
| println!("Zero amount: {} satoshis", zero.to_sat()); | ||
|
|
||
| // The `Amount` constructor errors if input value is too large. | ||
| let large = Amount::from_sat(100_000_000).expect("valid amount"); | ||
| println!("Large Amount = {}", large); | ||
|
|
||
Zeegaths marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Error free construction for amounts under approx 42 BTC ([u32::MAX]). | ||
| let small = Amount::from_sat_u32(50_000); | ||
| println!("Small Amount = {}", small); | ||
|
|
||
| // Parsing string type to `Amount` - result handling needed for potential error. | ||
| let amount1: Amount = "0.1 BTC".parse().expect("valid amount"); | ||
| println!("Amount1 parsed: {}", amount1); | ||
| let amount2 = "100 sat".parse::<Amount>().expect("valid"); | ||
| println!("Amount2 parsed: {}", amount2); | ||
|
|
||
| // Formatting with display_in (works without alloc). | ||
| println!("Display in BTC: {}", Amount::ONE_BTC.display_in(Denomination::Bitcoin)); | ||
| println!("Display in satoshis: {}", Amount::ONE_SAT.display_in(Denomination::Satoshi)); | ||
| println!( | ||
| "Display in BTC with denomination: {}", | ||
| Amount::ONE_BTC.display_in(Denomination::Bitcoin).show_denomination() | ||
| ); | ||
| println!( | ||
| "Display in satoshis with denomination: {}", | ||
| Amount::ONE_SAT.display_in(Denomination::Satoshi).show_denomination() | ||
| ); | ||
|
|
||
| // `display_dynamic` automatically selects denomination. | ||
| println!("Display dynamic: {}", Amount::ONE_SAT.display_dynamic()); // shows in satoshis. | ||
| println!("Display dynamic: {}", Amount::ONE_BTC.display_dynamic()); // shows in BTC. | ||
|
|
||
| // `to_string_in` and `to_string_with_denomination` both require the `alloc` feature. | ||
| #[cfg(feature = "alloc")] | ||
| { | ||
| println!("to_string_in: {}", Amount::ONE_BTC.to_string_in(Denomination::Bitcoin)); | ||
| println!( | ||
| "to_string_with_denomination: {}", | ||
| Amount::ONE_SAT.to_string_with_denomination(Denomination::Satoshi) | ||
| ); | ||
| } | ||
|
|
||
| // Arithmetic operations return `NumOpResult`. | ||
| let a = Amount::from_sat(1000).expect("valid"); | ||
| let b = Amount::from_sat(500).expect("valid"); | ||
|
|
||
| let sum = a + b; // Returns `NumOpResult<Amount>`. | ||
| println!("Sum = {:?}", sum); | ||
|
|
||
| // Extract the value using `.unwrap()`. | ||
| let sum_amount = (a + b).unwrap(); | ||
| println!("Sum amount: {} satoshis", sum_amount.to_sat()); | ||
|
|
||
| // Error in case of a negative result. | ||
| let tiny = Amount::from_sat(100).expect("valid"); | ||
| let big = Amount::from_sat(1000).expect("valid"); | ||
| let difference = tiny - big; | ||
| println!("Underflow result: {:?}", difference); | ||
| } | ||
| ``` | ||
|
|
||
| **Creating Amounts** | ||
|
|
||
| There are different ways of creating and representing amounts. | ||
| The 21 million cap is represented using the `MAX` constant. | ||
| This constant is used to validate inputs, set logic boundaries, and implement | ||
| sanity checks when testing. It is also more readable compared to hardcoding | ||
| the full 21 million amount. | ||
|
|
||
| `from_sat_u32` accepts a `u32`, which is small enough to always be within the | ||
| valid range, so result handling is not necessary. `from_sat` accepts a `u64`, which can exceed the 21 million cap, hence the `Result`. | ||
|
|
||
| The `Denomination` enum specifies which unit to display the value in. When you | ||
| call `show_denomination()`, it prints the unit alongside the value. When the | ||
| amount exceeds 21 million, it throws an out-of-range error. Other constants used to represent Bitcoin amounts include `ONE_SAT`, `ONE_BTC`, | ||
| `FIFTY_BTC`, and `ZERO`. | ||
|
|
||
| **Parsing and Formatting** | ||
|
|
||
| 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. | ||
| Result handling is always necessary since the input could be invalid. | ||
| We use `.expect()` for simplicity in these examples. | ||
|
|
||
| When formatting outputs, the preferred method is `.display_in()`, to output a number. | ||
| For a `String` output, we use `.to_string_in()` to output a plain string or | ||
| `.to_string_with_denomination()` to include the denomination. | ||
| These are convenience wrappers | ||
| around `.display_in()` and they require `alloc`. | ||
|
|
||
| Alternatively, `.display_dynamic()` automatically selects the denomination — | ||
| displaying in BTC for amounts greater than or equal to 1 BTC, and otherwise in satoshis. | ||
|
|
||
| Note that the exact formatting behaviour of `.display_in()` may change between | ||
| versions, though it guarantees accurate human-readable output that round-trips | ||
| with `parse`. | ||
|
|
||
| **NumOpResult** | ||
|
|
||
| Performing arithmetic operations produces a `NumOpResult`, which we discuss in more detail [here]. | ||
| 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. | ||
|
|
||
| We therefore explicitly extract the result using `.unwrap()` or other proper error handling options. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should try to use little 'b' for the currency and big 'B' for the network.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point