@@ -6,7 +6,10 @@ use iced::{Command, Subscription};
6
6
use liana:: {
7
7
descriptors:: LianaDescriptor ,
8
8
miniscript:: bitcoin:: {
9
- self , address, psbt:: Psbt , Address , Amount , Denomination , Network , OutPoint ,
9
+ self , address, psbt:: Psbt , secp256k1, Address , Amount , Denomination , Network , OutPoint ,
10
+ } ,
11
+ spend:: {
12
+ create_spend, CandidateCoin , SpendCreationError , SpendOutputAddress , SpendTxFees , TxGetter ,
10
13
} ,
11
14
} ;
12
15
@@ -19,7 +22,7 @@ use crate::{
19
22
app:: { cache:: Cache , error:: Error , message:: Message , state:: psbt, view, wallet:: Wallet } ,
20
23
daemon:: {
21
24
model:: { remaining_sequence, Coin , SpendTx } ,
22
- Daemon , DaemonError ,
25
+ Daemon ,
23
26
} ,
24
27
} ;
25
28
@@ -73,7 +76,9 @@ pub struct DefineSpend {
73
76
is_valid : bool ,
74
77
is_duplicate : bool ,
75
78
79
+ network : Network ,
76
80
descriptor : LianaDescriptor ,
81
+ curve : secp256k1:: Secp256k1 < secp256k1:: VerifyOnly > ,
77
82
timelock : u16 ,
78
83
coins : Vec < ( Coin , bool ) > ,
79
84
coins_labels : HashMap < String , String > ,
@@ -85,7 +90,12 @@ pub struct DefineSpend {
85
90
}
86
91
87
92
impl DefineSpend {
88
- pub fn new ( descriptor : LianaDescriptor , coins : & [ Coin ] , timelock : u16 ) -> Self {
93
+ pub fn new (
94
+ network : Network ,
95
+ descriptor : LianaDescriptor ,
96
+ coins : & [ Coin ] ,
97
+ timelock : u16 ,
98
+ ) -> Self {
89
99
let balance_available = coins
90
100
. iter ( )
91
101
. filter_map ( |coin| {
@@ -99,7 +109,7 @@ impl DefineSpend {
99
109
let coins: Vec < ( Coin , bool ) > = coins
100
110
. iter ( )
101
111
. filter_map ( |c| {
102
- if c. spend_info . is_none ( ) {
112
+ if c. spend_info . is_none ( ) && !c . is_immature {
103
113
Some ( ( c. clone ( ) , false ) )
104
114
} else {
105
115
None
@@ -109,7 +119,9 @@ impl DefineSpend {
109
119
110
120
Self {
111
121
balance_available,
122
+ network,
112
123
descriptor,
124
+ curve : secp256k1:: Secp256k1 :: verification_only ( ) ,
113
125
timelock,
114
126
generated : None ,
115
127
coins,
@@ -175,110 +187,122 @@ impl DefineSpend {
175
187
}
176
188
}
177
189
}
178
- fn auto_select_coins ( & mut self , daemon : Arc < dyn Daemon + Sync + Send > ) {
179
- // Set non-input values in the same way as for user selection.
180
- let mut outputs: HashMap < Address < address:: NetworkUnchecked > , u64 > = HashMap :: new ( ) ;
181
- for recipient in & self . recipients {
182
- outputs. insert (
183
- Address :: from_str ( & recipient. address . value ) . expect ( "Checked before" ) ,
184
- recipient. amount ( ) . expect ( "Checked before" ) ,
185
- ) ;
190
+ /// redraft calcul amount left to select and auto select coin is the user did not select a coin
191
+ /// manually
192
+ fn redraft ( & mut self , daemon : Arc < dyn Daemon + Sync + Send > ) {
193
+ if !self . form_values_are_valid ( ) || self . recipients . is_empty ( ) {
194
+ return ;
186
195
}
187
- let feerate_vb = self . feerate . value . parse :: < u64 > ( ) . unwrap_or ( 0 ) ;
196
+
197
+ let destinations: Vec < ( SpendOutputAddress , Amount ) > = self
198
+ . recipients
199
+ . iter ( )
200
+ . map ( |recipient| {
201
+ (
202
+ SpendOutputAddress {
203
+ addr : Address :: from_str ( & recipient. address . value )
204
+ . expect ( "Checked before" )
205
+ . assume_checked ( ) ,
206
+ info : None ,
207
+ } ,
208
+ Amount :: from_sat ( recipient. amount ( ) . expect ( "Checked before" ) ) ,
209
+ )
210
+ } )
211
+ . collect ( ) ;
212
+
213
+ let coins: Vec < CandidateCoin > = if self . is_user_coin_selection {
214
+ self . coins
215
+ . iter ( )
216
+ . filter_map ( |( c, selected) | {
217
+ if * selected {
218
+ Some ( CandidateCoin {
219
+ amount : c. amount ,
220
+ outpoint : c. outpoint ,
221
+ deriv_index : c. derivation_index ,
222
+ is_change : c. is_change ,
223
+ sequence : None ,
224
+ must_select : * selected,
225
+ } )
226
+ } else {
227
+ None
228
+ }
229
+ } )
230
+ . collect ( )
231
+ } else {
232
+ self . coins
233
+ . iter ( )
234
+ . map ( |( c, _) | CandidateCoin {
235
+ amount : c. amount ,
236
+ outpoint : c. outpoint ,
237
+ deriv_index : c. derivation_index ,
238
+ is_change : c. is_change ,
239
+ sequence : None ,
240
+ must_select : false ,
241
+ } )
242
+ . collect ( )
243
+ } ;
244
+
245
+ let dummy_address = self
246
+ . descriptor
247
+ . change_descriptor ( )
248
+ . derive ( 0 . into ( ) , & self . curve )
249
+ . address ( self . network ) ;
250
+
251
+ let feerate_vb = self . feerate . value . parse :: < u64 > ( ) . expect ( "Checked before" ) ;
188
252
// Create a spend with empty inputs in order to use auto-selection.
189
- match daemon. create_spend_tx ( & [ ] , & outputs, feerate_vb) {
253
+ match create_spend (
254
+ & self . descriptor ,
255
+ & self . curve ,
256
+ & mut DaemonTxGetter ( & daemon) ,
257
+ & destinations,
258
+ & coins,
259
+ SpendTxFees :: Regular ( feerate_vb) ,
260
+ // we enter a dummy address to calculate
261
+ SpendOutputAddress {
262
+ addr : dummy_address,
263
+ info : None ,
264
+ } ,
265
+ ) {
190
266
Ok ( spend) => {
191
267
self . warning = None ;
192
- let selected_coins: Vec < OutPoint > = spend
193
- . psbt
194
- . unsigned_tx
195
- . input
196
- . iter ( )
197
- . map ( |c| c. previous_output )
198
- . collect ( ) ;
199
- // Mark coins as selected.
200
- for ( coin, selected) in & mut self . coins {
201
- * selected = selected_coins. contains ( & coin. outpoint ) ;
268
+ if !self . is_user_coin_selection {
269
+ let selected_coins: Vec < OutPoint > = spend
270
+ . psbt
271
+ . unsigned_tx
272
+ . input
273
+ . iter ( )
274
+ . map ( |c| c. previous_output )
275
+ . collect ( ) ;
276
+ // Mark coins as selected.
277
+ for ( coin, selected) in & mut self . coins {
278
+ * selected = selected_coins. contains ( & coin. outpoint ) ;
279
+ }
202
280
}
203
281
// As coin selection was successful, we can assume there is nothing left to select.
204
282
self . amount_left_to_select = Some ( Amount :: from_sat ( 0 ) ) ;
205
283
}
284
+ // For coin selection error (insufficient funds), do not make any changes to
285
+ // selected coins on screen and just show user how much is left to select.
286
+ // User can then either:
287
+ // - modify recipient amounts and/or feerate and let coin selection run again, or
288
+ // - select coins manually.
289
+ Err ( SpendCreationError :: CoinSelection ( amount) ) => {
290
+ self . amount_left_to_select = Some ( Amount :: from_sat ( amount. missing ) ) ;
291
+ }
206
292
Err ( e) => {
207
- if let DaemonError :: CoinSelectionError = e {
208
- // For coin selection error (insufficient funds), do not make any changes to
209
- // selected coins on screen and just show user how much is left to select.
210
- // User can then either:
211
- // - modify recipient amounts and/or feerate and let coin selection run again, or
212
- // - select coins manually.
213
- self . amount_left_to_select ( ) ;
214
- } else {
215
- self . warning = Some ( e. into ( ) ) ;
216
- }
293
+ self . warning = Some ( e. into ( ) ) ;
217
294
}
218
295
}
219
296
}
220
- fn amount_left_to_select ( & mut self ) {
221
- // We need the feerate in order to compute the required amount of BTC to
222
- // select. Return early if we don't to not do unnecessary computation.
223
- let feerate = match self . feerate . value . parse :: < u64 > ( ) {
224
- Ok ( f) => f,
225
- Err ( _) => {
226
- self . amount_left_to_select = None ;
227
- return ;
228
- }
229
- } ;
230
-
231
- // The coins to be included in this transaction.
232
- let selected_coins: Vec < _ > = self
233
- . coins
234
- . iter ( )
235
- . filter_map ( |( c, selected) | if * selected { Some ( c) } else { None } )
236
- . collect ( ) ;
297
+ }
237
298
238
- // A dummy representation of the transaction that will be computed, for
239
- // the purpose of computing its size in order to anticipate the fees needed.
240
- // NOTE: we make the conservative estimation a change output will always be
241
- // needed.
242
- let tx_template = bitcoin:: Transaction {
243
- version : 2 ,
244
- lock_time : bitcoin:: blockdata:: locktime:: absolute:: LockTime :: ZERO ,
245
- input : selected_coins
246
- . iter ( )
247
- . map ( |_| bitcoin:: TxIn :: default ( ) )
248
- . collect ( ) ,
249
- output : self
250
- . recipients
251
- . iter ( )
252
- . filter_map ( |recipient| {
253
- if recipient. valid ( ) {
254
- Some ( bitcoin:: TxOut {
255
- script_pubkey : Address :: from_str ( & recipient. address . value )
256
- . unwrap ( )
257
- . payload
258
- . script_pubkey ( ) ,
259
- value : recipient. amount ( ) . unwrap ( ) ,
260
- } )
261
- } else {
262
- None
263
- }
264
- } )
265
- . collect ( ) ,
266
- } ;
267
- // nValue size + scriptPubKey CompactSize + OP_0 + PUSH32 + <wit program>
268
- const CHANGE_TXO_SIZE : usize = 8 + 1 + 1 + 1 + 32 ;
269
- let satisfaction_vsize = self . descriptor . max_sat_weight ( ) / 4 ;
270
- let transaction_size =
271
- tx_template. vsize ( ) + satisfaction_vsize * tx_template. input . len ( ) + CHANGE_TXO_SIZE ;
272
-
273
- // Now the calculation of the amount left to be selected by the user is a simple
274
- // substraction between the value needed by the transaction to be created and the
275
- // value that was selected already.
276
- let selected_amount = selected_coins. iter ( ) . map ( |c| c. amount . to_sat ( ) ) . sum ( ) ;
277
- let output_sum: u64 = tx_template. output . iter ( ) . map ( |o| o. value ) . sum ( ) ;
278
- let needed_amount: u64 = transaction_size as u64 * feerate + output_sum;
279
- self . amount_left_to_select = Some ( Amount :: from_sat (
280
- needed_amount. saturating_sub ( selected_amount) ,
281
- ) ) ;
299
+ pub struct DaemonTxGetter < ' a > ( & ' a Arc < dyn Daemon + Sync + Send > ) ;
300
+ impl < ' a > TxGetter for DaemonTxGetter < ' a > {
301
+ fn get_tx ( & mut self , txid : & bitcoin:: Txid ) -> Option < bitcoin:: Transaction > {
302
+ self . 0
303
+ . list_txs ( & [ * txid] )
304
+ . ok ( )
305
+ . and_then ( |mut txs| txs. transactions . pop ( ) . map ( |tx| tx. tx ) )
282
306
}
283
307
}
284
308
@@ -317,14 +341,11 @@ impl Step for DefineSpend {
317
341
if let Ok ( value) = s. parse :: < u64 > ( ) {
318
342
self . feerate . value = s;
319
343
self . feerate . valid = value != 0 ;
320
- self . amount_left_to_select ( ) ;
321
344
} else if s. is_empty ( ) {
322
345
self . feerate . value = "" . to_string ( ) ;
323
346
self . feerate . valid = true ;
324
- self . amount_left_to_select = None ;
325
347
} else {
326
348
self . feerate . valid = false ;
327
- self . amount_left_to_select = None ;
328
349
}
329
350
self . warning = None ;
330
351
}
@@ -368,7 +389,6 @@ impl Step for DefineSpend {
368
389
coin. 1 = !coin. 1 ;
369
390
// Once user edits selection, auto-selection can no longer be used.
370
391
self . is_user_coin_selection = true ;
371
- self . amount_left_to_select ( ) ;
372
392
}
373
393
}
374
394
_ => { }
@@ -378,12 +398,7 @@ impl Step for DefineSpend {
378
398
// - all form values have been added and validated
379
399
// - not a self-send
380
400
// - user has not yet selected coins manually
381
- if self . form_values_are_valid ( )
382
- && !self . recipients . is_empty ( )
383
- && !self . is_user_coin_selection
384
- {
385
- self . auto_select_coins ( daemon) ;
386
- }
401
+ self . redraft ( daemon) ;
387
402
self . check_valid ( ) ;
388
403
}
389
404
Message :: Psbt ( res) => match res {
0 commit comments