|
@ -4647,48 +4647,108 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) |
|
|
THROW_IF_SYNCING(HUSH_INSYNC); |
|
|
THROW_IF_SYNCING(HUSH_INSYNC); |
|
|
|
|
|
|
|
|
// Check that the from address is valid.
|
|
|
// Check that the from address is valid.
|
|
|
auto fromaddress = params[0].get_str(); |
|
|
auto fromaddress = params[0].get_str(); |
|
|
bool fromTaddr = false; |
|
|
bool fromTaddr = false; |
|
|
bool fromSapling = false; |
|
|
bool fromSapling = false; |
|
|
|
|
|
|
|
|
uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); |
|
|
uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); |
|
|
|
|
|
UniValue outputs = params[1].get_array(); |
|
|
|
|
|
|
|
|
|
|
|
if (outputs.size()==0) |
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); |
|
|
|
|
|
|
|
|
CTxDestination taddr = DecodeDestination(fromaddress); |
|
|
|
|
|
// TODO: implement special symbolic fromaddrs
|
|
|
// TODO: implement special symbolic fromaddrs
|
|
|
// TODO: list of (zaddr,amount)
|
|
|
// TODO: list of (zaddr,amount)
|
|
|
// "z" => spend from any zaddr
|
|
|
// "z" => spend from any zaddr
|
|
|
// "t" => spend from any taddr
|
|
|
// "t" => spend from any taddr
|
|
|
// "*" => spend from any addr, zaddrs first
|
|
|
// "*" => spend from any addr, zaddrs first
|
|
|
if(fromaddress == "z") { |
|
|
if(fromaddress == "z") { |
|
|
|
|
|
// TODO: refactor this and z_getbalances to use common code
|
|
|
|
|
|
std::set<libzcash::PaymentAddress> zaddrs = {}; |
|
|
|
|
|
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {}; |
|
|
|
|
|
pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs); |
|
|
|
|
|
|
|
|
|
|
|
zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end()); |
|
|
|
|
|
|
|
|
|
|
|
int nMinDepth = 1; |
|
|
std::vector<SaplingNoteEntry> saplingEntries; |
|
|
std::vector<SaplingNoteEntry> saplingEntries; |
|
|
//pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
|
|
|
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth); |
|
|
|
|
|
|
|
|
|
|
|
std::map<std::string, CAmount> mapBalances; |
|
|
for (auto & entry : saplingEntries) { |
|
|
for (auto & entry : saplingEntries) { |
|
|
// EncodePaymentAddress(entry.address);
|
|
|
auto zaddr = EncodePaymentAddress(entry.address); |
|
|
|
|
|
CAmount nBalance = CAmount(entry.note.value()); |
|
|
|
|
|
if(mapBalances.count(zaddr)) { |
|
|
|
|
|
mapBalances[zaddr] += nBalance; |
|
|
|
|
|
} else { |
|
|
|
|
|
mapBalances[zaddr] = nBalance; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
std::vector<std::pair<std::string,CAmount>> vec; |
|
|
} |
|
|
std::copy(mapBalances.begin(), mapBalances.end(), std::back_inserter<std::vector<std::pair<std::string,CAmount>>>(vec)); |
|
|
|
|
|
|
|
|
fromTaddr = IsValidDestination(taddr); |
|
|
std::sort(vec.begin(), vec.end(), [](const std::pair<std::string, CAmount> &l, const std::pair<std::string,CAmount> &r) |
|
|
if (!fromTaddr) { |
|
|
{ |
|
|
auto res = DecodePaymentAddress(fromaddress); |
|
|
if (l.second != r.second) { |
|
|
if (!IsValidPaymentAddress(res, branchId)) { |
|
|
return l.second > r.second; |
|
|
// invalid
|
|
|
} |
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); |
|
|
return l.first > r.first; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
//TODO: avoid calculating nTotalOut twice
|
|
|
|
|
|
CAmount nTotalOut = 0; |
|
|
|
|
|
for (const UniValue& o : outputs.getValues()) { |
|
|
|
|
|
if (!o.isObject()) |
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); |
|
|
|
|
|
|
|
|
|
|
|
UniValue av = find_value(o, "amount"); |
|
|
|
|
|
CAmount nAmount = AmountFromValue( av ); |
|
|
|
|
|
if (nAmount < 0) |
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); |
|
|
|
|
|
|
|
|
|
|
|
nTotalOut += nAmount; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Check that we have the spending key
|
|
|
//TODO: choose one random address with enough funds
|
|
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { |
|
|
CAmount nFee; |
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); |
|
|
if (params.size() > 3) { |
|
|
|
|
|
if (params[3].get_real() == 0.0) { |
|
|
|
|
|
nFee = 0; |
|
|
|
|
|
} else { |
|
|
|
|
|
nFee = AmountFromValue( params[3] ); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Remember whether this is a Sapling address
|
|
|
// the total amount needed in a single zaddr to use as fromaddress
|
|
|
fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; |
|
|
CAmount nMinBal = nTotalOut + nFee; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
UniValue outputs = params[1].get_array(); |
|
|
std::vector<std::string> vPotentialAddresses; |
|
|
|
|
|
for (auto & entry : vec) { |
|
|
|
|
|
if(entry.second >= nMinBal) { |
|
|
|
|
|
vPotentialAddresses.push_back(entry.first); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (outputs.size()==0) |
|
|
// select a random address with enough confirmed balance
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); |
|
|
fromaddress = vPotentialAddresses[ GetRandInt(vPotentialAddresses.size()) ]; |
|
|
|
|
|
} else { |
|
|
|
|
|
CTxDestination taddr = DecodeDestination(fromaddress); |
|
|
|
|
|
fromTaddr = IsValidDestination(taddr); |
|
|
|
|
|
if (!fromTaddr) { |
|
|
|
|
|
auto res = DecodePaymentAddress(fromaddress); |
|
|
|
|
|
if (!IsValidPaymentAddress(res, branchId)) { |
|
|
|
|
|
// invalid
|
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check that we have the spending key
|
|
|
|
|
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { |
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Remember whether this is a Sapling address
|
|
|
|
|
|
fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Keep track of addresses to spot duplicates
|
|
|
// Keep track of addresses to spot duplicates
|
|
|
set<std::string> setAddress; |
|
|
set<std::string> setAddress; |
|
|