Repository: rust-bitcoin/rust-bitcoin
Pull request #1811 (2023-04-24 14:49:06)

Use Amount type for TxOut value field
Propose using `Amount` type for the `TxOut` `value` field.  I only implemented `Decodable ` and `Encodable` enough to compile but this needs to completed obviously if using `Amount` seems like a good idea. 

yancyribbens: Also the tests would need to be updated as well to use `Amount`
RCasatta: I inadvertently pressed ready for review on mobile and now I can't find how to revert, sorry
Kixunil: Reverted back to draft.

Concept ACK, I originally wanted to do this myself, happy to see I won't have to! :)
yancyribbens: hmm almost ready.  Looks like there are some changes for nightly and 1.48.0 needed still.
Kixunil: Looks like it's not 1.48-related but serde-related.
yancyribbens: I believe the CI failure is unrelated to this PR.  Seems like cross rs can't find the docker container currently.
yancyribbens: Otherwise, this should be good to ship imo
yancyribbens: Is there some considerations that need to made since this is a breaking API change?  Downstream crates that use `u64` for `TxOut` value should get a deprecation warning?
apoelstra: @yancyribbens unfortunately AFAICT there is no way to make this a "nice" breaking change. But FWIW I think it will get into the first pre-release as the only major thing, and the fix is nearly a search-and-replace operation, so I think it might be okay.
tcharding: We are going to get merge conflicts if https://github.com/rust-bitcoin/rust-bitcoin/pull/1829 merges ahead of this.
Kixunil: This will actually simplify tons of code that contain conversions between `u64` and `Amount`.
yancyribbens: rebased to fix merge conflicts.

Patch:
diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs
index cdee6755..f3609a47 100644
--- a/bitcoin/examples/ecdsa-psbt.rs
+++ b/bitcoin/examples/ecdsa-psbt.rs
@@ -192,11 +192,8 @@ fn create_psbt<C: Verification>(&self, secp: &Secp256k1<C>) -> Result<Psbt> {
                 witness: Witness::default(),
             }],
             output: vec![
-                TxOut { value: to_amount.to_sat(), script_pubkey: to_address.script_pubkey() },
-                TxOut {
-                    value: change_amount.to_sat(),
-                    script_pubkey: change_address.script_pubkey(),
-                },
+                TxOut { value: to_amount, script_pubkey: to_address.script_pubkey() },
+                TxOut { value: change_amount, script_pubkey: change_address.script_pubkey() },
             ],
         };
 
@@ -282,7 +279,7 @@ fn previous_output() -> TxOut {
         .expect("failed to parse input utxo scriptPubkey");
     let amount = Amount::from_str(INPUT_UTXO_VALUE).expect("failed to parse input utxo value");
 
-    TxOut { value: amount.to_sat(), script_pubkey }
+    TxOut { value: amount, script_pubkey }
 }
 
 struct Error(Box<dyn std::error::Error>);
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index 62b95356..0ad84de0 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -40,7 +40,7 @@
     "5120be27fa8b1f5278faf82cab8da23e8761f8f9bd5d5ebebbb37e0e12a70d92dd16";
 const UTXO_PUBKEY: &str = "a6ac32163539c16b6b5dbbca01b725b8e8acaa5f821ba42c80e7940062140d19";
 const UTXO_MASTER_FINGERPRINT: &str = "e61b318f";
-const ABSOLUTE_FEES_IN_SATS: u64 = 1000;
+const ABSOLUTE_FEES_IN_SATS: Amount = Amount::from_sat(1_000);
 
 // UTXO_1 will be used for spending example 1
 const UTXO_1: P2trUtxo = P2trUtxo {
@@ -49,7 +49,7 @@
     script_pubkey: UTXO_SCRIPT_PUBKEY,
     pubkey: UTXO_PUBKEY,
     master_fingerprint: UTXO_MASTER_FINGERPRINT,
-    amount_in_sats: 50 * COIN_VALUE, // 50 BTC
+    amount_in_sats: Amount::from_sat(50 * 100_000_000), // 50 BTC
     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: 50 * COIN_VALUE,
+    amount_in_sats: Amount::from_sat(50 * 100_000_000), // 50 BTC
     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: 50 * COIN_VALUE,
+    amount_in_sats: Amount::from_sat(50 * 100_000_000), // 50 BTC
     derivation_path: BIP86_DERIVATION_PATH,
 };
 
@@ -80,7 +80,6 @@
 
 use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint};
 use bitcoin::consensus::encode;
