9
9
// You may not use this file except in accordance with one or both of these
10
10
// licenses.
11
11
12
+ use crate :: FeeRate ;
12
13
use bitcoin:: util:: psbt:: PartiallySignedTransaction as Psbt ;
13
14
use bitcoin:: TxOut ;
14
15
15
16
pub trait PsbtUtils {
16
17
fn get_utxo_for ( & self , input_index : usize ) -> Option < TxOut > ;
18
+
19
+ /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
20
+ /// If the PSBT is missing a TxOut for an input returns None.
21
+ fn fee_amount ( & self ) -> Option < u64 > ;
22
+
23
+ /// The transaction's fee rate. This value will only be accurate if calculated AFTER the
24
+ /// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
25
+ /// transaction.
26
+ /// If the PSBT is missing a TxOut for an input returns None.
27
+ fn fee_rate ( & self ) -> Option < FeeRate > ;
17
28
}
18
29
19
30
impl PsbtUtils for Psbt {
@@ -37,15 +48,39 @@ impl PsbtUtils for Psbt {
37
48
None
38
49
}
39
50
}
51
+
52
+ fn fee_amount ( & self ) -> Option < u64 > {
53
+ let tx = & self . unsigned_tx ;
54
+ let utxos: Option < Vec < TxOut > > = ( 0 ..tx. input . len ( ) ) . map ( |i| self . get_utxo_for ( i) ) . collect ( ) ;
55
+
56
+ utxos. map ( |inputs| {
57
+ let input_amount = inputs. iter ( ) . fold ( 0 , |acc, i| acc + i. value ) ;
58
+
59
+ let output_amount = & self
60
+ . unsigned_tx
61
+ . output
62
+ . iter ( )
63
+ . fold ( 0 , |acc, o| acc + o. value ) ;
64
+
65
+ input_amount - output_amount
66
+ } )
67
+ }
68
+
69
+ fn fee_rate ( & self ) -> Option < FeeRate > {
70
+ let fee_amount = self . fee_amount ( ) ;
71
+ let weight = self . clone ( ) . extract_tx ( ) . weight ( ) ;
72
+ fee_amount. map ( |fee| FeeRate :: from_wu ( fee, weight) )
73
+ }
40
74
}
41
75
42
76
#[ cfg( test) ]
43
77
mod test {
44
78
use crate :: bitcoin:: TxIn ;
45
79
use crate :: psbt:: Psbt ;
46
80
use crate :: wallet:: AddressIndex ;
81
+ use crate :: wallet:: AddressIndex :: New ;
47
82
use crate :: wallet:: { get_funded_wallet, test:: get_test_wpkh} ;
48
- use crate :: SignOptions ;
83
+ use crate :: { psbt , FeeRate , SignOptions } ;
49
84
use std:: str:: FromStr ;
50
85
51
86
// from bip 174
@@ -118,4 +153,83 @@ mod test {
118
153
119
154
let _ = wallet. sign ( & mut psbt, SignOptions :: default ( ) ) . unwrap ( ) ;
120
155
}
156
+
157
+ #[ test]
158
+ fn test_psbt_fee_rate_with_witness_utxo ( ) {
159
+ use psbt:: PsbtUtils ;
160
+
161
+ let expected_fee_rate = 1.2345 ;
162
+
163
+ let ( wallet, _, _) = get_funded_wallet ( "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)" ) ;
164
+ let addr = wallet. get_address ( New ) . unwrap ( ) ;
165
+ let mut builder = wallet. build_tx ( ) ;
166
+ builder. drain_to ( addr. script_pubkey ( ) ) . drain_wallet ( ) ;
167
+ builder. fee_rate ( FeeRate :: from_sat_per_vb ( expected_fee_rate) ) ;
168
+ let ( mut psbt, _) = builder. finish ( ) . unwrap ( ) ;
169
+ let fee_amount = psbt. fee_amount ( ) ;
170
+ assert ! ( fee_amount. is_some( ) ) ;
171
+
172
+ let unfinalized_fee_rate = psbt. fee_rate ( ) . unwrap ( ) ;
173
+
174
+ let finalized = wallet. sign ( & mut psbt, Default :: default ( ) ) . unwrap ( ) ;
175
+ assert ! ( finalized) ;
176
+
177
+ let finalized_fee_rate = psbt. fee_rate ( ) . unwrap ( ) ;
178
+ assert ! ( finalized_fee_rate. as_sat_per_vb( ) >= expected_fee_rate) ;
179
+ assert ! ( finalized_fee_rate. as_sat_per_vb( ) < unfinalized_fee_rate. as_sat_per_vb( ) ) ;
180
+ }
181
+
182
+ #[ test]
183
+ fn test_psbt_fee_rate_with_nonwitness_utxo ( ) {
184
+ use psbt:: PsbtUtils ;
185
+
186
+ let expected_fee_rate = 1.2345 ;
187
+
188
+ let ( wallet, _, _) = get_funded_wallet ( "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)" ) ;
189
+ let addr = wallet. get_address ( New ) . unwrap ( ) ;
190
+ let mut builder = wallet. build_tx ( ) ;
191
+ builder. drain_to ( addr. script_pubkey ( ) ) . drain_wallet ( ) ;
192
+ builder. fee_rate ( FeeRate :: from_sat_per_vb ( expected_fee_rate) ) ;
193
+ let ( mut psbt, _) = builder. finish ( ) . unwrap ( ) ;
194
+ let fee_amount = psbt. fee_amount ( ) ;
195
+ assert ! ( fee_amount. is_some( ) ) ;
196
+ let unfinalized_fee_rate = psbt. fee_rate ( ) . unwrap ( ) ;
197
+
198
+ let finalized = wallet. sign ( & mut psbt, Default :: default ( ) ) . unwrap ( ) ;
199
+ assert ! ( finalized) ;
200
+
201
+ let finalized_fee_rate = psbt. fee_rate ( ) . unwrap ( ) ;
202
+ assert ! ( finalized_fee_rate. as_sat_per_vb( ) >= expected_fee_rate) ;
203
+ assert ! ( finalized_fee_rate. as_sat_per_vb( ) < unfinalized_fee_rate. as_sat_per_vb( ) ) ;
204
+ }
205
+
206
+ #[ test]
207
+ fn test_psbt_fee_rate_with_missing_txout ( ) {
208
+ use psbt:: PsbtUtils ;
209
+
210
+ let expected_fee_rate = 1.2345 ;
211
+
212
+ let ( wpkh_wallet, _, _) = get_funded_wallet ( "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)" ) ;
213
+ let addr = wpkh_wallet. get_address ( New ) . unwrap ( ) ;
214
+ let mut builder = wpkh_wallet. build_tx ( ) ;
215
+ builder. drain_to ( addr. script_pubkey ( ) ) . drain_wallet ( ) ;
216
+ builder. fee_rate ( FeeRate :: from_sat_per_vb ( expected_fee_rate) ) ;
217
+ let ( mut wpkh_psbt, _) = builder. finish ( ) . unwrap ( ) ;
218
+
219
+ wpkh_psbt. inputs [ 0 ] . witness_utxo = None ;
220
+ wpkh_psbt. inputs [ 0 ] . non_witness_utxo = None ;
221
+ assert ! ( wpkh_psbt. fee_amount( ) . is_none( ) ) ;
222
+ assert ! ( wpkh_psbt. fee_rate( ) . is_none( ) ) ;
223
+
224
+ let ( pkh_wallet, _, _) = get_funded_wallet ( "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)" ) ;
225
+ let addr = pkh_wallet. get_address ( New ) . unwrap ( ) ;
226
+ let mut builder = pkh_wallet. build_tx ( ) ;
227
+ builder. drain_to ( addr. script_pubkey ( ) ) . drain_wallet ( ) ;
228
+ builder. fee_rate ( FeeRate :: from_sat_per_vb ( expected_fee_rate) ) ;
229
+ let ( mut pkh_psbt, _) = builder. finish ( ) . unwrap ( ) ;
230
+
231
+ pkh_psbt. inputs [ 0 ] . non_witness_utxo = None ;
232
+ assert ! ( pkh_psbt. fee_amount( ) . is_none( ) ) ;
233
+ assert ! ( pkh_psbt. fee_rate( ) . is_none( ) ) ;
234
+ }
121
235
}
0 commit comments