Repository: rust-bitcoin/rust-bitcoin
Pull request #3539 (2024-10-31 01:42:32)

Improve the `amount` module
Improve the `amount` module by doing:

- Patch 1: Add/update `from_int_btc` and `from_int_btc_const` functions
- Patch 2:  Add type to debug output for `Amount`
- Patch 3: Fix incorrect docs
- Patch 4: Make docs use correct style and be uniform across the two amount types
- Patch 5: Fix docs on `FromStr`



tcharding: Sorry in advance for asking you to review this @apoelstra.
tcharding: I threw up #3541 as well which is the same as this without the last 5 patches in case there is any push back against the last 5.
tcharding: The `from_int` stuff is quite different and will need review please. As for the div stuff I think we can do it as a follow up?
github-actions[bot]: :rotating_light: API BREAKING CHANGE DETECTED

To see the changes click details on "Check semver breaks / PR Semver - stable toolchain" job then expand "Run semver checker script" and scroll to the end of the section.

Patch:
diff --git a/base58/src/error.rs b/base58/src/error.rs
index 6257056e..f926acf5 100644
--- a/base58/src/error.rs
+++ b/base58/src/error.rs
@@ -145,7 +145,7 @@ pub(super) struct InvalidCharacterErrorInner {
 internals::impl_from_infallible!(InvalidCharacterErrorInner);
 
 impl InvalidCharacterError {
-    pub(super) fn new(invalid: u8) -> Self { Self(InvalidCharacterErrorInner { invalid }) }
+    pub(super) fn new(invalid: u8) -> Self { Self(InvalidCharacterErrorInner{ invalid }) }
 
     /// Returns the invalid base58 character.
     pub fn invalid_character(&self) -> u8 { self.0.invalid }
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index 9d418a50..4a69d718 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -49,7 +49,7 @@
     script_pubkey: UTXO_SCRIPT_PUBKEY,
     pubkey: UTXO_PUBKEY,
     master_fingerprint: UTXO_MASTER_FINGERPRINT,
-    amount_in_sats: Amount::from_int_btc(50),
+    amount_in_sats: Amount::from_int_btc_const(50),
     derivation_path: BIP86_DERIVATION_PATH,
 };
 
@@ -60,7 +60,7 @@
     script_pubkey: UTXO_SCRIPT_PUBKEY,
     pubkey: UTXO_PUBKEY,
     master_fingerprint: UTXO_MASTER_FINGERPRINT,
-    amount_in_sats: Amount::from_int_btc(50),
+    amount_in_sats: Amount::from_int_btc_const(50),
     derivation_path: BIP86_DERIVATION_PATH,
 };
 
@@ -71,7 +71,7 @@
     script_pubkey: UTXO_SCRIPT_PUBKEY,
     pubkey: UTXO_PUBKEY,
     master_fingerprint: UTXO_MASTER_FINGERPRINT,
-    amount_in_sats: Amount::from_int_btc(50),
+    amount_in_sats: Amount::from_int_btc_const(50),
     derivation_path: BIP86_DERIVATION_PATH,
 };
 
diff --git a/bitcoin/src/blockdata/script/push_bytes.rs b/bitcoin/src/blockdata/script/push_bytes.rs
index 68a7bbe9..fc8cf163 100644
--- a/bitcoin/src/blockdata/script/push_bytes.rs
+++ b/bitcoin/src/blockdata/script/push_bytes.rs
@@ -47,25 +47,32 @@ fn check_limit(len: usize) -> Result<(), PushBytesError> {
     impl PushBytes {
         /// Creates `&PushBytes` without checking the length.
         ///
+        /// # Safety
+        ///
         /// The caller is responsible for checking that the length is less than the 2^32.
-        fn from_slice_unchecked(bytes: &[u8]) -> &Self {
-            // SAFETY: The conversion is sound because &[u8] and &PushBytes
+        unsafe fn from_slice_unchecked(bytes: &[u8]) -> &Self {
+            // SAFETY: The caller must guarantee that bytes.len() < 2^32.
+            // If that is the case the conversion is sound because &[u8] and &PushBytes
             // have the same layout (because of #[repr(transparent)] on PushBytes).
-            unsafe { &*(bytes as *const [u8] as *const PushBytes) }
+            &*(bytes as *const [u8] as *const PushBytes)
         }
 
         /// Creates `&mut PushBytes` without checking the length.
         ///
+        /// # Safety
+        ///
         /// The caller is responsible for checking that the length is less than the 2^32.
-        fn from_mut_slice_unchecked(bytes: &mut [u8]) -> &mut Self {
-            // SAFETY: The conversion is sound because &mut [u8] and &mut PushBytes
+        unsafe fn from_mut_slice_unchecked(bytes: &mut [u8]) -> &mut Self {
+            // SAFETY: The caller must guarantee that bytes.len() < 2^32.
+            // If that is the case the conversion is sound because &mut [u8] and &mut PushBytes
             // have the same layout (because of #[repr(transparent)] on PushBytes).
-            unsafe { &mut *(bytes as *mut [u8] as *mut PushBytes) }
+            &mut *(bytes as *mut [u8] as *mut PushBytes)
         }
 
         /// Creates an empty `&PushBytes`.
         pub fn empty() -> &'static Self {
-            Self::from_slice_unchecked(&[])
+            // SAFETY: 0 < 2^32.
+            unsafe { Self::from_slice_unchecked(&[]) }
         }
 
         /// Returns the underlying bytes.
@@ -84,7 +91,10 @@ impl Index<$type> for PushBytes {
                     #[inline]
                     #[track_caller]
                     fn index(&self, index: $type) -> &Self::Output {
-                        Self::from_slice_unchecked(&self.0[index])
+                        // SAFETY: Slicing can not make slices longer.
+                        unsafe {
+                            Self::from_slice_unchecked(&self.0[index])
+                        }
                     }
                 }
 
@@ -92,7 +102,10 @@ impl IndexMut<$type> for PushBytes {
                     #[inline]
                     #[track_caller]
                     fn index_mut(&mut self, index: $type) -> &mut Self::Output {
-                        Self::from_mut_slice_unchecked(&mut self.0[index])
+                        // SAFETY: Slicing can not make slices longer.
+                        unsafe {
+                            Self::from_mut_slice_unchecked(&mut self.0[index])
+                        }
                     }
                 }
             )*
@@ -128,7 +141,8 @@ impl<'a> TryFrom<&'a [u8]> for &'a PushBytes {
 
         fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
             check_limit(bytes.len())?;
-            Ok(PushBytes::from_slice_unchecked(bytes))
+            // SAFETY: We've just checked the length.
+            Ok(unsafe { PushBytes::from_slice_unchecked(bytes) })
         }
     }
 
