2424
2525
2626namespace 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 )
2828{
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)};
5333 recipients.push_back (recipient);
5434 }
35+ return recipients;
5536}
5637
5738static 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
7657 }
7758}
7859
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+
7991static UniValue FinishTransaction (const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx)
8092{
8193 // Make a blank psbt
@@ -275,11 +287,6 @@ RPCHelpMan sendtoaddress()
275287 if (!request.params [3 ].isNull () && !request.params [3 ].get_str ().empty ())
276288 mapValue[" to" ] = request.params [3 ].get_str ();
277289
278- bool fSubtractFeeFromAmount = false ;
279- if (!request.params [4 ].isNull ()) {
280- fSubtractFeeFromAmount = request.params [4 ].get_bool ();
281- }
282-
283290 CCoinControl coin_control;
284291 if (!request.params [5 ].isNull ()) {
285292 coin_control.m_signal_bip125_rbf = request.params [5 ].get_bool ();
@@ -296,13 +303,10 @@ RPCHelpMan sendtoaddress()
296303 UniValue address_amounts (UniValue::VOBJ);
297304 const std::string address = request.params [0 ].get_str ();
298305 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+ );
306310 const bool verbose{request.params [10 ].isNull () ? false : request.params [10 ].get_bool ()};
307311
308312 return SendMoney (*pwallet, coin_control, recipients, mapValue, verbose);
@@ -386,19 +390,17 @@ RPCHelpMan sendmany()
386390 if (!request.params [3 ].isNull () && !request.params [3 ].get_str ().empty ())
387391 mapValue[" comment" ] = request.params [3 ].get_str ();
388392
389- UniValue subtractFeeFromAmount (UniValue::VARR);
390- if (!request.params [4 ].isNull ())
391- subtractFeeFromAmount = request.params [4 ].get_array ();
392-
393393 CCoinControl coin_control;
394394 if (!request.params [5 ].isNull ()) {
395395 coin_control.m_signal_bip125_rbf = request.params [5 ].get_bool ();
396396 }
397397
398398 SetFeeEstimateMode (*pwallet, coin_control, /* conf_target=*/ request.params [6 ], /* estimate_mode=*/ request.params [7 ], /* fee_rate=*/ request.params [8 ], /* override_min_fee=*/ false );
399399
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+ );
402404 const bool verbose{request.params [9 ].isNull () ? false : request.params [9 ].get_bool ()};
403405
404406 return SendMoney (*pwallet, coin_control, recipients, std::move (mapValue), verbose);
@@ -488,17 +490,17 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true)
488490 return args;
489491}
490492
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)
492494{
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 ());
493498 // Make sure the results are valid at least up to the most recent block
494499 // the user could have gotten from another RPC command prior to now
495500 wallet.BlockUntilSyncedToCurrentChain ();
496501
497502 std::optional<unsigned int > change_position;
498503 bool lockUnspents = false ;
499- UniValue subtractFeeFromOutputs;
500- std::set<int > setSubtractFeeFromOutputs;
501-
502504 if (!options.isNull ()) {
503505 if (options.type () == UniValue::VBOOL) {
504506 // backward compatibility bool only fallback
@@ -553,7 +555,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
553555
554556 if (options.exists (" changePosition" ) || options.exists (" change_position" )) {
555557 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 ()) {
557559 throw JSONRPCError (RPC_INVALID_PARAMETER, " changePosition out of bounds" );
558560 }
559561 change_position = (unsigned int )pos;
@@ -595,9 +597,6 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
595597 coinControl.fOverrideFeeRate = true ;
596598 }
597599
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-
601600 if (options.exists (" replaceable" )) {
602601 coinControl.m_signal_bip125_rbf = options[" replaceable" ].get_bool ();
603602 }
@@ -703,21 +702,10 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
703702 }
704703 }
705704
706- if (tx. vout . size () == 0 )
705+ if (recipients. empty () )
707706 throw JSONRPCError (RPC_INVALID_PARAMETER, " TX must have at least one output" );
708707
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);
721709 if (!txr) {
722710 throw JSONRPCError (RPC_WALLET_ERROR, ErrorString (txr).original );
723711 }
@@ -843,11 +831,25 @@ RPCHelpMan fundrawtransaction()
843831 if (!DecodeHexTx (tx, request.params [0 ].get_str (), try_no_witness, try_witness)) {
844832 throw JSONRPCError (RPC_DESERIALIZATION_ERROR, " TX decode failed" );
845833 }
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+ );
847846 CCoinControl coin_control;
848847 // Automatically select (additional) coins. Can be overridden by options.add_inputs.
849848 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 );
851853
852854 UniValue result (UniValue::VOBJ);
853855 result.pushKV (" hex" , EncodeHexTx (*txr.tx ));
@@ -1275,13 +1277,22 @@ RPCHelpMan send()
12751277
12761278
12771279 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+ );
12781286 CMutableTransaction rawTx = ConstructTransaction (options[" inputs" ], request.params [0 ], options[" locktime" ], rbf);
12791287 CCoinControl coin_control;
12801288 // Automatically select coins, unless at least one is manually selected. Can
12811289 // be overridden by options.add_inputs.
12821290 coin_control.m_allow_other_inputs = rawTx.vin .size () == 0 ;
12831291 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 );
12851296
12861297 return FinishTransaction (pwallet, options, CMutableTransaction (*txr.tx ));
12871298 }
@@ -1711,12 +1722,21 @@ RPCHelpMan walletcreatefundedpsbt()
17111722 const UniValue &replaceable_arg = options[" replaceable" ];
17121723 const bool rbf{replaceable_arg.isNull () ? wallet.m_signal_rbf : replaceable_arg.get_bool ()};
17131724 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+ );
17141731 CCoinControl coin_control;
17151732 // Automatically select coins, unless at least one is manually selected. Can
17161733 // be overridden by options.add_inputs.
17171734 coin_control.m_allow_other_inputs = rawTx.vin .size () == 0 ;
17181735 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 );
17201740
17211741 // Make a blank psbt
17221742 PartiallySignedTransaction psbtx (CMutableTransaction (*txr.tx ));
0 commit comments