@@ -74,6 +74,16 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
7474 }
7575 }
7676 }
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+ }
7787 }
7888 return sffo_set;
7989}
@@ -480,17 +490,17 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true)
480490 return args;
481491}
482492
483- 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)
484494{
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 ());
485498 // Make sure the results are valid at least up to the most recent block
486499 // the user could have gotten from another RPC command prior to now
487500 wallet.BlockUntilSyncedToCurrentChain ();
488501
489502 std::optional<unsigned int > change_position;
490503 bool lockUnspents = false ;
491- UniValue subtractFeeFromOutputs;
492- std::set<int > setSubtractFeeFromOutputs;
493-
494504 if (!options.isNull ()) {
495505 if (options.type () == UniValue::VBOOL) {
496506 // backward compatibility bool only fallback
@@ -545,7 +555,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
545555
546556 if (options.exists (" changePosition" ) || options.exists (" change_position" )) {
547557 int pos = (options.exists (" change_position" ) ? options[" change_position" ] : options[" changePosition" ]).getInt <int >();
548- if (pos < 0 || (unsigned int )pos > tx. vout .size ()) {
558+ if (pos < 0 || (unsigned int )pos > recipients .size ()) {
549559 throw JSONRPCError (RPC_INVALID_PARAMETER, " changePosition out of bounds" );
550560 }
551561 change_position = (unsigned int )pos;
@@ -587,9 +597,6 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
587597 coinControl.fOverrideFeeRate = true ;
588598 }
589599
590- if (options.exists (" subtractFeeFromOutputs" ) || options.exists (" subtract_fee_from_outputs" ) )
591- subtractFeeFromOutputs = (options.exists (" subtract_fee_from_outputs" ) ? options[" subtract_fee_from_outputs" ] : options[" subtractFeeFromOutputs" ]).get_array ();
592-
593600 if (options.exists (" replaceable" )) {
594601 coinControl.m_signal_bip125_rbf = options[" replaceable" ].get_bool ();
595602 }
@@ -695,21 +702,10 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
695702 }
696703 }
697704
698- if (tx. vout . size () == 0 )
705+ if (recipients. empty () )
699706 throw JSONRPCError (RPC_INVALID_PARAMETER, " TX must have at least one output" );
700707
701- for (unsigned int idx = 0 ; idx < subtractFeeFromOutputs.size (); idx++) {
702- int pos = subtractFeeFromOutputs[idx].getInt <int >();
703- if (setSubtractFeeFromOutputs.count (pos))
704- throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, duplicated position: %d" , pos));
705- if (pos < 0 )
706- throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, negative position: %d" , pos));
707- if (pos >= int (tx.vout .size ()))
708- throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid parameter, position too large: %d" , pos));
709- setSubtractFeeFromOutputs.insert (pos);
710- }
711-
712- auto txr = FundTransaction (wallet, tx, change_position, lockUnspents, setSubtractFeeFromOutputs, coinControl);
708+ auto txr = FundTransaction (wallet, tx, recipients, change_position, lockUnspents, coinControl);
713709 if (!txr) {
714710 throw JSONRPCError (RPC_WALLET_ERROR, ErrorString (txr).original );
715711 }
@@ -835,11 +831,25 @@ RPCHelpMan fundrawtransaction()
835831 if (!DecodeHexTx (tx, request.params [0 ].get_str (), try_no_witness, try_witness)) {
836832 throw JSONRPCError (RPC_DESERIALIZATION_ERROR, " TX decode failed" );
837833 }
838-
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+ );
839846 CCoinControl coin_control;
840847 // Automatically select (additional) coins. Can be overridden by options.add_inputs.
841848 coin_control.m_allow_other_inputs = true ;
842- 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 );
843853
844854 UniValue result (UniValue::VOBJ);
845855 result.pushKV (" hex" , EncodeHexTx (*txr.tx ));
@@ -1267,13 +1277,22 @@ RPCHelpMan send()
12671277
12681278
12691279 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+ );
12701286 CMutableTransaction rawTx = ConstructTransaction (options[" inputs" ], request.params [0 ], options[" locktime" ], rbf);
12711287 CCoinControl coin_control;
12721288 // Automatically select coins, unless at least one is manually selected. Can
12731289 // be overridden by options.add_inputs.
12741290 coin_control.m_allow_other_inputs = rawTx.vin .size () == 0 ;
12751291 SetOptionsInputWeights (options[" inputs" ], options);
1276- 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 );
12771296
12781297 return FinishTransaction (pwallet, options, CMutableTransaction (*txr.tx ));
12791298 }
@@ -1703,12 +1722,21 @@ RPCHelpMan walletcreatefundedpsbt()
17031722 const UniValue &replaceable_arg = options[" replaceable" ];
17041723 const bool rbf{replaceable_arg.isNull () ? wallet.m_signal_rbf : replaceable_arg.get_bool ()};
17051724 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+ );
17061731 CCoinControl coin_control;
17071732 // Automatically select coins, unless at least one is manually selected. Can
17081733 // be overridden by options.add_inputs.
17091734 coin_control.m_allow_other_inputs = rawTx.vin .size () == 0 ;
17101735 SetOptionsInputWeights (request.params [0 ], options);
1711- 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 );
17121740
17131741 // Make a blank psbt
17141742 PartiallySignedTransaction psbtx (CMutableTransaction (*txr.tx ));
0 commit comments