@@ -137,7 +151,8 @@ impl<'a> TryFrom<&'a mut [u8]> for &'a mut PushBytes {
 
         fn try_from(bytes: &'a mut [u8]) -> Result<Self, Self::Error> {
             check_limit(bytes.len())?;
-            Ok(PushBytes::from_mut_slice_unchecked(bytes))
+            // SAFETY: We've just checked the length.
+            Ok(unsafe { PushBytes::from_mut_slice_unchecked(bytes) })
         }
     }
 
@@ -148,15 +163,16 @@ macro_rules! from_array {
                     fn from(bytes: &'a [u8; $len]) -> Self {
                         // Check that the macro wasn't called with a wrong number.
                         const _: () = [(); 1][($len >= 0x100000000u64) as usize];
-                        PushBytes::from_slice_unchecked(bytes)
+                        // SAFETY: We know the size of array statically and we checked macro input.
+                        unsafe { PushBytes::from_slice_unchecked(bytes) }
                     }
                 }
 
                 impl<'a> From<&'a mut [u8; $len]> for &'a mut PushBytes {
                     fn from(bytes: &'a mut [u8; $len]) -> Self {
                         // Macro check already above, no need to duplicate.
-                        // We know the size of array statically and we checked macro input.
-                        PushBytes::from_mut_slice_unchecked(bytes)
+                        // SAFETY: We know the size of array statically and we checked macro input.
+                        unsafe { PushBytes::from_mut_slice_unchecked(bytes) }
                     }
                 }
 
@@ -257,13 +273,13 @@ pub fn truncate(&mut self, len: usize) { self.0.truncate(len) }
         /// Extracts `PushBytes` slice
         pub fn as_push_bytes(&self) -> &PushBytes {
             // length guaranteed by our invariant
-            PushBytes::from_slice_unchecked(&self.0)
+            unsafe { PushBytes::from_slice_unchecked(&self.0) }
         }
 
         /// Extracts mutable `PushBytes` slice
         pub fn as_mut_push_bytes(&mut self) -> &mut PushBytes {
             // length guaranteed by our invariant
-            PushBytes::from_mut_slice_unchecked(&mut self.0)
+            unsafe { PushBytes::from_mut_slice_unchecked(&mut self.0) }
         }
 
         /// Accesses inner `Vec` - provided for `super` to impl other methods.
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 3dc203da..e9207099 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -1970,7 +1970,7 @@ fn outpoint_format() {
         let display = "0000000000000000000000000000000000000000000000000000000000000000:4294967295";
         assert_eq!(display, format!("{}", &outpoint));
 
-        let pretty_debug = "OutPoint {\n    txid: 0x0000000000000000000000000000000000000000000000000000000000000000,\n    vout: 4294967295,\n}";
+        let pretty_debug = "OutPoint {\n    txid: 0000000000000000000000000000000000000000000000000000000000000000,\n    vout: 4294967295,\n}";
         assert_eq!(pretty_debug, format!("{:#?}", &outpoint));
 
         let debug_txid = "0000000000000000000000000000000000000000000000000000000000000000";
diff --git a/bitcoin/src/consensus/serde.rs b/bitcoin/src/consensus/serde.rs
index 66d06033..32f9d417 100644
--- a/bitcoin/src/consensus/serde.rs
+++ b/bitcoin/src/consensus/serde.rs
@@ -74,9 +74,7 @@ impl Case for super::Upper {
     pub struct Encoder<C: Case>(BufEncoder<{ HEX_BUF_SIZE }>, PhantomData<C>);
 
     impl<C: Case> From<super::Hex<C>> for Encoder<C> {
-        fn from(_: super::Hex<C>) -> Self {
-            Encoder(BufEncoder::new(C::INTERNAL_CASE), Default::default())
-        }
+        fn from(_: super::Hex<C>) -> Self { Encoder(BufEncoder::new(C::INTERNAL_CASE), Default::default()) }
     }
 
     impl<C: Case> super::EncodeBytes for Encoder<C> {
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 4ebf4f82..86103e97 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -446,6 +446,7 @@ pub fn public_key<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>) -> PublicKe
         }
     }
 
+
     /// Serializes the private key to bytes.
     #[deprecated(since = "TBD", note = "use to_vec instead")]
     pub fn to_bytes(self) -> Vec<u8> { self.to_vec() }
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 5685414b..11c13f83 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -118,49 +118,34 @@
 #[doc(inline)]
 pub use crate::{
     address::{Address, AddressType, KnownHrp},
+    amount::{Amount, Denomination, SignedAmount},
     bip158::{FilterHash, FilterHeader},
     bip32::XKeyIdentifier,
+    blockdata::block::{self, Block, BlockHash, WitnessCommitment},
+    blockdata::constants,
+    blockdata::fee_rate::FeeRate,
+    blockdata::locktime::{self, absolute, relative},
+    blockdata::opcodes::{self, Opcode},
+    blockdata::script::witness_program::{self, WitnessProgram},
+    blockdata::script::witness_version::{self, WitnessVersion},
+    blockdata::script::{self, Script, ScriptBuf, ScriptHash, WScriptHash},
+    blockdata::transaction::{self, OutPoint, Transaction, TxIn, TxOut, Txid, Wtxid},
+    blockdata::weight::Weight,
+    blockdata::witness::{self, Witness},
     crypto::ecdsa,
     crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, CompressedPublicKey, WPubkeyHash, XOnlyPublicKey},
     crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag},
-    merkle_tree::MerkleBlock,
+    merkle_tree::{MerkleBlock, TxMerkleNode, WitnessMerkleNode},
     network::{Network, NetworkKind, TestnetVersion},
     network::params::{self, Params},
-    pow::{Target, Work},
+    pow::{CompactTarget, Target, Work},
     psbt::Psbt,
     sighash::{EcdsaSighashType, TapSighashType},
     taproot::{TapBranchTag, TapLeafHash, TapLeafTag, TapNodeHash, TapTweakHash, TapTweakTag},
 };
-// Re-export all modules from `blockdata`, users should never need to use `blockdata` directly.
-#[doc(inline)]
-pub use crate::{
-    // These modules also re-export all the respective `primitives` types.
-    blockdata::{block, constants, fee_rate, locktime, opcodes, script, transaction, weight, witness},
-    // And re-export types and modules from `blockdata` that don't come from `primitives`.
-    blockdata::block::Block, // TODO: Move this down below after it is in primitives.
-    blockdata::locktime::{absolute, relative},
-    blockdata::script::witness_program::{self, WitnessProgram},
-    blockdata::script::witness_version::{self, WitnessVersion},
-    blockdata::script::{ScriptHash, WScriptHash}, // TODO: Move these down below after they are in primitives.
-};
-#[doc(inline)]
-pub use primitives::{
-    block::{BlockHash, WitnessCommitment, Header as BlockHeader},
-    merkle_tree::{TxMerkleNode, WitnessMerkleNode},
-    opcodes::Opcode,
-    pow::CompactTarget,
-    script::{Script, ScriptBuf},
-    transaction::{OutPoint, Transaction, TxIn, TxOut, Txid, Wtxid},
-    witness::Witness,
-    sequence::Sequence,
-};
 #[doc(inline)]
