24
24
25
25
26
26
namespace wallet {
27
- static void ParseRecipients (const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient >& recipients )
27
+ std::vector<CRecipient> CreateRecipients (const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set< int >& subtract_fee_outputs )
28
28
{
29
- std::set<CTxDestination> destinations;
30
- int i = 0 ;
31
- for (const std::string& address: address_amounts.getKeys ()) {
32
- CTxDestination dest = DecodeDestination (address);
33
- if (!IsValidDestination (dest)) {
34
- throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, std::string (" Invalid Bitcoin address: " ) + address);
35
- }
36
-
37
- if (destinations.count (dest)) {
38
- throw JSONRPCError (RPC_INVALID_PARAMETER, std::string (" Invalid parameter, duplicated address: " ) + address);
39
- }
40
- destinations.insert (dest);
41
-
42
- CAmount amount = AmountFromValue (address_amounts[i++]);
43
-
44
- bool subtract_fee = false ;
45
- for (unsigned int idx = 0 ; idx < subtract_fee_outputs.size (); idx++) {
46
- const UniValue& addr = subtract_fee_outputs[idx];
47
- if (addr.get_str () == address) {
48
- subtract_fee = true ;
49
- }
50
- }
51
-
52
- CRecipient recipient = {dest, amount, subtract_fee};
29
+ std::vector<CRecipient> recipients;
30
+ for (size_t i = 0 ; i < outputs.size (); ++i) {
31
+ const auto & [destination, amount] = outputs.at (i);
32
+ CRecipient recipient{destination, amount, subtract_fee_outputs.contains (i)};
53
33
recipients.push_back (recipient);
54
34
}
35
+ return recipients;
55
36
}
56
37
57
38
static void InterpretFeeEstimationInstructions (const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, UniValue& options)
@@ -76,6 +57,37 @@ static void InterpretFeeEstimationInstructions(const UniValue& conf_target, cons
76
57
}
77
58
}
78
59
60
+ std::set<int > InterpretSubtractFeeFromOutputInstructions (const UniValue& sffo_instructions, const std::vector<std::string>& destinations)
61
+ {
62
+ std::set<int > sffo_set;
63
+ if (sffo_instructions.isNull ()) return sffo_set;
64
+ if (sffo_instructions.isBool ()) {
65
+ if (sffo_instructions.get_bool ()) sffo_set.insert (0 );
66
+ return sffo_set;
67
+ }
68
+ for (const auto & sffo : sffo_instructions.getValues ()) {
69
+ if (sffo.isStr ()) {
70
+ for (size_t i = 0 ; i < destinations.size (); ++i) {
71
+ if (sffo.get_str () == destinations.at (i)) {
72
+ sffo_set.insert (i);
73
+ break ;
74
+ }
75
+ }
76
+ }
77
+ if (sffo.isNum ()) {
78
+ int pos = sffo.getInt <int >();
79
+ if (sffo_set.contains (pos))
80
+ throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, duplicated position: %d" , pos));
81
+ if (pos < 0 )
82
+ throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, negative position: %d" , pos));
83
+ if (pos >= int (destinations.size ()))
84
+ throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, position too large: %d" , pos));
85
+ sffo_set.insert (pos);
86
+ }
87
+ }
88
+ return sffo_set;
89
+ }
90
+
79
91
static UniValue FinishTransaction (const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx)
80
92
{
81
93
// Make a blank psbt
@@ -275,11 +287,6 @@ RPCHelpMan sendtoaddress()
275
287
if (!request.params [3 ].isNull () && !request.params [3 ].get_str ().empty ())
276
288
mapValue[" to" ] = request.params [3 ].get_str ();
277
289
278
- bool fSubtractFeeFromAmount = false ;
279
- if (!request.params [4 ].isNull ()) {
280
- fSubtractFeeFromAmount = request.params [4 ].get_bool ();
281
- }
282
-
283
290
CCoinControl coin_control;
284
291
if (!request.params [5 ].isNull ()) {
285
292
coin_control.m_signal_bip125_rbf = request.params [5 ].get_bool ();
@@ -296,13 +303,10 @@ RPCHelpMan sendtoaddress()
296
303
UniValue address_amounts (UniValue::VOBJ);
297
304
const std::string address = request.params [0 ].get_str ();
298
305
address_amounts.pushKV (address, request.params [1 ]);
299
- UniValue subtractFeeFromAmount (UniValue::VARR);
300
- if (fSubtractFeeFromAmount ) {
301
- subtractFeeFromAmount.push_back (address);
302
- }
303
-
304
- std::vector<CRecipient> recipients;
305
- ParseRecipients (address_amounts, subtractFeeFromAmount, recipients);
306
+ std::vector<CRecipient> recipients = CreateRecipients (
307
+ ParseOutputs (address_amounts),
308
+ InterpretSubtractFeeFromOutputInstructions (request.params [4 ], address_amounts.getKeys ())
309
+ );
306
310
const bool verbose{request.params [10 ].isNull () ? false : request.params [10 ].get_bool ()};
307
311
308
312
return SendMoney (*pwallet, coin_control, recipients, mapValue, verbose);
@@ -386,19 +390,17 @@ RPCHelpMan sendmany()
386
390
if (!request.params [3 ].isNull () && !request.params [3 ].get_str ().empty ())
387
391
mapValue[" comment" ] = request.params [3 ].get_str ();
388
392
389
- UniValue subtractFeeFromAmount (UniValue::VARR);
390
- if (!request.params [4 ].isNull ())
391
- subtractFeeFromAmount = request.params [4 ].get_array ();
392
-
393
393
CCoinControl coin_control;
394
394
if (!request.params [5 ].isNull ()) {
395
395
coin_control.m_signal_bip125_rbf = request.params [5 ].get_bool ();
396
396
}
397
397
398
398
SetFeeEstimateMode (*pwallet, coin_control, /* conf_target=*/ request.params [6 ], /* estimate_mode=*/ request.params [7 ], /* fee_rate=*/ request.params [8 ], /* override_min_fee=*/ false );
399
399
400
- std::vector<CRecipient> recipients;
401
- ParseRecipients (sendTo, subtractFeeFromAmount, recipients);
400
+ std::vector<CRecipient> recipients = CreateRecipients (
401
+ ParseOutputs (sendTo),
402
+ InterpretSubtractFeeFromOutputInstructions (request.params [4 ], sendTo.getKeys ())
403
+ );
402
404
const bool verbose{request.params [9 ].isNull () ? false : request.params [9 ].get_bool ()};
403
405
404
406
return SendMoney (*pwallet, coin_control, recipients, std::move (mapValue), verbose);
@@ -488,17 +490,17 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true)
488
490
return args;
489
491
}
490
492
491
- CreatedTransactionResult FundTransaction (CWallet& wallet, const CMutableTransaction& tx, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
493
+ CreatedTransactionResult FundTransaction (CWallet& wallet, const CMutableTransaction& tx, const std::vector<CRecipient>& recipients, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
492
494
{
495
+ // We want to make sure tx.vout is not used now that we are passing outputs as a vector of recipients.
496
+ // This sets us up to remove tx completely in a future PR in favor of passing the inputs directly.
497
+ CHECK_NONFATAL (tx.vout .empty ());
493
498
// Make sure the results are valid at least up to the most recent block
494
499
// the user could have gotten from another RPC command prior to now
495
500
wallet.BlockUntilSyncedToCurrentChain ();
496
501
497
502
std::optional<unsigned int > change_position;
498
503
bool lockUnspents = false ;
499
- UniValue subtractFeeFromOutputs;
500
- std::set<int > setSubtractFeeFromOutputs;
501
-
502
504
if (!options.isNull ()) {
503
505
if (options.type () == UniValue::VBOOL) {
504
506
// backward compatibility bool only fallback
@@ -553,7 +555,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
553
555
554
556
if (options.exists (" changePosition" ) || options.exists (" change_position" )) {
555
557
int pos = (options.exists (" change_position" ) ? options[" change_position" ] : options[" changePosition" ]).getInt <int >();
556
- if (pos < 0 || (unsigned int )pos > tx. vout .size ()) {
558
+ if (pos < 0 || (unsigned int )pos > recipients .size ()) {
557
559
throw JSONRPCError (RPC_INVALID_PARAMETER, " changePosition out of bounds" );
558
560
}
559
561
change_position = (unsigned int )pos;
@@ -595,9 +597,6 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
595
597
coinControl.fOverrideFeeRate = true ;
596
598
}
597
599
598
- if (options.exists (" subtractFeeFromOutputs" ) || options.exists (" subtract_fee_from_outputs" ) )
599
- subtractFeeFromOutputs = (options.exists (" subtract_fee_from_outputs" ) ? options[" subtract_fee_from_outputs" ] : options[" subtractFeeFromOutputs" ]).get_array ();
600
-
601
600
if (options.exists (" replaceable" )) {
602
601
coinControl.m_signal_bip125_rbf = options[" replaceable" ].get_bool ();
603
602
}
@@ -703,21 +702,10 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
703
702
}
704
703
}
705
704
706
- if (tx. vout . size () == 0 )
705
+ if (recipients. empty () )
707
706
throw JSONRPCError (RPC_INVALID_PARAMETER, " TX must have at least one output" );
708
707
709
- for (unsigned int idx = 0 ; idx < subtractFeeFromOutputs.size (); idx++) {
710
- int pos = subtractFeeFromOutputs[idx].getInt <int >();
711
- if (setSubtractFeeFromOutputs.count (pos))
712
- throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, duplicated position: %d" , pos));
713
- if (pos < 0 )
714
- throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, negative position: %d" , pos));
715
- if (pos >= int (tx.vout .size ()))
716
- throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, position too large: %d" , pos));
717
- setSubtractFeeFromOutputs.insert (pos);
718
- }
719
-
720
- auto txr = FundTransaction (wallet, tx, change_position, lockUnspents, setSubtractFeeFromOutputs, coinControl);
708
+ auto txr = FundTransaction (wallet, tx, recipients, change_position, lockUnspents, coinControl);
721
709
if (!txr) {
722
710
throw JSONRPCError (RPC_WALLET_ERROR, ErrorString (txr).original );
723
711
}
@@ -843,11 +831,25 @@ RPCHelpMan fundrawtransaction()
843
831
if (!DecodeHexTx (tx, request.params [0 ].get_str (), try_no_witness, try_witness)) {
844
832
throw JSONRPCError (RPC_DESERIALIZATION_ERROR, " TX decode failed" );
845
833
}
846
-
834
+ UniValue options = request.params [1 ];
835
+ std::vector<std::pair<CTxDestination, CAmount>> destinations;
836
+ for (const auto & tx_out : tx.vout ) {
837
+ CTxDestination dest;
838
+ ExtractDestination (tx_out.scriptPubKey , dest);
839
+ destinations.emplace_back (dest, tx_out.nValue );
840
+ }
841
+ std::vector<std::string> dummy (destinations.size (), " dummy" );
842
+ std::vector<CRecipient> recipients = CreateRecipients (
843
+ destinations,
844
+ InterpretSubtractFeeFromOutputInstructions (options[" subtractFeeFromOutputs" ], dummy)
845
+ );
847
846
CCoinControl coin_control;
848
847
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
849
848
coin_control.m_allow_other_inputs = true ;
850
- auto txr = FundTransaction (*pwallet, tx, request.params [1 ], coin_control, /* override_min_fee=*/ true );
849
+ // Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
850
+ // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
851
+ tx.vout .clear ();
852
+ auto txr = FundTransaction (*pwallet, tx, recipients, options, coin_control, /* override_min_fee=*/ true );
851
853
852
854
UniValue result (UniValue::VOBJ);
853
855
result.pushKV (" hex" , EncodeHexTx (*txr.tx ));
@@ -1275,13 +1277,22 @@ RPCHelpMan send()
1275
1277
1276
1278
1277
1279
bool rbf{options.exists (" replaceable" ) ? options[" replaceable" ].get_bool () : pwallet->m_signal_rbf };
1280
+ UniValue outputs (UniValue::VOBJ);
1281
+ outputs = NormalizeOutputs (request.params [0 ]);
1282
+ std::vector<CRecipient> recipients = CreateRecipients (
1283
+ ParseOutputs (outputs),
1284
+ InterpretSubtractFeeFromOutputInstructions (options[" subtract_fee_from_outputs" ], outputs.getKeys ())
1285
+ );
1278
1286
CMutableTransaction rawTx = ConstructTransaction (options[" inputs" ], request.params [0 ], options[" locktime" ], rbf);
1279
1287
CCoinControl coin_control;
1280
1288
// Automatically select coins, unless at least one is manually selected. Can
1281
1289
// be overridden by options.add_inputs.
1282
1290
coin_control.m_allow_other_inputs = rawTx.vin .size () == 0 ;
1283
1291
SetOptionsInputWeights (options[" inputs" ], options);
1284
- auto txr = FundTransaction (*pwallet, rawTx, options, coin_control, /* override_min_fee=*/ false );
1292
+ // Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
1293
+ // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
1294
+ rawTx.vout .clear ();
1295
+ auto txr = FundTransaction (*pwallet, rawTx, recipients, options, coin_control, /* override_min_fee=*/ false );
1285
1296
1286
1297
return FinishTransaction (pwallet, options, CMutableTransaction (*txr.tx ));
1287
1298
}
@@ -1711,12 +1722,21 @@ RPCHelpMan walletcreatefundedpsbt()
1711
1722
const UniValue &replaceable_arg = options[" replaceable" ];
1712
1723
const bool rbf{replaceable_arg.isNull () ? wallet.m_signal_rbf : replaceable_arg.get_bool ()};
1713
1724
CMutableTransaction rawTx = ConstructTransaction (request.params [0 ], request.params [1 ], request.params [2 ], rbf);
1725
+ UniValue outputs (UniValue::VOBJ);
1726
+ outputs = NormalizeOutputs (request.params [1 ]);
1727
+ std::vector<CRecipient> recipients = CreateRecipients (
1728
+ ParseOutputs (outputs),
1729
+ InterpretSubtractFeeFromOutputInstructions (options[" subtractFeeFromOutputs" ], outputs.getKeys ())
1730
+ );
1714
1731
CCoinControl coin_control;
1715
1732
// Automatically select coins, unless at least one is manually selected. Can
1716
1733
// be overridden by options.add_inputs.
1717
1734
coin_control.m_allow_other_inputs = rawTx.vin .size () == 0 ;
1718
1735
SetOptionsInputWeights (request.params [0 ], options);
1719
- auto txr = FundTransaction (wallet, rawTx, options, coin_control, /* override_min_fee=*/ true );
1736
+ // Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
1737
+ // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
1738
+ rawTx.vout .clear ();
1739
+ auto txr = FundTransaction (wallet, rawTx, recipients, options, coin_control, /* override_min_fee=*/ true );
1720
1740
1721
1741
// Make a blank psbt
1722
1742
PartiallySignedTransaction psbtx (CMutableTransaction (*txr.tx ));
0 commit comments