From 04862cf7a72dd180f2123442851a7f9888a79f10 Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 8 May 2026 16:51:03 -0700 Subject: [PATCH] Support fee-subtracting sendmany payouts --- src/wallet/rpcwallet.cpp | 38 +++++++++++++++++++++++----- src/wallet/wallet.cpp | 54 ++++++++++++++++++++++++++++++++-------- src/wallet/wallet.h | 3 ++- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 12a73b41..d824c624 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -914,9 +914,9 @@ UniValue sendfrom(const UniValue& params, bool fHelp) UniValue sendmany(const UniValue& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (fHelp || params.size() < 2 || params.size() > 8) throw runtime_error( - "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" )\n" + "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" subtractfeefrom )\n" "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase() + "\n" @@ -929,6 +929,8 @@ UniValue sendmany(const UniValue& params, bool fHelp) " }\n" "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" "4. \"comment\" (string, optional) A comment\n" + "5. subtractfeefrom (array, optional) A json array with addresses that pay the fee\n" + "\nAlso accepts Dash/PIVX-style sendmany arguments where comment is argument 5 and subtractfeefrom is argument 6.\n" "\nResult:\n" "\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" @@ -952,15 +954,31 @@ UniValue sendmany(const UniValue& params, bool fHelp) CWalletTx wtx; wtx.strFromAccount = strAccount; - if (params.size() > 3 && !params[3].isNull() && !params[3].get_str().empty()) - wtx.mapValue["comment"] = params[3].get_str(); + int nCommentIndex = 3; + int nSubtractFeeFromIndex = 4; + if (params.size() > 3 && params[3].isBool()) { + nCommentIndex = 4; + nSubtractFeeFromIndex = 5; + } + + if (params.size() > nCommentIndex && !params[nCommentIndex].isNull() && params[nCommentIndex].isStr() && !params[nCommentIndex].get_str().empty()) + wtx.mapValue["comment"] = params[nCommentIndex].get_str(); set setAddress; + set setSubtractFeeFromAddress; + set setSubtractFeeFromOutputs; vector > vecSend; + if (params.size() > nSubtractFeeFromIndex && !params[nSubtractFeeFromIndex].isNull()) { + UniValue subtractFeeFrom = params[nSubtractFeeFromIndex].get_array(); + for (unsigned int idx = 0; idx < subtractFeeFrom.size(); idx++) + setSubtractFeeFromAddress.insert(subtractFeeFrom[idx].get_str()); + } + CAmount totalAmount = 0; vector keys = sendTo.getKeys(); - for (const string& name_ : keys) { + for (unsigned int i = 0; i < keys.size(); i++) { + const string& name_ = keys[i]; CBitcoinAddress address(name_); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Agrarian address: ")+name_); @@ -974,6 +992,14 @@ UniValue sendmany(const UniValue& params, bool fHelp) totalAmount += nAmount; vecSend.push_back(make_pair(scriptPubKey, nAmount)); + + if (setSubtractFeeFromAddress.count(name_)) + setSubtractFeeFromOutputs.insert(i); + } + + for (const string& name_ : setSubtractFeeFromAddress) { + if (!sendTo.exists(name_)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, subtract fee from address not in send set: ")+name_); } EnsureWalletIsUnlocked(); @@ -987,7 +1013,7 @@ UniValue sendmany(const UniValue& params, bool fHelp) CReserveKey keyChange(pwalletMain); CAmount nFeeRequired = 0; string strFailReason; - bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); + bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason, NULL, ALL_COINS, false, 0, &setSubtractFeeFromOutputs); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); if (!pwalletMain->CommitTransaction(wtx, keyChange)) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5fc525e0..3bf03e84 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2094,11 +2094,13 @@ bool CWallet::CreateTransaction(const vector >& vecSend, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX, - CAmount nFeePay) + CAmount nFeePay, + const std::set* setSubtractFeeFromOutputs) { if (useIX && nFeePay < CENT) nFeePay = CENT; CAmount nValue = 0; + const bool fSubtractFeeFromAmount = setSubtractFeeFromOutputs && !setSubtractFeeFromOutputs->empty(); for (const PAIRTYPE(CScript, CAmount) & s : vecSend) { if (nValue < 0) { @@ -2126,13 +2128,29 @@ bool CWallet::CreateTransaction(const vector >& vecSend, txNew.vout.clear(); wtxNew.fFromMe = true; - CAmount nTotalValue = nValue + nFeeRet; + CAmount nTotalValue = nValue + (fSubtractFeeFromAmount ? 0 : nFeeRet); double dPriority = 0; // vouts to the payees if (coinControl && !coinControl->fSplitBlock) { - for (const PAIRTYPE(CScript, CAmount) & s : vecSend) { - CTxOut txout(s.second, s.first); + for (unsigned int i = 0; i < vecSend.size(); i++) { + const PAIRTYPE(CScript, CAmount)& s = vecSend[i]; + CAmount nAmount = s.second; + + if(fSubtractFeeFromAmount && setSubtractFeeFromOutputs->count(i)) { + CAmount nSubtractFee = nFeeRet / setSubtractFeeFromOutputs->size(); + if (i == (unsigned int)*setSubtractFeeFromOutputs->begin()) + nSubtractFee += nFeeRet % setSubtractFeeFromOutputs->size(); + + nAmount -= nSubtractFee; + } + + if (nAmount <= 0) { + strFailReason = _("The transaction amount is too small to pay the fee"); + return false; + } + + CTxOut txout(nAmount, s.first); if (txout.IsDust(::minRelayTxFee)) { strFailReason = _("Transaction amount too small"); return false; @@ -2148,13 +2166,29 @@ bool CWallet::CreateTransaction(const vector >& vecSend, else nSplitBlock = 1; - for (const PAIRTYPE(CScript, CAmount) & s : vecSend) { - for (int i = 0; i < nSplitBlock; i++) { - if (i == nSplitBlock - 1) { - uint64_t nRemainder = s.second % nSplitBlock; - txNew.vout.push_back(CTxOut((s.second / nSplitBlock) + nRemainder, s.first)); + for (unsigned int i = 0; i < vecSend.size(); i++) { + const PAIRTYPE(CScript, CAmount)& s = vecSend[i]; + CAmount nAmount = s.second; + + if(fSubtractFeeFromAmount && setSubtractFeeFromOutputs->count(i)) { + CAmount nSubtractFee = nFeeRet / setSubtractFeeFromOutputs->size(); + if (i == (unsigned int)*setSubtractFeeFromOutputs->begin()) + nSubtractFee += nFeeRet % setSubtractFeeFromOutputs->size(); + + nAmount -= nSubtractFee; + } + + if (nAmount <= 0) { + strFailReason = _("The transaction amount is too small to pay the fee"); + return false; + } + + for (int j = 0; j < nSplitBlock; j++) { + if (j == nSplitBlock - 1) { + uint64_t nRemainder = nAmount % nSplitBlock; + txNew.vout.push_back(CTxOut((nAmount / nSplitBlock) + nRemainder, s.first)); } else - txNew.vout.push_back(CTxOut(s.second / nSplitBlock, s.first)); + txNew.vout.push_back(CTxOut(nAmount / nSplitBlock, s.first)); } } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1e633b89..94b1caf8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -500,7 +500,8 @@ public: const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = false, - CAmount nFeePay = 0); + CAmount nFeePay = 0, + const std::set* setSubtractFeeFromOutputs = NULL); bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl = NULL, AvailableCoinsType coin_type = ALL_COINS, bool useIX = false, CAmount nFeePay = 0); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, std::string strCommand = "tx"); bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb);