Skip to main content

bsv_transaction/
output.rs

1//! Transaction output with satoshi value and locking script.
2//!
3//! Defines the spending conditions for the output's value.  Provides
4//! binary serialization/deserialization following the Bitcoin wire format.
5
6use bsv_primitives::util::{BsvReader, BsvWriter, VarInt};
7use bsv_script::Script;
8
9use crate::TransactionError;
10
11/// Maximum satoshi value: 21 million BSV = 2.1 × 10^15 satoshis.
12const MAX_SATOSHIS: u64 = 21_000_000 * 100_000_000;
13
14/// A single output in a BSV transaction.
15///
16/// Each output specifies a satoshi `value` and a `locking_script`
17/// (scriptPubKey) that defines the conditions under which the funds
18/// may be spent.  The `change` flag is a local-only annotation used
19/// during fee calculation to identify outputs that should receive any
20/// leftover satoshis; it is not serialized.
21///
22/// # Wire format
23///
24/// | Field            | Size           |
25/// |------------------|----------------|
26/// | satoshis         | 8 bytes (LE)   |
27/// | script length    | VarInt         |
28/// | locking_script   | variable       |
29#[derive(Clone, Debug)]
30pub struct TransactionOutput {
31    /// The number of satoshis (1 satoshi = 10^-8 BSV) locked by this output.
32    pub satoshis: u64,
33
34    /// The locking script (scriptPubKey) that defines spending conditions.
35    pub locking_script: Script,
36
37    /// Local-only flag marking this output as a change output.
38    /// Used by fee calculation; not serialized on the wire.
39    pub change: bool,
40}
41
42impl TransactionOutput {
43    /// Create a new `TransactionOutput` with zero satoshis and an empty script.
44    ///
45    /// # Returns
46    /// A default `TransactionOutput`.
47    pub fn new() -> Self {
48        TransactionOutput {
49            satoshis: 0,
50            locking_script: Script::new(),
51            change: false,
52        }
53    }
54
55    /// Deserialize a `TransactionOutput` from a `BsvReader`.
56    ///
57    /// Reads 8-byte LE satoshis, a varint script length, and the script bytes.
58    ///
59    /// # Arguments
60    /// * `reader` - The reader positioned at the start of an encoded output.
61    ///
62    /// # Returns
63    /// `Ok(TransactionOutput)` on success, or a `TransactionError` if the
64    /// data is truncated or malformed.
65    pub fn read_from(reader: &mut BsvReader) -> Result<Self, TransactionError> {
66        let satoshis = reader.read_u64_le().map_err(|e| {
67            TransactionError::SerializationError(format!("reading satoshis: {}", e))
68        })?;
69
70        let script_len = reader.read_varint().map_err(|e| {
71            TransactionError::SerializationError(format!("reading script length: {}", e))
72        })?;
73
74        let script_bytes = reader
75            .read_bytes(script_len.value() as usize)
76            .map_err(|e| {
77                TransactionError::SerializationError(format!("reading locking script: {}", e))
78            })?;
79
80        if satoshis > MAX_SATOSHIS {
81            return Err(TransactionError::SerializationError(format!(
82                "satoshi value {} exceeds maximum supply ({})",
83                satoshis, MAX_SATOSHIS
84            )));
85        }
86
87        Ok(TransactionOutput {
88            satoshis,
89            locking_script: Script::from_bytes(script_bytes),
90            change: false,
91        })
92    }
93
94    /// Serialize this `TransactionOutput` into a `BsvWriter`.
95    ///
96    /// Writes 8-byte LE satoshis, a varint script length, and the script.
97    ///
98    /// # Arguments
99    /// * `writer` - The writer to append serialized bytes to.
100    pub fn write_to(&self, writer: &mut BsvWriter) {
101        writer.write_u64_le(self.satoshis);
102        let script_bytes = self.locking_script.to_bytes();
103        writer.write_varint(VarInt::from(script_bytes.len()));
104        writer.write_bytes(script_bytes);
105    }
106
107    /// Serialize this output to a byte vector.
108    ///
109    /// # Returns
110    /// A `Vec<u8>` containing the wire-format bytes.
111    pub fn to_bytes(&self) -> Vec<u8> {
112        let mut writer = BsvWriter::new();
113        self.write_to(&mut writer);
114        writer.into_bytes()
115    }
116
117    /// Serialize this output for use in signature hash computation.
118    ///
119    /// The format is identical to `to_bytes`: satoshis(8) + varint(script_len) + script.
120    ///
121    /// # Returns
122    /// A `Vec<u8>` containing the serialized output suitable for sighash.
123    pub fn bytes_for_sig_hash(&self) -> Vec<u8> {
124        self.to_bytes()
125    }
126
127    /// Return the locking script as a hex-encoded string.
128    ///
129    /// # Returns
130    /// A lowercase hex string of the locking script bytes.
131    pub fn locking_script_hex(&self) -> String {
132        self.locking_script.to_hex()
133    }
134}
135
136impl Default for TransactionOutput {
137    fn default() -> Self {
138        Self::new()
139    }
140}