-pub use units::{
-    amount::{Amount, Denomination, SignedAmount},
-    block::{BlockHeight, BlockInterval},
-    fee_rate::FeeRate,
-    weight::Weight
-};
+pub use primitives::Sequence;
+pub use units::{BlockHeight, BlockInterval};
 
 #[rustfmt::skip]
 #[allow(unused_imports)]
diff --git a/chacha20_poly1305/src/chacha20.rs b/chacha20_poly1305/src/chacha20.rs
index fa7798ce..20b4ac60 100644
--- a/chacha20_poly1305/src/chacha20.rs
+++ b/chacha20_poly1305/src/chacha20.rs
@@ -327,7 +327,6 @@ pub fn block(&mut self, block: u32) {
 #[cfg(feature = "alloc")]
 mod tests {
     use alloc::vec::Vec;
-
     use hex::prelude::*;
 
     use super::*;
diff --git a/chacha20_poly1305/src/lib.rs b/chacha20_poly1305/src/lib.rs
index 04d7f377..7f60209f 100644
--- a/chacha20_poly1305/src/lib.rs
+++ b/chacha20_poly1305/src/lib.rs
@@ -4,16 +4,6 @@
 //! to form an authenticated encryption with additional data (AEAD) algorithm.
 #![no_std]
 
-// Experimental features we need.
-#![cfg_attr(docsrs, feature(doc_auto_cfg))]
-// Coding conventions.
-#![warn(missing_docs)]
-#![warn(deprecated_in_future)]
-#![doc(test(attr(warn(unused))))]
-// Exclude lints we don't think are valuable.
-#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
-#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
-
 #[cfg(feature = "alloc")]
 extern crate alloc;
 #[cfg(feature = "std")]
@@ -160,7 +150,6 @@ pub fn decrypt(
 #[cfg(feature = "alloc")]
 mod tests {
     use alloc::vec::Vec;
-
     use hex::prelude::*;
 
     use super::*;
diff --git a/chacha20_poly1305/src/poly1305.rs b/chacha20_poly1305/src/poly1305.rs
index 39e4e7a8..50d0e0a2 100644
--- a/chacha20_poly1305/src/poly1305.rs
+++ b/chacha20_poly1305/src/poly1305.rs
@@ -224,7 +224,6 @@ fn r_times_a(&mut self) {
 #[cfg(feature = "alloc")]
 mod tests {
     use alloc::vec::Vec;
-
     use hex::prelude::*;
 
     use super::*;
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index 2e4701b3..92939934 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -2,6 +2,49 @@
 
 //! Non-public macros
 
+macro_rules! arr_newtype_fmt_impl {
+    ($ty:ident, $bytes:expr $(, $gen:ident: $gent:ident)*) => {
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
+            #[inline]
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                let case = $crate::hex::Case::Lower;
+                if <$ty<$($gen),*> as crate::Hash>::DISPLAY_BACKWARD {
+                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter().rev(), case)
+                } else {
+                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter(), case)
+                }
+            }
+        }
+
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
+            #[inline]
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                let case = $crate::hex::Case::Upper;
+                if <$ty<$($gen),*> as crate::Hash>::DISPLAY_BACKWARD {
+                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter().rev(), case)
+                } else {
+                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter(), case)
+                }
+            }
+        }
+
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
+            #[inline]
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                $crate::_export::_core::fmt::LowerHex::fmt(self, f)
+            }
+        }
+
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> {
+            #[inline]
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                write!(f, "{:#}", self)
+            }
+        }
+    }
+}
+pub(crate) use arr_newtype_fmt_impl;
+
 /// Adds trait impls to the type called `Hash` in the current scope.
 ///
 /// Implpements various conversion traits as well as the [`crate::Hash`] trait.
@@ -20,7 +63,28 @@
 /// `from_engine` obviously implements the finalization algorithm.
 macro_rules! hash_trait_impls {
     ($bits:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => {
-        $crate::impl_bytelike_traits!(Hash, { $bits / 8 }, $reverse $(, $gen: $gent)*);
+        impl<$($gen: $gent),*> $crate::_export::_core::str::FromStr for Hash<$($gen),*> {
+            type Err = $crate::hex::HexToArrayError;
+            fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
+                use $crate::{hex::{FromHex}};
+
+                let mut bytes = <[u8; $bits / 8]>::from_hex(s)?;
+                if $reverse {
+                    bytes.reverse();
+                }
+                Ok(Self::from_byte_array(bytes))
+            }
+        }
+
+        $crate::internal_macros::arr_newtype_fmt_impl!(Hash, $bits / 8 $(, $gen: $gent)*);
+        $crate::serde_impl!(Hash, { $bits / 8 } $(, $gen: $gent)*);
+        $crate::borrow_slice_impl!(Hash $(, $gen: $gent)*);
+
+        impl<$($gen: $gent),*> $crate::_export::_core::convert::AsRef<[u8; $bits / 8]> for Hash<$($gen),*> {
+            fn as_ref(&self) -> &[u8; $bits / 8] {
+                &self.0
+            }
+        }
 
         impl<$($gen: $gent),*> $crate::GeneralHash for Hash<$($gen),*> {
             type Engine = HashEngine;
diff --git a/hashes/src/macros.rs b/hashes/src/macros.rs
index 2d37645c..f30063cd 100644
--- a/hashes/src/macros.rs
+++ b/hashes/src/macros.rs
@@ -133,7 +133,9 @@ macro_rules! hash_newtype {
             $({ $($type_attrs)* })*
         }
 
-        $crate::impl_bytelike_traits!($newtype, { <$newtype as $crate::Hash>::LEN }, <$newtype as $crate::Hash>::DISPLAY_BACKWARD);
+        $crate::hex_fmt_impl!(<$newtype as $crate::Hash>::DISPLAY_BACKWARD, <$newtype as $crate::Hash>::LEN, $newtype);
+        $crate::serde_impl!($newtype, { <$newtype as $crate::Hash>::LEN });
+        $crate::borrow_slice_impl!($newtype);
 
         #[allow(unused)] // Private wrapper types may not need all functions.
         impl $newtype {
@@ -190,84 +192,96 @@ fn to_byte_array(self) -> Self::Bytes { self.to_byte_array() }
 
             fn as_byte_array(&self) -> &Self::Bytes { self.as_byte_array() }
         }
+
+        impl $crate::_export::_core::str::FromStr for $newtype {
+            type Err = $crate::hex::HexToArrayError;
+            fn from_str(s: &str) -> $crate::_export::_core::result::Result<$newtype, Self::Err> {
+                use $crate::{hex::FromHex};
+
+                let mut bytes = <[u8; <Self as $crate::Hash>::LEN]>::from_hex(s)?;
+                if <Self as $crate::Hash>::DISPLAY_BACKWARD {
+                    bytes.reverse();
+                };
+                Ok($newtype(<$hash>::from_byte_array(bytes)))
+            }
+        }
+
+        impl $crate::_export::_core::convert::AsRef<[u8; <$hash as $crate::Hash>::LEN]> for $newtype {
+            fn as_ref(&self) -> &[u8; <$hash as $crate::Hash>::LEN] {
+                AsRef::<[u8; <$hash as $crate::Hash>::LEN]>::as_ref(&self.0)
+            }
+        }
         )+
     };
 }
 
