1
2use std::borrow::Borrow;
3use std::collections::BTreeMap;
4
5use bitcoin::{
6 taproot, Address, AddressType, Amount, Denomination, FeeRate, OutPoint, ScriptBuf, TapNodeHash, Transaction, TxOut, Weight
7};
8use bitcoin::taproot::ControlBlock;
9use bitcoin::secp256k1::{self, Keypair, Secp256k1};
10
11use crate::{fee, P2PKH_DUST, P2SH_DUST, P2TR_DUST, P2WPKH_DUST, P2WSH_DUST};
12
13pub trait KeypairExt: Borrow<Keypair> {
15 fn for_keyspend(
17 &self,
18 secp: &Secp256k1<impl secp256k1::Verification>,
19 tap_merkle_root: Option<TapNodeHash>,
20 ) -> Keypair {
21 let tweak = taproot::TapTweakHash::from_key_and_tweak(
22 self.borrow().x_only_public_key().0,
23 tap_merkle_root,
24 );
25 self.borrow().add_xonly_tweak(secp, &tweak.to_scalar()).expect("hashed values")
26 }
27
28 fn for_keyspend_only(&self, secp: &Secp256k1<impl secp256k1::Verification>) -> Keypair {
30 self.for_keyspend(secp, None)
31 }
32}
33impl KeypairExt for Keypair {}
34
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
38pub enum NonStandardOutput {
39 #[error("output value is below the dust limit for its script type")]
41 Dust,
42 #[error("output uses a non-standard script type")]
45 Script,
46}
47
48pub trait TxOutExt: Borrow<TxOut> {
50 fn is_p2a_fee_anchor(&self) -> bool {
52 self.borrow().script_pubkey == *fee::P2A_SCRIPT
53 }
54
55 fn is_standard(&self) -> bool {
57 self.check_standard().is_ok()
58 }
59
60 fn check_standard(&self) -> Result<(), NonStandardOutput> {
67 let out = self.borrow();
68
69 let dust_limit = if out.script_pubkey.is_p2pkh() {
70 P2PKH_DUST
71 } else if out.script_pubkey.is_p2sh() {
72 P2SH_DUST
73 } else if out.script_pubkey.is_p2wpkh() {
74 P2WPKH_DUST
75 } else if out.script_pubkey.is_p2wsh() {
76 P2WSH_DUST
77 } else if out.script_pubkey.is_p2tr() {
78 P2TR_DUST
79 } else if out.script_pubkey.is_op_return() {
80 return if out.script_pubkey.len() <= 83 {
81 Ok(())
82 } else {
83 Err(NonStandardOutput::Script)
84 };
85 } else {
86 return Err(NonStandardOutput::Script);
87 };
88
89 if out.value >= dust_limit { Ok(()) } else { Err(NonStandardOutput::Dust) }
90 }
91}
92impl TxOutExt for TxOut {}
93
94
95pub trait TransactionExt: Borrow<Transaction> {
97 fn fee_anchor(&self) -> Option<(OutPoint, &TxOut)> {
101 for (i, out) in self.borrow().output.iter().enumerate() {
102 if out.is_p2a_fee_anchor() {
103 let point = OutPoint::new(self.borrow().compute_txid(), i as u32);
104 return Some((point, out));
105 }
106 }
107 None
108 }
109
110 fn output_value(&self) -> Amount {
112 self.borrow().output.iter().map(|o| o.value).sum()
113 }
114
115 #[cfg(feature = "all-related-utxos")]
117 fn all_related_utxos(&self) -> impl Iterator<Item = OutPoint> {
118 let tx = self.borrow();
119 let inputs = tx.input.iter().map(|i| i.previous_output);
120 let txid = tx.compute_txid();
121 let outputs = (0..tx.output.len()).map(move |idx| OutPoint::new(txid, idx as u32));
122 inputs.chain(outputs)
123 }
124}
125impl TransactionExt for Transaction {}
126
127
128pub trait TaprootSpendInfoExt: Borrow<taproot::TaprootSpendInfo> {
130 fn script_pubkey(&self) -> ScriptBuf {
132 ScriptBuf::new_p2tr_tweaked(self.borrow().output_key())
133 }
134
135 fn psbt_tap_scripts(&self) -> BTreeMap<ControlBlock, (ScriptBuf, taproot::LeafVersion)> {
137 let s = self.borrow();
138 s.script_map().keys().map(|pair| {
139 let cb = s.control_block(pair).unwrap();
140 let (script, leaf_version) = pair;
141 (cb, (script.clone(), *leaf_version))
142 }).collect()
143 }
144}
145impl TaprootSpendInfoExt for taproot::TaprootSpendInfo {}
146
147pub trait AmountExt: Borrow<Amount> {
149 fn to_msat(&self) -> u64 {
150 self.borrow().to_sat().saturating_mul(1_000)
151 }
152
153 fn from_msat_ceil(value: u64) -> Amount {
155 Amount::from_sat((value + 999) / 1_000)
156 }
157
158 fn from_msat_floor(value: u64) -> Amount {
160 Amount::from_sat(value / 1_000)
161 }
162}
163impl AmountExt for Amount {}
164
165
166pub trait AddressExt: Borrow<Address> {
168 fn is_uppercasable(&self) -> bool {
173 matches!(
174 self.borrow().address_type(),
175 Some(AddressType::P2wpkh | AddressType::P2wsh | AddressType::P2tr),
176 )
177 }
178}
179impl AddressExt for Address {}
180
181
182pub trait FeeRateExt: Borrow<FeeRate> {
184 fn from_amount_per_kvb_ceil(amount_vkb: Amount) -> FeeRate {
185 FeeRate::from_sat_per_kvb_ceil(amount_vkb.to_sat())
186 }
187
188 fn from_amount_and_weight_ceil(fee: Amount, weight: Weight) -> Option<FeeRate> {
189 if weight == Weight::ZERO {
190 return None;
191 }
192
193 let amount_time_thousand = u64::checked_mul(fee.to_sat(), 1_000)?;
195 let sat_kwu = u64::div_ceil(amount_time_thousand, weight.to_wu());
196 Some(FeeRate::from_sat_per_kwu(sat_kwu))
197 }
198
199 fn from_sat_per_kvb_ceil(sat_kvb: u64) -> FeeRate {
200 FeeRate::from_sat_per_kwu((sat_kvb + 3) / 4)
202 }
203
204 fn from_sat_per_vb_decimal_checked_ceil(sat_vb: f64) -> Option<FeeRate> {
205 let fee = (sat_vb * 250.0).ceil();
206 if fee.is_finite() && fee >= 0.0 && fee <= u64::MAX as f64 {
207 Some(FeeRate::from_sat_per_kwu(fee as u64))
208 } else {
209 None
210 }
211 }
212
213 fn to_btc_per_kvb(&self) -> String {
214 Amount::from_sat(self.to_sat_per_kvb()).to_string_in(Denomination::Bitcoin)
215 }
216
217 fn to_sat_per_kvb(&self) -> u64 {
218 self.borrow().to_sat_per_kwu() * 4
219 }
220}
221impl FeeRateExt for FeeRate {}
222
223
224#[cfg(test)]
225mod test {
226 use super::*;
227
228 fn p2tr_script() -> ScriptBuf {
231 let mut bytes = Vec::with_capacity(34);
232 bytes.push(0x51); bytes.push(0x20); bytes.extend_from_slice(&[0u8; 32]);
235 ScriptBuf::from(bytes)
236 }
237
238 #[test]
239 fn check_standard_p2tr_above_dust() {
240 let out = TxOut { value: P2TR_DUST, script_pubkey: p2tr_script() };
241 assert_eq!(out.check_standard(), Ok(()));
242 assert!(out.is_standard());
243 }
244
245 #[test]
246 fn check_standard_p2tr_sub_dust() {
247 let out = TxOut {
248 value: P2TR_DUST - Amount::from_sat(1),
249 script_pubkey: p2tr_script(),
250 };
251 assert_eq!(out.check_standard(), Err(NonStandardOutput::Dust));
252 assert!(!out.is_standard());
253 }
254
255 #[test]
256 fn check_standard_unrecognised_script() {
257 let out = TxOut {
260 value: Amount::from_sat(100_000),
261 script_pubkey: ScriptBuf::from(vec![0xab, 0xcd, 0xef]),
262 };
263 assert_eq!(out.check_standard(), Err(NonStandardOutput::Script));
264 }
265
266 #[test]
267 fn check_standard_op_return_within_limit() {
268 let mut bytes = vec![0x6a, 0x4c, 80]; bytes.extend_from_slice(&[0u8; 80]);
271 let out = TxOut {
272 value: Amount::ZERO,
273 script_pubkey: ScriptBuf::from(bytes),
274 };
275 assert_eq!(out.check_standard(), Ok(()));
276 }
277
278 #[test]
279 fn check_standard_op_return_over_limit() {
280 let mut bytes = vec![0x6a, 0x4c, 82]; bytes.extend_from_slice(&[0u8; 82]);
284 assert!(bytes.len() > 83);
285 let out = TxOut {
286 value: Amount::ZERO,
287 script_pubkey: ScriptBuf::from(bytes),
288 };
289 assert_eq!(out.check_standard(), Err(NonStandardOutput::Script));
290 }
291
292 #[test]
293 fn amount_from_msat() {
294 assert_eq!(Amount::from_msat_ceil(3000), Amount::from_sat(3));
295 assert_eq!(Amount::from_msat_ceil(3001), Amount::from_sat(4));
296 assert_eq!(Amount::from_msat_ceil(3999), Amount::from_sat(4));
297
298 assert_eq!(Amount::from_msat_floor(3000), Amount::from_sat(3));
299 assert_eq!(Amount::from_msat_floor(3001), Amount::from_sat(3));
300 assert_eq!(Amount::from_msat_floor(3999), Amount::from_sat(3));
301 }
302
303 #[test]
304 fn fee_rate_from_amount_per_kvb() {
305 assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(1_000)),
306 FeeRate::from_sat_per_kwu(250)
307 );
308 assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(7_372)),
309 FeeRate::from_sat_per_kwu(1_843)
310 );
311 assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(238)),
312 FeeRate::from_sat_per_kwu(60) );
314 assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(15_775)),
315 FeeRate::from_sat_per_kwu(3_944) );
317 assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(10_125)),
318 FeeRate::from_sat_per_kwu(2_532) );
320 }
321
322 #[test]
323 fn fee_rate_from_amount_and_weight() {
324 assert_eq!(FeeRate::from_amount_and_weight_ceil(
325 Amount::from_sat(1_000), Weight::from_wu(0),
326 ),
327 None );
329 assert_eq!(FeeRate::from_amount_and_weight_ceil(
330 Amount::from_sat(u64::MAX / 2), Weight::from_wu(1),
331 ),
332 None );
334 assert_eq!(FeeRate::from_amount_and_weight_ceil(
335 Amount::from_sat(0), Weight::from_wu(1_000),
336 ),
337 Some(FeeRate::ZERO)
338 );
339 assert_eq!(FeeRate::from_amount_and_weight_ceil(
340 Amount::from_sat(500), Weight::from_wu(250)
341 ),
342 Some(FeeRate::from_sat_per_kwu(2_000))
343 );
344 assert_eq!(FeeRate::from_amount_and_weight_ceil(
345 Amount::from_sat(100), Weight::from_wu(1000)
346 ),
347 Some(FeeRate::from_sat_per_kwu(100))
348 );
349 assert_eq!(FeeRate::from_amount_and_weight_ceil(
350 Amount::from_sat(10_000), Weight::from_wu(327)
351 ),
352 Some(FeeRate::from_sat_per_kwu(30_582)) );
354 assert_eq!(FeeRate::from_amount_and_weight_ceil(
355 Amount::from_sat(10_000), Weight::from_wu(256)
356 ),
357 Some(FeeRate::from_sat_per_kwu(39_063)) );
359 assert_eq!(FeeRate::from_amount_and_weight_ceil(
360 Amount::from_sat(10_000), Weight::from_wu(2_588)
361 ),
362 Some(FeeRate::from_sat_per_kwu(3_864)) );
364 }
365
366 #[test]
367 fn fee_rate_from_sat_per_kvb() {
368 assert_eq!(FeeRate::from_sat_per_kvb_ceil(1_000),
369 FeeRate::from_sat_per_kwu(250)
370 );
371 assert_eq!(FeeRate::from_sat_per_kvb_ceil(7_372),
372 FeeRate::from_sat_per_kwu(1_843)
373 );
374 assert_eq!(FeeRate::from_sat_per_kvb_ceil(238),
375 FeeRate::from_sat_per_kwu(60) );
377 assert_eq!(FeeRate::from_sat_per_kvb_ceil(15_775),
378 FeeRate::from_sat_per_kwu(3_944) );
380 assert_eq!(FeeRate::from_sat_per_kvb_ceil(10_125),
381 FeeRate::from_sat_per_kwu(2_532) );
383 }
384
385 #[test]
386 fn fee_rate_from_sat_per_vb_decimal_checked() {
387 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(-1.0), None);
388 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(-15_4921.0), None);
389
390 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(1.0),
391 Some(FeeRate::from_sat_per_kwu(250))
392 );
393 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(7.372),
394 Some(FeeRate::from_sat_per_kwu(1_843))
395 );
396 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(0.238),
397 Some(FeeRate::from_sat_per_kwu(60)) );
399 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(15.775),
400 Some(FeeRate::from_sat_per_kwu(3_944)) );
402 assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(10.12452),
403 Some(FeeRate::from_sat_per_kwu(2_532)) );
405 }
406
407 #[test]
408 fn fee_rate_to_btc_per_kvb() {
409 assert_eq!(FeeRate::from_sat_per_kwu(250).to_btc_per_kvb(), "0.00001");
410 assert_eq!(FeeRate::from_sat_per_kwu(1_843).to_btc_per_kvb(), "0.00007372");
411 assert_eq!(FeeRate::from_sat_per_kwu(60).to_btc_per_kvb(), "0.0000024");
412 assert_eq!(FeeRate::from_sat_per_kwu(3_944).to_btc_per_kvb(), "0.00015776");
413 assert_eq!(FeeRate::from_sat_per_kwu(2_532).to_btc_per_kvb(), "0.00010128");
414 }
415
416 #[test]
417 fn fee_rate_to_sat_per_kvb() {
418 assert_eq!(FeeRate::from_sat_per_kwu(250).to_sat_per_kvb(), 1_000);
419 assert_eq!(FeeRate::from_sat_per_kwu(1_843).to_sat_per_kvb(), 7_372);
420 assert_eq!(FeeRate::from_sat_per_kwu(60).to_sat_per_kvb(), 240);
421 assert_eq!(FeeRate::from_sat_per_kwu(3_944).to_sat_per_kvb(), 15_776);
422 assert_eq!(FeeRate::from_sat_per_kwu(2_532).to_sat_per_kvb(), 10_128);
423 }
424}