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}