-/// Adds trait impls to a bytelike type.
-///
-/// Implements:
-///
-/// * `str::FromStr`
-/// * `fmt::{LowerHex, UpperHex}` using `hex-conservative`.
-/// * `fmt::{Display, Debug}` by calling `LowerHex`
-/// * `serde::{Deserialize, Serialize}`
-/// * `AsRef[u8; $len]`
-/// * `AsRef[u8]`
-/// * `Borrow<[u8; $len]>`
-/// * `Borrow<[u8]>`
-///
-/// Requires:
-///
-/// * [`hex-conservative`] to publicly available as `$crate::hex`.
-/// * `$ty` must implement `IntoIterator<Item=Borrow<u8>>`.
-///
-/// (See also [`hex-conservative::fmt_hex_exact`].)
-///
-/// ## Parameters
-///
-/// * `ty` - The bytelike type to implement the traits on.
-/// * `$len` - The number of bytes this type has.
-/// * `$reverse` - `true` if the type should be displayed backwards, `false` otherwise.
-/// * `$gen: $gent` - generic type(s) and trait bound(s).
-///
-/// [`hex-conservative`]: <https://crates.io/crates/hex-conservative>
+/// Adds hexadecimal formatting implementation of a trait `$imp` to a given type `$ty`.
 #[doc(hidden)]
 #[macro_export]
-macro_rules! impl_bytelike_traits {
-    ($ty:ident, $len:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => {
-        impl<$($gen: $gent),*> $crate::_export::_core::str::FromStr for $ty<$($gen),*> {
-            type Err = $crate::hex::HexToArrayError;
-
-            fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
-                use $crate::hex::FromHex;
-
-                let mut bytes = <[u8; { $len }]>::from_hex(s)?;
+macro_rules! hex_fmt_impl(
+    ($reverse:expr, $len:expr, $ty:ident) => (
+        $crate::hex_fmt_impl!($reverse, $len, $ty, );
+    );
+    ($reverse:expr, $len:expr, $ty:ident, $($gen:ident: $gent:ident),*) => (
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
+            #[inline]
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
                 if $reverse {
-                    bytes.reverse();
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self).iter().rev(), $crate::hex::Case::Lower)
+                } else {
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self), $crate::hex::Case::Lower)
                 }
-                Ok(Self::from_byte_array(bytes))
             }
         }
 
-        $crate::hex::impl_fmt_traits! {
-            #[display_backward($reverse)]
-            impl<$($gen: $gent),*> fmt_traits for $ty<$($gen),*> {
-                const LENGTH: usize = ($len); // parens required due to rustc parser weirdness
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
+            #[inline]
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                if $reverse {
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self).iter().rev(), $crate::hex::Case::Upper)
+                } else {
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self), $crate::hex::Case::Upper)
+                }
             }
         }
 
-        $crate::serde_impl!($ty, $len $(, $gen: $gent)*);
-
-        impl<$($gen: $gent),*> $crate::_export::_core::convert::AsRef<[u8; { $len }]> for $ty<$($gen),*> {
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
             #[inline]
-            fn as_ref(&self) -> &[u8; { $len }] { self.as_byte_array() }
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                $crate::_export::_core::fmt::LowerHex::fmt(&self, f)
+            }
         }
 
-        impl<$($gen: $gent),*> $crate::_export::_core::convert::AsRef<[u8]> for $ty<$($gen),*> {
+        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> {
             #[inline]
-            fn as_ref(&self) -> &[u8] { self.as_byte_array() }
+            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
+                write!(f, "{}", self)
+            }
         }
+    );
+);
 
-        impl<$($gen: $gent),*> $crate::_export::_core::borrow::Borrow<[u8; { $len }]> for $ty<$($gen),*>  {
-            fn borrow(&self) -> &[u8; { $len }] {  self.as_byte_array() }
+/// Adds slicing traits implementations to a given type `$ty`
+#[doc(hidden)]
+#[macro_export]
+macro_rules! borrow_slice_impl(
+    ($ty:ident) => (
+        $crate::borrow_slice_impl!($ty, );
+    );
+    ($ty:ident, $($gen:ident: $gent:ident),*) => (
+        impl<$($gen: $gent),*> $crate::_export::_core::borrow::Borrow<[u8]> for $ty<$($gen),*>  {
+            fn borrow(&self) -> &[u8] {
+                self.as_byte_array()
+            }
         }
 
-        impl<$($gen: $gent),*> $crate::_export::_core::borrow::Borrow<[u8]> for $ty<$($gen),*>  {
-            fn borrow(&self) -> &[u8] {  self.as_byte_array() }
+        impl<$($gen: $gent),*> $crate::_export::_core::convert::AsRef<[u8]> for $ty<$($gen),*>  {
+            fn as_ref(&self) -> &[u8] {
+                self.as_byte_array()
+            }
         }
-    }
-}
+    )
+);
 
 // Generates the struct only (no impls)
 //
