1use core::fmt;
17
18use bitcoin::{FeeRate, Transaction, Txid};
19
20use crate::error::{write_err, FeeError};
21use crate::v2::{DetermineLockTimeError, Psbt};
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26pub struct Extractor(Psbt);
27
28impl Extractor {
29 pub fn new(psbt: Psbt) -> Result<Self, ExtractError> {
33 if psbt.inputs.iter().any(|input| !input.is_finalized()) {
34 return Err(ExtractError::PsbtNotFinalized);
35 }
36 let _ = psbt.determine_lock_time()?;
37
38 Ok(Self(psbt))
39 }
40
41 pub fn id(&self) -> Txid {
43 self.0.id().expect("Extractor guarantees lock time can be determined")
44 }
45}
46
47impl Extractor {
48 pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_unchecked(25_000);
53
54 pub fn extract_tx(&self) -> Result<Transaction, ExtractTxFeeRateError> {
56 self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE)
57 }
58
59 pub fn extract_tx_fee_rate_limit(&self) -> Result<Transaction, ExtractTxFeeRateError> {
67 self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE)
68 }
69
70 pub fn extract_tx_with_fee_rate_limit(
72 &self,
73 max_fee_rate: FeeRate,
74 ) -> Result<Transaction, ExtractTxFeeRateError> {
75 self.internal_extract_tx_with_fee_rate_limit(max_fee_rate)
76 }
77
78 #[allow(clippy::result_large_err)]
82 pub fn extract_tx_unchecked_fee_rate(&self) -> Result<Transaction, ExtractTxError> {
83 self.internal_extract_tx()
84 }
85
86 #[inline]
87 fn internal_extract_tx_with_fee_rate_limit(
88 &self,
89 max_fee_rate: FeeRate,
90 ) -> Result<Transaction, ExtractTxFeeRateError> {
91 let fee = self.0.fee()?;
92 let tx = self.internal_extract_tx()?;
93
94 let fee_rate =
96 FeeRate::from_sat_per_kwu(fee.to_sat().saturating_mul(1000) / tx.weight().to_wu());
97 if fee_rate > max_fee_rate {
99 return Err(ExtractTxFeeRateError::FeeTooHigh { fee: fee_rate, max: max_fee_rate });
100 }
101
102 Ok(tx)
103 }
104
105 #[inline]
109 #[allow(clippy::result_large_err)]
110 fn internal_extract_tx(&self) -> Result<Transaction, ExtractTxError> {
111 if !self.0.is_finalized() {
112 return Err(ExtractTxError::Unfinalized);
113 }
114
115 let lock_time = self.0.determine_lock_time()?;
116
117 let tx = Transaction {
118 version: self.0.global.tx_version,
119 lock_time,
120 input: self.0.inputs.iter().map(|input| input.signed_tx_in()).collect(),
121 output: self.0.outputs.iter().map(|ouput| ouput.tx_out()).collect(),
122 };
123
124 Ok(tx)
125 }
126}
127
128#[derive(Debug)]
130pub enum ExtractError {
131 PsbtNotFinalized,
133 DetermineLockTime(DetermineLockTimeError),
135}
136
137impl fmt::Display for ExtractError {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 use ExtractError::*;
140
141 match *self {
142 PsbtNotFinalized => write!(f, "attempted to extract tx from an unfinalized PSBT"),
143 DetermineLockTime(ref e) =>
144 write_err!(f, "extractor must be able to determine the lock time"; e),
145 }
146 }
147}
148
149#[cfg(feature = "std")]
150impl std::error::Error for ExtractError {
151 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
152 use ExtractError::*;
153
154 match *self {
155 DetermineLockTime(ref e) => Some(e),
156 PsbtNotFinalized => None,
157 }
158 }
159}
160
161impl From<DetermineLockTimeError> for ExtractError {
162 fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167#[non_exhaustive]
168pub enum ExtractTxFeeRateError {
169 Fee(FeeError),
171 FeeTooHigh {
173 fee: FeeRate,
175 max: FeeRate,
177 },
178 ExtractTx(ExtractTxError),
180}
181
182impl fmt::Display for ExtractTxFeeRateError {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 use ExtractTxFeeRateError::*;
185
186 match *self {
187 Fee(ref e) => write_err!(f, "fee calculation"; e),
188 FeeTooHigh { fee, max } => write!(f, "fee {} is greater than max {}", fee, max),
189 ExtractTx(ref e) => write_err!(f, "extract"; e),
190 }
191 }
192}
193
194#[cfg(feature = "std")]
195impl std::error::Error for ExtractTxFeeRateError {
196 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
197 use ExtractTxFeeRateError::*;
198
199 match *self {
200 Fee(ref e) => Some(e),
201 ExtractTx(ref e) => Some(e),
202 FeeTooHigh { .. } => None,
203 }
204 }
205}
206
207impl From<FeeError> for ExtractTxFeeRateError {
208 fn from(e: FeeError) -> Self { Self::Fee(e) }
209}
210
211impl From<ExtractTxError> for ExtractTxFeeRateError {
212 fn from(e: ExtractTxError) -> Self { Self::ExtractTx(e) }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217#[non_exhaustive]
218pub enum ExtractTxError {
219 Unfinalized,
221 DetermineLockTime(DetermineLockTimeError),
223}
224
225impl fmt::Display for ExtractTxError {
226 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { todo!() }
227}
228
229#[cfg(feature = "std")]
230impl std::error::Error for ExtractTxError {
231 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { todo!() }
232}
233
234impl From<DetermineLockTimeError> for ExtractTxError {
235 fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) }
236}