Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/rpcwallet.cpp
Show First 20 Lines • Show All 925 Lines • ▼ Show 20 Lines | |||||
static UniValue sendmany(const Config &config, const JSONRPCRequest &request) { | static UniValue sendmany(const Config &config, const JSONRPCRequest &request) { | ||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { | if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
if (request.fHelp || request.params.size() < 2 || | const RPCHelpMan help{ | ||||
request.params.size() > 5) { | |||||
throw std::runtime_error(RPCHelpMan{ | |||||
"sendmany", | "sendmany", | ||||
"\nSend multiple times. Amounts are double-precision " | "\nSend multiple times. Amounts are double-precision " | ||||
"floating point numbers." + | "floating point numbers." + | ||||
HelpRequiringPassphrase(pwallet) + "\n", | HelpRequiringPassphrase(pwallet) + "\n", | ||||
{ | { | ||||
{"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, | {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"Must be set to \"\" for backwards compatibility.", "\"\""}, | "Must be set to \"\" for backwards compatibility.", "\"\""}, | ||||
{ | { | ||||
"amounts", | "amounts", | ||||
RPCArg::Type::OBJ, | RPCArg::Type::OBJ, | ||||
RPCArg::Optional::NO, | RPCArg::Optional::NO, | ||||
"A json object with addresses and amounts", | "A json object with addresses and amounts", | ||||
{ | { | ||||
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, | {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, | ||||
"The bitcoin address is the key, the numeric " | "The bitcoin address is the key, the numeric amount (can " | ||||
"amount (can be string) in " + | "be string) in " + | ||||
CURRENCY_UNIT + " is the value"}, | CURRENCY_UNIT + " is the value"}, | ||||
}, | }, | ||||
}, | }, | ||||
{"minconf", RPCArg::Type::NUM, /* default */ "1", | {"minconf", RPCArg::Type::NUM, /* default */ "1", | ||||
"Only use the balance confirmed at least this many " | "Only use the balance confirmed at least this many times."}, | ||||
"times."}, | {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, | ||||
{"comment", RPCArg::Type::STR, | "A comment"}, | ||||
RPCArg::Optional::OMITTED_NAMED_ARG, "A comment"}, | |||||
{ | { | ||||
"subtractfeefrom", | "subtractfeefrom", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
RPCArg::Optional::OMITTED_NAMED_ARG, | RPCArg::Optional::OMITTED_NAMED_ARG, | ||||
"A json array with addresses.\n" | "A json array with addresses.\n" | ||||
" The fee will be equally " | " The fee will be equally deducted " | ||||
"deducted from the amount of each selected address.\n" | "from the amount of each selected address.\n" | ||||
" Those recipients will " | " Those recipients will receive less " | ||||
"receive less bitcoins than you enter in their " | "bitcoins than you enter in their corresponding amount field.\n" | ||||
"corresponding amount field.\n" | " If no addresses are specified " | ||||
" If no addresses are " | "here, the sender pays the fee.", | ||||
"specified here, the sender pays the fee.", | |||||
{ | { | ||||
{"address", RPCArg::Type::STR, | {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, | ||||
RPCArg::Optional::OMITTED, | |||||
"Subtract fee from this address"}, | "Subtract fee from this address"}, | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
RPCResult{ | RPCResult{ | ||||
"\"txid\" (string) The transaction id for " | "\"txid\" (string) The transaction id for the " | ||||
"the send. Only 1 transaction is created regardless of \n" | "send. Only 1 transaction is created regardless of \n" | ||||
" the number of " | " the number of addresses.\n"}, | ||||
"addresses.\n"}, | |||||
RPCExamples{ | RPCExamples{ | ||||
"\nSend two amounts to two different addresses:\n" + | "\nSend two amounts to two different addresses:\n" + | ||||
HelpExampleCli( | HelpExampleCli( | ||||
"sendmany", | "sendmany", | ||||
"\"\" " | "\"\" " | ||||
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | ||||
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}" | "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + | ||||
"\"") + | |||||
"\nSend two amounts to two different addresses setting the " | "\nSend two amounts to two different addresses setting the " | ||||
"confirmation and comment:\n" + | "confirmation and comment:\n" + | ||||
HelpExampleCli( | HelpExampleCli("sendmany", | ||||
"sendmany", | |||||
"\"\" " | "\"\" " | ||||
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | ||||
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" " | "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" " | ||||
"6 \"testing\"") + | "6 \"testing\"") + | ||||
"\nSend two amounts to two different addresses, subtract fee " | "\nSend two amounts to two different addresses, subtract fee " | ||||
"from amount:\n" + | "from amount:\n" + | ||||
HelpExampleCli( | HelpExampleCli( | ||||
"sendmany", | "sendmany", | ||||
"\"\" " | "\"\" " | ||||
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | ||||
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" " | "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" " | ||||
"1 \"\" " | |||||
"\"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"," | "\"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"," | ||||
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + | "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc( | HelpExampleRpc("sendmany", | ||||
"sendmany", | |||||
"\"\", " | "\"\", " | ||||
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," | ||||
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"," | "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"," | ||||
" 6, \"testing\"")}, | " 6, \"testing\"")}, | ||||
} | }; | ||||
.ToString()); | |||||
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { | |||||
throw std::runtime_error(help.ToString()); | |||||
} | } | ||||
// Make sure the results are valid at least up to the most recent block | // Make sure the results are valid at least up to the most recent block | ||||
// the user could have gotten from another RPC command prior to now | // the user could have gotten from another RPC command prior to now | ||||
pwallet->BlockUntilSyncedToCurrentChain(); | pwallet->BlockUntilSyncedToCurrentChain(); | ||||
auto locked_chain = pwallet->chain().lock(); | auto locked_chain = pwallet->chain().lock(); | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { | if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { | ||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_CLIENT_P2P_DISABLED, | RPC_CLIENT_P2P_DISABLED, | ||||
"Error: Peer-to-peer functionality missing or disabled"); | "Error: Peer-to-peer functionality missing or disabled"); | ||||
} | } | ||||
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { | if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"Dummy value must be set to \"\""); | "Dummy value must be set to \"\""); | ||||
} | } | ||||
UniValue sendTo = request.params[1].get_obj(); | UniValue sendTo = request.params[1].get_obj(); | ||||
int nMinDepth = 1; | |||||
if (!request.params[2].isNull()) { | |||||
nMinDepth = request.params[2].get_int(); | |||||
} | |||||
mapValue_t mapValue; | mapValue_t mapValue; | ||||
if (!request.params[3].isNull() && !request.params[3].get_str().empty()) { | if (!request.params[3].isNull() && !request.params[3].get_str().empty()) { | ||||
mapValue["comment"] = request.params[3].get_str(); | mapValue["comment"] = request.params[3].get_str(); | ||||
} | } | ||||
UniValue subtractFeeFromAmount(UniValue::VARR); | UniValue subtractFeeFromAmount(UniValue::VARR); | ||||
if (!request.params[4].isNull()) { | if (!request.params[4].isNull()) { | ||||
subtractFeeFromAmount = request.params[4].get_array(); | subtractFeeFromAmount = request.params[4].get_array(); | ||||
} | } | ||||
std::set<CTxDestination> destinations; | std::set<CTxDestination> destinations; | ||||
std::vector<CRecipient> vecSend; | std::vector<CRecipient> vecSend; | ||||
Amount totalAmount = Amount::zero(); | |||||
std::vector<std::string> keys = sendTo.getKeys(); | std::vector<std::string> keys = sendTo.getKeys(); | ||||
for (const std::string &name_ : keys) { | for (const std::string &name_ : keys) { | ||||
CTxDestination dest = DecodeDestination(name_, config.GetChainParams()); | CTxDestination dest = DecodeDestination(name_, config.GetChainParams()); | ||||
if (!IsValidDestination(dest)) { | if (!IsValidDestination(dest)) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
std::string("Invalid Bitcoin address: ") + | std::string("Invalid Bitcoin address: ") + | ||||
name_); | name_); | ||||
} | } | ||||
if (destinations.count(dest)) { | if (destinations.count(dest)) { | ||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_INVALID_PARAMETER, | RPC_INVALID_PARAMETER, | ||||
std::string("Invalid parameter, duplicated address: ") + name_); | std::string("Invalid parameter, duplicated address: ") + name_); | ||||
} | } | ||||
destinations.insert(dest); | destinations.insert(dest); | ||||
CScript scriptPubKey = GetScriptForDestination(dest); | CScript scriptPubKey = GetScriptForDestination(dest); | ||||
Amount nAmount = AmountFromValue(sendTo[name_]); | Amount nAmount = AmountFromValue(sendTo[name_]); | ||||
if (nAmount <= Amount::zero()) { | if (nAmount <= Amount::zero()) { | ||||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); | ||||
} | } | ||||
totalAmount += nAmount; | |||||
bool fSubtractFeeFromAmount = false; | bool fSubtractFeeFromAmount = false; | ||||
for (size_t idx = 0; idx < subtractFeeFromAmount.size(); idx++) { | for (size_t idx = 0; idx < subtractFeeFromAmount.size(); idx++) { | ||||
const UniValue &addr = subtractFeeFromAmount[idx]; | const UniValue &addr = subtractFeeFromAmount[idx]; | ||||
if (addr.get_str() == name_) { | if (addr.get_str() == name_) { | ||||
fSubtractFeeFromAmount = true; | fSubtractFeeFromAmount = true; | ||||
} | } | ||||
} | } | ||||
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; | CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; | ||||
vecSend.push_back(recipient); | vecSend.push_back(recipient); | ||||
} | } | ||||
EnsureWalletIsUnlocked(pwallet); | EnsureWalletIsUnlocked(pwallet); | ||||
// Check funds | |||||
if (totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth)) { | |||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, | |||||
"Wallet has insufficient funds"); | |||||
} | |||||
// Shuffle recipient list | // Shuffle recipient list | ||||
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); | std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); | ||||
// Send | // Send | ||||
CReserveKey keyChange(pwallet); | CReserveKey keyChange(pwallet); | ||||
Amount nFeeRequired = Amount::zero(); | Amount nFeeRequired = Amount::zero(); | ||||
int nChangePosRet = -1; | int nChangePosRet = -1; | ||||
std::string strFailReason; | std::string strFailReason; | ||||
▲ Show 20 Lines • Show All 3,707 Lines • Show Last 20 Lines |