diff --git a/nightly-version b/nightly-version
index ddde6232..3f02ee57 100644
--- a/nightly-version
+++ b/nightly-version
@@ -1,1 +1,1 @@
-nightly-2024-11-03
+nightly-2024-10-30
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index b8f09ac8..6991b331 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -49,13 +49,13 @@
 #[doc(inline)]
 pub use units::{
     block::{BlockHeight, BlockInterval},
-    fee_rate::{self, FeeRate},
-    weight::{self, Weight},
+    fee_rate::FeeRate,
+    weight::Weight,
 };
 
 #[doc(inline)]
 pub use self::{
-    block::{BlockHash, Header as BlockHeader, WitnessCommitment},
+    block::{BlockHash, WitnessCommitment},
     merkle_tree::{TxMerkleNode, WitnessMerkleNode},
     pow::CompactTarget,
     sequence::Sequence,
@@ -66,7 +66,7 @@
 #[cfg(feature = "alloc")]
 pub use self::{
     locktime::{absolute, relative},
-    transaction::{Transaction, TxIn, TxOut},
+    transaction::{TxIn, TxOut, Transaction},
     witness::Witness,
 };
 
diff --git a/primitives/src/locktime/absolute.rs b/primitives/src/locktime/absolute.rs
index 09a32ea5..ca548cdb 100644
--- a/primitives/src/locktime/absolute.rs
+++ b/primitives/src/locktime/absolute.rs
@@ -14,8 +14,8 @@
 use mutagen::mutate;
 use units::parse::{self, PrefixedHexError, UnprefixedHexError};
 
-#[cfg(all(doc, feature = "alloc"))]
-use crate::{absolute, Transaction};
+#[cfg(doc)]
+use crate::absolute;
 
 #[rustfmt::skip]                // Keep public re-exports separate.
 #[doc(inline)]
@@ -24,14 +24,14 @@
 /// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds
 /// since epoch).
 ///
-/// Used for transaction lock time (`nLockTime` in Bitcoin Core and [`Transaction::lock_time`]
+/// Used for transaction lock time (`nLockTime` in Bitcoin Core and `Transaction::lock_time`
 /// in this library) and also for the argument to opcode 'OP_CHECKLOCKTIMEVERIFY`.
 ///
 /// ### Note on ordering
 ///
 /// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total
 /// ordering on locktimes. We therefore have implemented [`PartialOrd`] but not [`Ord`].
-/// For [`Transaction`], which has a locktime field, we implement a total ordering to make
+/// For `Transaction`, which has a locktime field, we implement a total ordering to make
 /// it easy to store transactions in sorted data structures, and use the locktime's 32-bit integer
 /// consensus encoding to order it. We also implement [`ordered::ArbitraryOrd`] if the "ordered"
 /// feature is enabled.
@@ -85,7 +85,7 @@ pub enum LockTime {
 }
 
 impl LockTime {
-    /// If [`Transaction::lock_time`] is set to zero it is ignored, in other words a
+    /// If `Transaction::lock_time` is set to zero it is ignored, in other words a
     /// transaction with nLocktime==0 is able to be included immediately in any block.
     pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO);
 
@@ -197,7 +197,7 @@ pub const fn is_block_time(&self) -> bool { !self.is_block_height() }
     /// blocktime based lock it is checked against `time`.
     ///
     /// A 'timelock constraint' refers to the `n` from `n OP_CHEKCLOCKTIMEVERIFY`, this constraint
-    /// is satisfied if a transaction with nLockTime ([`Transaction::lock_time`]) set to
+    /// is satisfied if a transaction with nLockTime (`Transaction::lock_time`) set to
     /// `height`/`time` is valid.
     ///
     /// # Examples
diff --git a/primitives/src/locktime/relative.rs b/primitives/src/locktime/relative.rs
index 67adea78..e50d36ad 100644
--- a/primitives/src/locktime/relative.rs
+++ b/primitives/src/locktime/relative.rs
@@ -12,9 +12,9 @@
 #[cfg(all(test, mutate))]
 use mutagen::mutate;
 
+#[cfg(doc)]
+use crate::relative;
 use crate::Sequence;
-#[cfg(all(doc, feature = "alloc"))]
-use crate::{relative, TxIn};
 
 #[rustfmt::skip]                // Keep public re-exports separate.
 #[doc(inline)]
@@ -22,7 +22,7 @@
 
 /// A relative lock time value, representing either a block height or time (512 second intervals).
 ///
-/// Used for sequence numbers (`nSequence` in Bitcoin Core and [`TxIn::sequence`]
+/// Used for sequence numbers (`nSequence` in Bitcoin Core and `TxIn::sequence`
 /// in this library) and also for the argument to opcode 'OP_CHECKSEQUENCEVERIFY`.
 ///
 /// ### Note on ordering
diff --git a/primitives/src/sequence.rs b/primitives/src/sequence.rs
index 5e2a7f32..7bac4f1b 100644
--- a/primitives/src/sequence.rs
+++ b/primitives/src/sequence.rs
@@ -3,7 +3,8 @@
 //! Bitcoin transaction input sequence number.
 //!
 //! The sequence field is used for:
-//! - Indicating whether absolute lock-time (specified in `lock_time` field of [`Transaction`]) is enabled.
+//! - Indicating whether absolute lock-time (specified in `lock_time` field of `Transaction`)
+//!   is enabled.
 //! - Indicating and encoding [BIP-68] relative lock-times.
 //! - Indicating whether a transaction opts-in to [BIP-125] replace-by-fee.
 //!
@@ -27,8 +28,6 @@
 
 #[cfg(feature = "alloc")]
 use crate::locktime::relative;
-#[cfg(all(doc, feature = "alloc"))]
-use crate::transaction::Transaction;
 
 /// Bitcoin transaction input sequence number.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -75,7 +74,7 @@ impl Sequence {
     /// BIP-68 relative lock time type flag mask.
     const LOCK_TYPE_MASK: u32 = 0x00400000;
 
-    /// Returns `true` if the sequence number enables absolute lock-time ([`Transaction::lock_time`]).
+    /// Returns `true` if the sequence number enables absolute lock-time (`Transaction::lock_time`).
     #[inline]
     pub fn enables_absolute_lock_time(&self) -> bool { *self != Sequence::MAX }
 
diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs
index ed365f6a..78219c73 100644
--- a/units/src/amount/signed.rs
+++ b/units/src/amount/signed.rs
@@ -46,19 +46,49 @@ impl SignedAmount {
     /// The maximum value of an amount.
     pub const MAX: SignedAmount = SignedAmount(i64::MAX);
 
-    /// Create an [`SignedAmount`] with satoshi precision and the given number of satoshis.
+    /// Creates a [`SignedAmount`] with satoshi precision and the given number of satoshis.
     pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
 
     /// Gets the number of satoshis in this [`SignedAmount`].
     pub const fn to_sat(self) -> i64 { self.0 }
 
-    /// Convert from a value expressing bitcoins to an [`SignedAmount`].
+    /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
     #[cfg(feature = "alloc")]
     pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
         SignedAmount::from_float_in(btc, Denomination::Bitcoin)
     }
 
-    /// Parse a decimal string as a value in the given denomination.
+    /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
+    ///
+    /// # Errors
+    ///
+    /// The function errors if the argument multiplied by the number of sats
+    /// per bitcoin overflows an `i64` type.
+    pub fn from_int_btc(btc: i64) -> Result<SignedAmount, OutOfRangeError> {
+        match btc.checked_mul(100_000_000) {
+            Some(amount) => Ok(SignedAmount::from_sat(amount)),
+            None => Err(OutOfRangeError {
+                is_signed: true,
+                is_greater_than_max: true,
+            })
+        }
+    }
+
+    /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
+    /// in const context.
+    ///
+    /// # Panics
+    ///
+    /// The function panics if the argument multiplied by the number of sats
+    /// per bitcoin overflows an `i64` type.
+    pub const fn from_int_btc_const(btc: i64) -> SignedAmount {
+        match btc.checked_mul(100_000_000) {
+            Some(amount) => SignedAmount::from_sat(amount),
+            None => panic!("checked_mul overflowed"),
+        }
+    }
+
+    /// Parses a decimal string as a value in the given denomination.
     ///
     /// Note: This only parses the value string.  If you want to parse a value
     /// with denomination, use [`FromStr`].
@@ -75,10 +105,10 @@ pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAm
         }
     }
 