-use bitcoin::constants::COIN_VALUE;
 use bitcoin::key::{TapTweak, XOnlyPublicKey};
 use bitcoin::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP};
 use bitcoin::psbt::{self, Input, Output, Psbt, PsbtSighashType};
@@ -105,7 +104,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     let change_address =
         Address::from_str("bcrt1pz449kexzydh2kaypatup5ultru3ej284t6eguhnkn6wkhswt0l7q3a7j76")?
             .require_network(Network::Regtest)?;
-    let amount_to_send_in_sats = COIN_VALUE;
+    let amount_to_send_in_sats = Amount::ONE_BTC;
     let change_amount = UTXO_1
         .amount_in_sats
         .checked_sub(amount_to_send_in_sats)
@@ -216,7 +215,7 @@ struct P2trUtxo<'a> {
     script_pubkey: &'a str,
     pubkey: &'a str,
     master_fingerprint: &'a str,
-    amount_in_sats: u64,
+    amount_in_sats: Amount,
     derivation_path: &'a str,
 }
 
@@ -259,9 +258,7 @@ fn generate_bip86_key_spend_tx(
         witness_utxo: {
             let script_pubkey = ScriptBuf::from_hex(input_utxo.script_pubkey)
                 .expect("failed to parse input utxo scriptPubkey");
-            let amount = Amount::from_sat(from_amount);
-
-            Some(TxOut { value: amount.to_sat(), script_pubkey })
+            Some(TxOut { value: from_amount, script_pubkey })
         },
         tap_key_origins: origins,
         ..Default::default()
@@ -448,9 +445,7 @@ fn create_inheritance_funding_tx(
         let input = Input {
             witness_utxo: {
                 let script_pubkey = script_pubkey;
-                let amount = Amount::from_sat(value);
-
-                Some(TxOut { value: amount.to_sat(), script_pubkey })
+                Some(TxOut { value, script_pubkey })
             },
             tap_key_origins: origins,
             tap_merkle_root: taproot_spend_info.merkle_root(),
@@ -594,9 +589,9 @@ fn refresh_tx(
             let input = Input {
                 witness_utxo: {
                     let script_pubkey = output_script_pubkey;
-                    let amount = Amount::from_sat(output_value);
+                    let amount = output_value;
 
-                    Some(TxOut { value: amount.to_sat(), script_pubkey })
+                    Some(TxOut { value: amount, script_pubkey })
                 },
                 tap_key_origins: origins,
                 tap_merkle_root: taproot_spend_info.merkle_root(),
diff --git a/bitcoin/src/amount.rs b/bitcoin/src/amount.rs
index a5c4e6ef..2b1e95c4 100644
--- a/bitcoin/src/amount.rs
+++ b/bitcoin/src/amount.rs
@@ -11,6 +11,8 @@
 use core::str::FromStr;
 use core::{default, ops};
 
+use crate::consensus::encode::{self, Decodable, Encodable};
+use crate::io;
 use crate::prelude::*;
 
 /// A set of denominations in which amounts can be expressed.
@@ -483,6 +485,8 @@ fn fmt_satoshi_in(
 /// the checked arithmetic methods.
 ///
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
 pub struct Amount(u64);
 
 impl Amount {
@@ -660,6 +664,20 @@ pub fn to_signed(self) -> Result<SignedAmount, ParseAmountError> {
     }
 }
 
+impl Decodable for Amount {
+    #[inline]
+    fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+        Ok(Amount(Decodable::consensus_decode(r)?))
+    }
+}
+
+impl Encodable for Amount {
+    #[inline]
+    fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+        self.0.consensus_encode(w)
+    }
+}
+
 impl default::Default for Amount {
     fn default() -> Self { Amount::ZERO }
 }
diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs
index 05e6a2bc..380a8402 100644
--- a/bitcoin/src/bip152.rs
+++ b/bitcoin/src/bip152.rs
@@ -378,7 +378,8 @@ mod test {
     use crate::consensus::encode::{deserialize, serialize};
     use crate::hash_types::TxMerkleNode;
     use crate::{
-        CompactTarget, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
+        Amount, CompactTarget, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid,
+        Witness,
     };
 
     fn dummy_tx(nonce: &[u8]) -> Transaction {
@@ -391,7 +392,7 @@ fn dummy_tx(nonce: &[u8]) -> Transaction {
                 sequence: Sequence(1),
                 witness: Witness::new(),
             }],
-            output: vec![TxOut { value: 1, script_pubkey: ScriptBuf::new() }],
+            output: vec![TxOut { value: Amount::ONE_SAT, script_pubkey: ScriptBuf::new() }],
         }
     }
 
diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs
index f8825ef7..8a368d45 100644
--- a/bitcoin/src/blockdata/constants.rs
+++ b/bitcoin/src/blockdata/constants.rs
@@ -22,9 +22,8 @@
 use crate::internal_macros::impl_bytes_newtype;
 use crate::network::constants::Network;
 use crate::pow::CompactTarget;
+use crate::Amount;
 
-/// How many satoshis are in "one bitcoin".
-pub const COIN_VALUE: u64 = 100_000_000;
 /// How many seconds between blocks we expect on average.
 pub const TARGET_BLOCK_SPACING: u32 = 600;
 /// How many blocks between diffchanges.
@@ -61,11 +60,6 @@
 /// Number of blocks needed for an output from a coinbase transaction to be spendable.
 pub const COINBASE_MATURITY: u32 = 100;
 
-/// The maximum value allowed in an output (useful for sanity checking,
-/// since keeping everything below this value should prevent overflows
-/// if you are doing anything remotely sane with monetary values).
-pub const MAX_MONEY: u64 = 21_000_000 * COIN_VALUE;
-
 /// Constructs and returns the coinbase (and only) transaction of the Bitcoin genesis block.
 fn bitcoin_genesis_tx() -> Transaction {
     // Base
@@ -93,7 +87,7 @@ fn bitcoin_genesis_tx() -> Transaction {
     let script_bytes = hex!("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f");
     let out_script =
         script::Builder::new().push_slice(script_bytes).push_opcode(OP_CHECKSIG).into_script();
-    ret.output.push(TxOut { value: 50 * COIN_VALUE, script_pubkey: out_script });
+    ret.output.push(TxOut { value: Amount::from_sat(50 * 100_000_000), script_pubkey: out_script });
 
     // end
     ret
@@ -198,6 +192,8 @@ pub fn from_genesis_block_hash(block_hash: crate::BlockHash) -> Self {
 
 #[cfg(test)]
 mod test {
+    use core::str::FromStr;
+
     use super::*;
     use crate::blockdata::locktime::absolute;
     use crate::consensus::encode::serialize;
@@ -219,7 +215,7 @@ fn bitcoin_genesis_first_transaction() {
         assert_eq!(gen.output.len(), 1);
         assert_eq!(serialize(&gen.output[0].script_pubkey),
                    hex!("434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"));
-        assert_eq!(gen.output[0].value, 50 * COIN_VALUE);
+        assert_eq!(gen.output[0].value, Amount::from_str("50 BTC").unwrap());
         assert_eq!(gen.lock_time, absolute::LockTime::ZERO);
 
         assert_eq!(
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 1b6dd415..e1a5c94d 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -35,7 +35,7 @@
 #[cfg(doc)]
 use crate::sighash::{EcdsaSighashType, TapSighashType};
 use crate::string::FromHexStr;
-use crate::{io, VarInt};
+use crate::{io, Amount, VarInt};
 
 /// A reference to a transaction output.
 ///
@@ -478,7 +478,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.
 #[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
 pub struct TxOut {
     /// The value of the output, in satoshis.
-    pub value: u64,
+    pub value: Amount,
     /// The script which must be satisfied for the output to be spent.
     pub script_pubkey: ScriptBuf,
 }
@@ -512,7 +512,7 @@ pub fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self {
         let dust_amount = (len as u64) * 3;
 
         TxOut {
-            value: dust_amount + 1, // minimal non-dust amount is one higher than dust amount
+            value: Amount::from_sat(dust_amount + 1), // minimal non-dust amount is one higher than dust amount
             script_pubkey,
         }
     }
@@ -520,7 +520,9 @@ pub fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self {
 
 // This is used as a "null txout" in consensus signing code.
 impl Default for TxOut {
-    fn default() -> TxOut { TxOut { value: 0xffffffffffffffff, script_pubkey: ScriptBuf::new() } }
+    fn default() -> TxOut {
+        TxOut { value: Amount::from_sat(0xffffffffffffffff), script_pubkey: ScriptBuf::new() }
+    }
 }
 
 /// Result of [`Transaction::encode_signing_data_to`].
@@ -962,12 +964,7 @@ pub fn verify_with_flags<S, F>(&self, mut spent: S, flags: F) -> Result<(), scri
         let flags: u32 = flags.into();
         for (idx, input) in self.input.iter().enumerate() {
             if let Some(output) = spent(&input.previous_output) {
-                output.script_pubkey.verify_with_flags(
-                    idx,
-                    crate::Amount::from_sat(output.value),
-                    tx.as_slice(),
-                    flags,
-                )?;
+                output.script_pubkey.verify_with_flags(idx, output.value, tx.as_slice(), flags)?;
             } else {
                 return Err(script::Error::UnknownSpentOutput(input.previous_output));
             }
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 8ffdbf8d..6c8bc908 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -22,7 +22,7 @@
 use crate::error::impl_std_error;
 use crate::prelude::*;
 use crate::taproot::{LeafVersion, TapLeafHash, TAPROOT_ANNEX_PREFIX};
-use crate::{io, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut};
+use crate::{io, Amount, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut};
 
 /// Used for signature hash for invalid use of SIGHASH_SINGLE.
 #[rustfmt::skip]
@@ -751,7 +751,7 @@ pub fn segwit_encode_signing_data_to<Write: io::Write>(
         mut writer: Write,
         input_index: usize,
         script_code: &Script,
-        value: u64,
+        value: Amount,
         sighash_type: EcdsaSighashType,
     ) -> Result<(), Error> {
         let zero_hash = sha256d::Hash::all_zeros();
@@ -810,7 +810,7 @@ pub fn segwit_signature_hash(
         &mut self,
         input_index: usize,
         script_code: &Script,
-        value: u64,
+        value: Amount,
         sighash_type: EcdsaSighashType,
     ) -> Result<SegwitV0Sighash, Error> {
         let mut enc = SegwitV0Sighash::engine();
@@ -1062,7 +1062,7 @@ impl<R: BorrowMut<Transaction>> SighashCache<R> {
     ///
     /// This allows in-line signing such as
     /// ```
-    /// use bitcoin::{absolute, Transaction, Script};
+    /// use bitcoin::{absolute, Amount, Transaction, Script};
     /// use bitcoin::sighash::{EcdsaSighashType, SighashCache};
     ///
     /// let mut tx_to_sign = Transaction { version: 2, lock_time: absolute::LockTime::ZERO, input: Vec::new(), output: Vec::new() };
@@ -1071,7 +1071,7 @@ impl<R: BorrowMut<Transaction>> SighashCache<R> {
     /// let mut sig_hasher = SighashCache::new(&mut tx_to_sign);
     /// for inp in 0..input_count {
     ///     let prevout_script = Script::empty();
-    ///     let _sighash = sig_hasher.segwit_signature_hash(inp, prevout_script, 42, EcdsaSighashType::All);
+    ///     let _sighash = sig_hasher.segwit_signature_hash(inp, prevout_script, Amount::ONE_SAT, EcdsaSighashType::All);
     ///     // ... sign the sighash
     ///     sig_hasher.witness_mut(inp).unwrap().push(&Vec::new());
     /// }
@@ -1468,7 +1468,7 @@ struct UtxoSpent {
             #[serde(rename = "scriptPubKey")]
             script_pubkey: ScriptBuf,
             #[serde(rename = "amountSats")]
-            value: u64,
+            value: Amount,
         }
 
         #[derive(serde::Deserialize)]
@@ -1685,7 +1685,7 @@ fn bip143_p2wpkh() {
 
         let witness_script =
             p2pkh_hex("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357");
-        let value = 600_000_000;
+        let value = Amount::from_sat(600_000_000);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
@@ -1726,7 +1726,7 @@ fn bip143_p2wpkh_nested_in_p2sh() {
 
         let witness_script =
             p2pkh_hex("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873");
-        let value = 1_000_000_000;
+        let value = Amount::from_sat(1_000_000_000);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
@@ -1773,7 +1773,7 @@ fn bip143_p2wsh_nested_in_p2sh() {
              56ae",
         )
         .unwrap();
-        let value = 987654321;
+        let value = Amount::from_sat(987_654_321);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 7aeeabcb..c057f1fa 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -459,11 +459,11 @@ fn output_type(&self, input_index: usize) -> Result<OutputType, SignError> {
     pub fn fee(&self) -> Result<Amount, Error> {
         let mut inputs: u64 = 0;
         for utxo in self.iter_funding_utxos() {
-            inputs = inputs.checked_add(utxo?.value).ok_or(Error::FeeOverflow)?;
+            inputs = inputs.checked_add(utxo?.value.to_sat()).ok_or(Error::FeeOverflow)?;
         }
         let mut outputs: u64 = 0;
         for out in &self.unsigned_tx.output {
-            outputs = outputs.checked_add(out.value).ok_or(Error::FeeOverflow)?;
+            outputs = outputs.checked_add(out.value.to_sat()).ok_or(Error::FeeOverflow)?;
         }
         inputs.checked_sub(outputs).map(Amount::from_sat).ok_or(Error::NegativeFee)
     }
@@ -922,14 +922,14 @@ fn serialize_then_deserialize_global() {
                 }],
                 output: vec![
                     TxOut {
-                        value: 99999699,
+                        value: Amount::from_sat(99_999_699),
                         script_pubkey: ScriptBuf::from_hex(
                             "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
                         )
                         .unwrap(),
                     },
                     TxOut {
-                        value: 100000000,
+                        value: Amount::from_sat(100_000_000),
                         script_pubkey: ScriptBuf::from_hex(
                             "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
                         )
@@ -995,7 +995,7 @@ fn test_serde_psbt() {
                 )]),
             }],
             output: vec![TxOut {
-                value: 190303501938,
+                value: Amount::from_sat(190_303_501_938),
                 script_pubkey: ScriptBuf::from_hex(
                     "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
                 )
@@ -1046,7 +1046,7 @@ fn test_serde_psbt() {
                 Input {
                     non_witness_utxo: Some(tx),
                     witness_utxo: Some(TxOut {
-                        value: 190303501938,
+                        value: Amount::from_sat(190_303_501_938),
                         script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                     }),
                     sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<PsbtSighashType>().unwrap()),
@@ -1190,11 +1190,11 @@ fn valid_vector_1() {
                     ],
                     output: vec![
                         TxOut {
-                            value: 99999699,
+                            value: Amount::from_sat(99_999_699),
                             script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
                         },
                         TxOut {
-                            value: 100000000,
+                            value: Amount::from_sat(100_000_000),
                             script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
                         },
                     ],
@@ -1237,11 +1237,11 @@ fn valid_vector_1() {
                             ],
                             output: vec![
                                 TxOut {
-                                    value: 200000000,
+                                    value: Amount::from_sat(200_000_000),
                                     script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
                                 },
                                 TxOut {
-                                    value: 190303501938,
+                                    value: Amount::from_sat(190_303_501_938),
                                     script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                                 },
                             ],
@@ -1522,11 +1522,11 @@ fn serialize_and_deserialize_preimage_psbt() {
                 ],
                 output: vec![
                     TxOut {
-                        value: 99999699,
+                        value: Amount::from_sat(99_999_699),
                         script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
                     },
                     TxOut {
-                        value: 100000000,
+                        value: Amount::from_sat(100_000_000),
                         script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
                     },
                 ],
@@ -1569,11 +1569,11 @@ fn serialize_and_deserialize_preimage_psbt() {
                         ],
                         output: vec![
                             TxOut {
-                                value: 200000000,
+                                value: Amount::from_sat(200_000_000),
                                 script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
                             },
                             TxOut {
-                                value: 190303501938,
+                                value: Amount::from_sat(190_303_501_938),
                                 script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                             },
                         ],
@@ -1670,9 +1670,9 @@ fn get_key_btree_map() {
 
     #[test]
     fn test_fee() {
-        let output_0_val = 99999699;
-        let output_1_val = 100000000;
-        let prev_output_val = 200000000;
+        let output_0_val = Amount::from_sat(99_999_699);
+        let output_1_val = Amount::from_sat(100_000_000);
+        let prev_output_val = Amount::from_sat(200_000_000);
 
         let mut t = PartiallySignedTransaction {
             unsigned_tx: Transaction {
@@ -1733,7 +1733,7 @@ fn test_fee() {
                                 ..Default::default()
                             },
                             TxOut {
-                                value: 190303501938,
+                                value: Amount::from_sat(190_303_501_938),
                                 ..Default::default()
                             },
                         ],
@@ -1752,7 +1752,7 @@ fn test_fee() {
         };
         assert_eq!(
             t.fee().expect("fee calculation"),
-            Amount::from_sat(prev_output_val - (output_0_val + output_1_val))
+            prev_output_val - (output_0_val + output_1_val)
         );
         // no previous output
         let mut t2 = t.clone();
@@ -1769,8 +1769,8 @@ fn test_fee() {
             e => panic!("unexpected error: {:?}", e),
         }
         // overflow
-        t.unsigned_tx.output[0].value = u64::MAX;
-        t.unsigned_tx.output[1].value = u64::MAX;
+        t.unsigned_tx.output[0].value = Amount::MAX;
+        t.unsigned_tx.output[1].value = Amount::MAX;
         match t.fee().unwrap_err() {
             Error::FeeOverflow => {}
             e => panic!("unexpected error: {:?}", e),
@@ -1801,7 +1801,7 @@ fn sign_psbt() {
 
         // First input we can spend. See comment above on key_map for why we use defaults here.
         let txout_wpkh = TxOut {
-            value: 10,
+            value: Amount::from_sat(10),
             script_pubkey: ScriptBuf::new_v0_p2wpkh(&WPubkeyHash::hash(&pk.to_bytes())),
         };
         psbt.inputs[0].witness_utxo = Some(txout_wpkh);
@@ -1813,8 +1813,10 @@ fn sign_psbt() {
         // Second input is unspendable by us e.g., from another wallet that supports future upgrades.
         let unknown_prog =
             WitnessProgram::new(crate::address::WitnessVersion::V4, vec![0xaa; 34]).unwrap();
-        let txout_unknown_future =
-            TxOut { value: 10, script_pubkey: ScriptBuf::new_witness_program(&unknown_prog) };
+        let txout_unknown_future = TxOut {
+            value: Amount::from_sat(10),
+            script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),
+        };
         psbt.inputs[1].witness_utxo = Some(txout_unknown_future);
 
         let sigs = psbt.sign(&key_map, &secp).unwrap();


Test patch:
diff --git a/bitcoin/tests/psbt.rs b/bitcoin/tests/psbt.rs
index a36b1afb..692cc9b6 100644
--- a/bitcoin/tests/psbt.rs
+++ b/bitcoin/tests/psbt.rs
@@ -188,15 +188,13 @@ struct TvInput {
         output: vec![
             TxOut {
                 value: Amount::from_str_in(output_0.amount, Denomination::Bitcoin)
-                    .expect("failed to parse amount")
-                    .to_sat(),
+                    .expect("failed to parse amount"),
                 script_pubkey: ScriptBuf::from_hex(output_0.script_pubkey)
                     .expect("failed to parse script"),
             },
             TxOut {
                 value: Amount::from_str_in(output_1.amount, Denomination::Bitcoin)
-                    .expect("failed to parse amount")
-                    .to_sat(),
+                    .expect("failed to parse amount"),
                 script_pubkey: ScriptBuf::from_hex(output_1.script_pubkey)
                     .expect("failed to parse script"),
             },
diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs
index 042ac0ba..fc125975 100644
--- a/bitcoin/tests/serde.rs
+++ b/bitcoin/tests/serde.rs
@@ -38,8 +38,8 @@
 use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
 use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapTree, TaprootBuilder};
 use bitcoin::{
-    ecdsa, Address, Block, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Target,
-    Transaction, TxIn, TxOut, Txid, Work,
+    ecdsa, Address, Amount, Block, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence,
+    Target, Transaction, TxIn, TxOut, Txid, Work,
 };
 
 /// Implicitly does regression test for `BlockHeader` also.
@@ -110,8 +110,10 @@ fn serde_regression_txin() {
 
 #[test]
 fn serde_regression_txout() {
-    let txout =
-        TxOut { value: 0xDEADBEEFCAFEBABE, script_pubkey: ScriptBuf::from(vec![0u8, 1u8, 2u8]) };
+    let txout = TxOut {
+        value: Amount::from_sat(0xDEADBEEFCAFEBABE),
+        script_pubkey: ScriptBuf::from(vec![0u8, 1u8, 2u8]),
+    };
     let got = serialize(&txout).unwrap();
     let want = include_bytes!("data/serde/txout_bincode") as &[_];
     assert_eq!(got, want)
@@ -237,7 +239,7 @@ fn serde_regression_psbt() {
             .unwrap()]),
         }],
         output: vec![TxOut {
-            value: 190303501938,
+            value: Amount::from_sat(190_303_501_938),
             script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587")
                 .unwrap(),
         }],
@@ -282,7 +284,7 @@ fn serde_regression_psbt() {
         inputs: vec![Input {
             non_witness_utxo: Some(tx),
             witness_utxo: Some(TxOut {
-                value: 190303501938,
+                value: Amount::from_sat(190_303_501_938),
                 script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
             }),
             sighash_type: Some(PsbtSighashType::from(EcdsaSighashType::from_str("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY").unwrap())),