Support fee-subtracting sendmany payouts

This commit is contained in:
2026-05-08 16:51:03 -07:00
parent eb87369a49
commit 04862cf7a7
3 changed files with 78 additions and 17 deletions
+32 -6
View File
@@ -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<CBitcoinAddress> setAddress;
set<string> setSubtractFeeFromAddress;
set<int> setSubtractFeeFromOutputs;
vector<pair<CScript, CAmount> > 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<string> 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))
+44 -10
View File
@@ -2094,11 +2094,13 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
const CCoinControl* coinControl,
AvailableCoinsType coin_type,
bool useIX,
CAmount nFeePay)
CAmount nFeePay,
const std::set<int>* 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<pair<CScript, CAmount> >& 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<pair<CScript, CAmount> >& 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));
}
}
}
+2 -1
View File
@@ -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<int>* 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);