-    /// Parses amounts with denomination suffix like they are produced with
-    /// [`Self::to_string_with_denomination`] or with [`fmt::Display`].
-    /// If you want to parse only the amount without the denomination,
-    /// use [`Self::from_str_in`].
+    /// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
+    /// or with [`fmt::Display`].
+    ///
+    /// If you want to parse only the amount without the denomination, use [`Self::from_str_in`].
     pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
         let (amt, denom) = split_amount_and_denomination(s)?;
         SignedAmount::from_str_in(amt, denom).map_err(Into::into)
@@ -94,9 +124,15 @@ pub fn to_float_in(self, denom: Denomination) -> f64 {
 
     /// Express this [`SignedAmount`] as a floating-point value in Bitcoin.
     ///
-    /// Equivalent to `to_float_in(Denomination::Bitcoin)`.
-    ///
     /// Please be aware of the risk of using floating-point numbers.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use bitcoin_units::amount::{SignedAmount, Denomination};
+    /// let amount = SignedAmount::from_sat(100_000);
+    /// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin))
+    /// ```
     #[cfg(feature = "alloc")]
     pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
 
@@ -118,7 +154,7 @@ pub fn from_float_in(
         SignedAmount::from_str_in(&value.to_string(), denom)
     }
 
-    /// Create an object that implements [`fmt::Display`] using specified denomination.
+    /// Creates an object that implements [`fmt::Display`] using specified denomination.
     pub fn display_in(self, denomination: Denomination) -> Display {
         Display {
             sats_abs: self.unsigned_abs().to_sat(),
@@ -127,7 +163,7 @@ pub fn display_in(self, denomination: Denomination) -> Display {
         }
     }
 
-    /// Create an object that implements [`fmt::Display`] dynamically selecting denomination.
+    /// Creates an object that implements [`fmt::Display`] dynamically selecting denomination.
     ///
     /// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To
     /// avoid confusion the denomination is always shown.
@@ -139,7 +175,7 @@ pub fn display_dynamic(self) -> Display {
         }
     }
 
-    /// Format the value of this [`SignedAmount`] in the given denomination.
+    /// Formats the value of this [`SignedAmount`] in the given denomination.
     ///
     /// Does not include the denomination.
     #[rustfmt::skip]
@@ -148,14 +184,14 @@ pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::R
         fmt_satoshi_in(self.unsigned_abs().to_sat(), self.is_negative(), f, denom, false, FormatOptions::default())
     }
 
-    /// Get a string number of this [`SignedAmount`] in the given denomination.
+    /// Returns a formatted string representing this [`SignedAmount`] in the given denomination.
     ///
     /// Does not include the denomination.
     #[cfg(feature = "alloc")]
     pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
 
-    /// Get a formatted string of this [`SignedAmount`] in the given denomination,
-    /// suffixed with the abbreviation for the denomination.
+    /// Returns a formatted string representing this [`Amount`] in the given denomination, suffixed
+    /// with the abbreviation for the denomination.
     #[cfg(feature = "alloc")]
     pub fn to_string_with_denomination(self, denom: Denomination) -> String {
         self.display_in(denom).show_denomination().to_string()
@@ -166,7 +202,7 @@ pub fn to_string_with_denomination(self, denom: Denomination) -> String {
     /// Get the absolute value of this [`SignedAmount`].
     pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
 
-    /// Get the absolute value of this [`SignedAmount`] returning [`Amount`].
+    /// Gets the absolute value of this [`SignedAmount`] returning [`Amount`].
     pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.0.unsigned_abs()) }
 
     /// Returns a number representing sign of this [`SignedAmount`].
@@ -218,8 +254,7 @@ pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
 
     /// Checked integer division.
     ///
-    /// Be aware that integer division loses the remainder if no exact division
-    /// can be made.
+    /// Be aware that integer division loses the remainder if no exact division can be made.
     ///
     /// Returns [`None`] if overflow occurred.
     pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
@@ -361,12 +396,7 @@ impl FromStr for SignedAmount {
 
     /// Parses a string slice where the slice includes a denomination.
     ///
-    /// If the string slice is zero or negative zero, then no denomination is required.
-    ///
-    /// # Returns
-    ///
-    /// `Ok(Amount)` if the string amount and denomination parse successfully,
-    /// otherwise, return `Err(ParseError)`.
+    /// If the returned value would be zero or negative zero, then no denomination is required.
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         let result = SignedAmount::from_str_with_denomination(s);
 
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index 46738bbd..c38257c8 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -46,9 +46,9 @@ impl Amount {
     /// Exactly one satoshi.
     pub const ONE_SAT: Amount = Amount(1);
     /// Exactly one bitcoin.
-    pub const ONE_BTC: Amount = Self::from_int_btc(1);
+    pub const ONE_BTC: Amount = Self::from_int_btc_const(1);
     /// The maximum value allowed as an amount. Useful for sanity checking.
-    pub const MAX_MONEY: Amount = Self::from_int_btc(21_000_000);
+    pub const MAX_MONEY: Amount = Self::from_int_btc_const(21_000_000);
     /// The minimum value of an amount.
     pub const MIN: Amount = Amount::ZERO;
     /// The maximum value of an amount.
@@ -62,20 +62,36 @@ pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) }
     /// Gets the number of satoshis in this [`Amount`].
     pub const fn to_sat(self) -> u64 { self.0 }
 
-    /// Converts from a value expressing bitcoins to an [`Amount`].
+    /// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
     #[cfg(feature = "alloc")]
     pub fn from_btc(btc: f64) -> Result<Amount, ParseAmountError> {
         Amount::from_float_in(btc, Denomination::Bitcoin)
     }
 
-    /// Converts from a value expressing integer values of bitcoins to an [`Amount`]
+    /// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
+    ///
+    /// # Errors
+    ///
+    /// The function errors if the argument multiplied by the number of sats
+    /// per bitcoin overflows a `u64` type.
+    pub fn from_int_btc(btc: u64) -> Result<Amount, OutOfRangeError> {
+        match btc.checked_mul(100_000_000) {
+            Some(amount) => Ok(Amount::from_sat(amount)),
+            None => Err(OutOfRangeError {
+                is_signed: false,
+                is_greater_than_max: true,
+            })
+        }
+    }
+
+    /// Converts from a value expressing a whole number of bitcoin to an [`Amount`]
     /// in const context.
     ///
     /// # Panics
     ///
     /// The function panics if the argument multiplied by the number of sats
-    /// per bitcoin overflows a u64 type.
-    pub const fn from_int_btc(btc: u64) -> Amount {
+    /// per bitcoin overflows a `u64` type.
+    pub const fn from_int_btc_const(btc: u64) -> Amount {
         match btc.checked_mul(100_000_000) {
             Some(amount) => Amount::from_sat(amount),
             None => panic!("checked_mul overflowed"),
@@ -95,10 +111,10 @@ pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountEr
         Ok(Amount::from_sat(satoshi))
     }
 
-    /// Parses amounts with denomination suffix like they are produced with
-    /// [`Self::to_string_with_denomination`] or with [`fmt::Display`].
-    /// If you want to parse only the amount without the denomination,
-    /// use [`Self::from_str_in`].
+    /// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
+    /// or with [`fmt::Display`].
+    ///
+    /// If you want to parse only the amount without the denomination, use [`Self::from_str_in`].
     pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseError> {
         let (amt, denom) = split_amount_and_denomination(s)?;
         Amount::from_str_in(amt, denom).map_err(Into::into)
@@ -174,14 +190,14 @@ pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::R
         fmt_satoshi_in(self.to_sat(), false, f, denom, false, FormatOptions::default())
     }
 
-    /// Returns a formatted string of this [`Amount`] in the given denomination.
+    /// Returns a formatted string representing this [`Amount`] in the given denomination.
     ///
     /// Does not include the denomination.
     #[cfg(feature = "alloc")]
     pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
 
-    /// Returns a formatted string of this [`Amount`] in the given denomination,
-    /// suffixed with the abbreviation for the denomination.
+    /// Returns a formatted string representing this [`Amount`] in the given denomination, suffixed
+    /// with the abbreviation for the denomination.
     #[cfg(feature = "alloc")]
     pub fn to_string_with_denomination(self, denom: Denomination) -> String {
         self.display_in(denom).show_denomination().to_string()
@@ -210,8 +226,8 @@ pub fn checked_mul(self, rhs: u64) -> Option<Amount> { self.0.checked_mul(rhs).m
 
     /// Checked integer division.
     ///
-    /// Be aware that integer division loses the remainder if no exact division
-    /// can be made.
+    /// Be aware that integer division loses the remainder if no exact division can be made.
+    ///
     /// Returns [`None`] if overflow occurred.
     pub fn checked_div(self, rhs: u64) -> Option<Amount> { self.0.checked_div(rhs).map(Amount) }
 
@@ -219,9 +235,9 @@ pub fn checked_div(self, rhs: u64) -> Option<Amount> { self.0.checked_div(rhs).m
     ///
     /// Be aware that integer division loses the remainder if no exact division
     /// can be made.  This method rounds up ensuring the transaction fee-rate is
-    /// sufficient.  If you wish to round-down, use the unchecked version instead.
+    /// sufficient. If you wish to round down use `amount / weight`.
     ///
-    /// [`None`] is returned if an overflow occurred.
+    /// Returns [`None`] if overflow occurred.
     #[cfg(feature = "alloc")]
     pub fn checked_div_by_weight(self, rhs: Weight) -> Option<FeeRate> {
         let sats = self.0.checked_mul(1000)?;
@@ -269,7 +285,9 @@ fn default() -> Self { Amount::ZERO }
 }
 
 impl fmt::Debug for Amount {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} SAT", self.to_sat()) }
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Amount({} SAT)", self.to_sat())
+    }
 }
 
 // No one should depend on a binding contract for Display for this type.
@@ -343,12 +361,7 @@ impl FromStr for Amount {
 
     /// Parses a string slice where the slice includes a denomination.
     ///
-    /// If the string slice is zero, then no denomination is required.
-    ///
-    /// # Returns
-    ///
-    /// `Ok(Amount)` if the string amount and denomination parse successfully,
-    /// otherwise, return `Err(ParseError)`.
+    /// If the returned value would be zero or negative zero, then no denomination is required.
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         let result = Amount::from_str_with_denomination(s);
 
diff --git a/units/src/fee_rate.rs b/units/src/fee_rate.rs
index 237de0f5..b3974422 100644
--- a/units/src/fee_rate.rs
+++ b/units/src/fee_rate.rs
@@ -211,6 +211,10 @@ fn mul(self, rhs: Weight) -> Self::Output { rhs * self }
 impl Div<Weight> for Amount {
     type Output = FeeRate;
 
+    /// Truncating integer division.
+    ///
+    /// This is likely the wrong thing for a user dividing an amount by a weight. Consider using
+    /// `checked_div_by_weight` instead.
     fn div(self, rhs: Weight) -> Self::Output { FeeRate(self.to_sat() * 1000 / rhs.to_wu()) }
 }
 
diff --git a/units/src/lib.rs b/units/src/lib.rs
index f0e3994a..1d7f47ab 100644
--- a/units/src/lib.rs
+++ b/units/src/lib.rs
@@ -4,6 +4,7 @@
 //!
 //! This library provides basic types used by the Rust Bitcoin ecosystem.
 
+#![no_std]
 // Experimental features we need.
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 // Coding conventions.
@@ -13,7 +14,6 @@
 // Exclude lints we don't think are valuable.
 #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
 #![allow(clippy::manual_range_contains)] // More readable than clippy's format.
-#![no_std]
 
 #[cfg(feature = "alloc")]
 extern crate alloc;
@@ -22,16 +22,23 @@
 extern crate std;
 
 pub mod amount;
+#[cfg(feature = "alloc")]
 pub mod block;
+#[cfg(feature = "alloc")]
 pub mod fee_rate;
+#[cfg(feature = "alloc")]
 pub mod locktime;
+#[cfg(feature = "alloc")]
 pub mod parse;
+#[cfg(feature = "alloc")]
 pub mod weight;
 
+#[doc(inline)]
+pub use self::amount::{Amount, SignedAmount};
+#[cfg(feature = "alloc")]
 #[doc(inline)]
 #[rustfmt::skip]
 pub use self::{
-    amount::{Amount, SignedAmount},
     block::{BlockHeight, BlockInterval},
     fee_rate::FeeRate,
     weight::Weight
diff --git a/units/src/locktime/absolute.rs b/units/src/locktime/absolute.rs
index f5a82c21..92283468 100644
--- a/units/src/locktime/absolute.rs
+++ b/units/src/locktime/absolute.rs
@@ -2,11 +2,15 @@
 
 //! Provides [`Height`] and [`Time`] types used by the `rust-bitcoin` `absolute::LockTime` type.
 
+#[cfg(feature = "alloc")]
+use alloc::{boxed::Box, string::String};
 use core::fmt;
 
 use internals::error::InputString;
 
-use crate::parse::{self, ParseIntError};
+use crate::parse::ParseIntError;
+#[cfg(feature = "alloc")]
+use crate::parse;
 
 /// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
 ///
@@ -307,9 +311,7 @@ enum ParseError {
 
 impl ParseError {
     fn invalid_int<S: Into<InputString>>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self {
-        move |source| {
-            Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source })
-        }
+        move |source| Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true , source })
     }
 
     fn display(
@@ -324,29 +326,13 @@ fn display(
         use ParseError::*;
 
         match self {
-            ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
-                if *source.kind() == IntErrorKind::PosOverflow =>
-            {
+            ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) if *source.kind() == IntErrorKind::PosOverflow => {
                 // Outputs "failed to parse <input_string> as absolute Height/Time (<subject> is above limit <upper_bound>)"
-                write!(
-                    f,
-                    "{} ({} is above limit {})",
-                    input.display_cannot_parse("absolute Height/Time"),
-                    subject,
-                    upper_bound
-                )
+                write!(f, "{} ({} is above limit {})", input.display_cannot_parse("absolute Height/Time"), subject, upper_bound)
             }
-            ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
-                if *source.kind() == IntErrorKind::NegOverflow =>
-            {
+            ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) if *source.kind() == IntErrorKind::NegOverflow => {
                 // Outputs "failed to parse <input_string> as absolute Height/Time (<subject> is below limit <lower_bound>)"
-                write!(
-                    f,
-                    "{} ({} is below limit {})",
-                    input.display_cannot_parse("absolute Height/Time"),
-                    subject,
-                    lower_bound
-                )
+                write!(f, "{} ({} is below limit {})", input.display_cannot_parse("absolute Height/Time"), subject, lower_bound)
             }
             ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
                 write!(f, "{} ({})", input.display_cannot_parse("absolute Height/Time"), subject)
@@ -368,12 +354,8 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         use ParseError::*;
 
         match self {
-            ParseInt(ParseIntError { source, .. })
-                if *source.kind() == IntErrorKind::PosOverflow =>
-                None,
-            ParseInt(ParseIntError { source, .. })
-                if *source.kind() == IntErrorKind::NegOverflow =>
-                None,
+            ParseInt(ParseIntError { source, .. }) if *source.kind() == IntErrorKind::PosOverflow => None,
+            ParseInt(ParseIntError { source, .. }) if *source.kind() == IntErrorKind::NegOverflow => None,
             ParseInt(ParseIntError { source, .. }) => Some(source),
             Conversion(_) => None,
         }
diff --git a/units/src/parse.rs b/units/src/parse.rs
index 7eb3f19c..a2ee0e4d 100644
--- a/units/src/parse.rs
+++ b/units/src/parse.rs
@@ -2,11 +2,12 @@
 
 //! Parsing utilities.
 
+use alloc::string::String;
 use core::fmt;
 use core::str::FromStr;
 
-use internals::error::InputString;
 use internals::write_err;
+use internals::error::InputString;
 
 /// Error with rich context returned when a string can't be parsed as an integer.
 ///
@@ -106,9 +107,10 @@ fn try_from(s: $from) -> core::result::Result<Self, Self::Error> {
 #[macro_export]
 macro_rules! impl_parse_str_from_int_infallible {
     ($to:ident, $inner:ident, $fn:ident) => {
-        $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn);
-        #[cfg(feature = "alloc")]
-        $crate::impl_tryfrom_str_from_int_infallible!(alloc::string::String, $to, $inner, $fn; alloc::boxed::Box<str>, $to, $inner, $fn);
+        #[cfg(all(feature = "alloc", not(feature = "std")))]
+        $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; alloc::string::String, $to, $inner, $fn; alloc::boxed::Box<str>, $to, $inner, $fn);
+        #[cfg(feature = "std")]
+        $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; std::string::String, $to, $inner, $fn; std::boxed::Box<str>, $to, $inner, $fn);
 
         impl core::str::FromStr for $to {
             type Err = $crate::parse::ParseIntError;
@@ -141,9 +143,7 @@ fn try_from(s: $from) -> core::result::Result<Self, Self::Error> {
 #[macro_export]
 macro_rules! impl_parse_str {
     ($to:ty, $err:ty, $inner_fn:expr) => {
-        $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn);
-        #[cfg(feature = "alloc")]
-        $crate::impl_tryfrom_str!(alloc::string::String, $to, $err, $inner_fn; alloc::boxed::Box<str>, $to, $err, $inner_fn);
+        $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn; String, $to, $err, $inner_fn; Box<str>, $to, $err, $inner_fn);
 
         impl core::str::FromStr for $to {
             type Err = $err;
@@ -166,7 +166,7 @@ pub fn hex_remove_prefix(s: &str) -> Result<&str, PrefixedHexError> {
     } else if let Some(checked) = s.strip_prefix("0X") {
         Ok(checked)
     } else {
-        Err(MissingPrefixError::new(s).into())
+        Err(MissingPrefixError::new(s.into()).into())
     }
 }
 
@@ -177,7 +177,7 @@ pub fn hex_remove_prefix(s: &str) -> Result<&str, PrefixedHexError> {
 /// If the input string contains a prefix.
 pub fn hex_check_unprefixed(s: &str) -> Result<&str, UnprefixedHexError> {
     if s.starts_with("0x") || s.starts_with("0X") {
-        return Err(ContainsPrefixError::new(s).into());
+        return Err(ContainsPrefixError::new(s.into()).into());
     }
     Ok(s)
 }
@@ -374,17 +374,17 @@ fn from(e: ParseIntError) -> Self { Self::ParseInt(e) }
 /// Error returned when a hex string is missing a prefix (e.g. `0x`).
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct MissingPrefixError {
-    hex: InputString,
+    hex: String,
 }
 
 impl MissingPrefixError {
     /// Creates an error from the string with the missing prefix.
-    pub(crate) fn new(hex: &str) -> Self { Self { hex: hex.into() } }
+    pub(crate) fn new(hex: String) -> Self { Self { hex } }
 }
 
 impl fmt::Display for MissingPrefixError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{} because it is missing the '0x' prefix", self.hex.display_cannot_parse("hex"))
+        write!(f, "hex string is missing a prefix (e.g. 0x): {}", self.hex)
     }
 }
 
@@ -394,17 +394,17 @@ impl std::error::Error for MissingPrefixError {}
 /// Error when hex string contains a prefix (e.g. 0x).
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct ContainsPrefixError {
-    hex: InputString,
+    hex: String,
 }
 
 impl ContainsPrefixError {
     /// Creates an error from the string that contains the prefix.
-    pub(crate) fn new(hex: &str) -> Self { Self { hex: hex.into() } }
+    pub(crate) fn new(hex: String) -> Self { Self { hex } }
 }
 
 impl fmt::Display for ContainsPrefixError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{} because it contains the '0x' prefix", self.hex.display_cannot_parse("hex"))
+        write!(f, "hex string contains a prefix: {}", self.hex)
     }
 }
 


Test patch:
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index a695bb10..9c3e6bf9 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -65,13 +65,13 @@ fn from_str_zero_without_denomination() {
 
 #[test]
 fn from_int_btc() {
-    let amt = Amount::from_int_btc(2);
+    let amt = Amount::from_int_btc_const(2);
     assert_eq!(Amount::from_sat(200_000_000), amt);
 }
 
 #[should_panic]
 #[test]
-fn from_int_btc_panic() { Amount::from_int_btc(u64::MAX); }
+fn from_int_btc_panic() { Amount::from_int_btc_const(u64::MAX); }
 
 #[test]
 fn test_signed_amount_try_from_amount() {