diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -74,6 +74,7 @@ {"listunspent", 0, "minconf"}, {"listunspent", 1, "maxconf"}, {"listunspent", 2, "addresses"}, + {"listunspent", 4, "query_options"}, {"getblock", 1, "verbose"}, {"getblockheader", 1, "verbose"}, {"getchaintxstats", 0, "nblocks"}, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3063,10 +3063,10 @@ return NullUniValue; } - if (request.fHelp || request.params.size() > 4) { + if (request.fHelp || request.params.size() > 5) { throw std::runtime_error( "listunspent ( minconf maxconf [\"addresses\",...] " - "[include_unsafe] )\n" + "[include_unsafe] [query_options])\n" "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filter to only include txouts paid to specified " @@ -3076,19 +3076,32 @@ "confirmations to filter\n" "2. maxconf (numeric, optional, default=9999999) The " "maximum confirmations to filter\n" - "3. \"addresses\" (string) A json array of bitcoin addresses to " - "filter\n" + "3. \"addresses\" (string) A json array of bitcoin addresses " + "to filter\n" " [\n" - " \"address\" (string) bitcoin address\n" + " \"address\" (string) bitcoin address\n" " ,...\n" " ]\n" "4. include_unsafe (bool, optional, default=true) Include outputs " "that are not safe to spend\n" - " because they come from unconfirmed untrusted " - "transactions or unconfirmed\n" - " replacement transactions (cases where we are " - "less sure that a conflicting\n" - " transaction won't be mined).\n" + " See description of \"safe\" attribute below.\n" + "5. query_options (json, optional) JSON with query options\n" + " {\n" + " \"minimumAmount\" (numeric or string, default=0) Minimum " + "value of each UTXO in " + + CURRENCY_UNIT + "\n" + " \"maximumAmount\" (numeric or string, " + "default=unlimited) Maximum value of each UTXO " + "in " + + CURRENCY_UNIT + "\n" + " \"maximumCount\" (numeric or string, " + "default=unlimited) Maximum number of UTXOs\n" + " \"minimumSumAmount\" (numeric or string, " + "default=unlimited) Minimum sum value of all UTXOs " + "in " + + CURRENCY_UNIT + + "\n" + " }\n" "\nResult\n" "[ (array of json object)\n" " {\n" @@ -3129,7 +3142,13 @@ HelpExampleRpc("listunspent", "6, 9999999 " "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\"," - "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")); + "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + + HelpExampleCli( + "listunspent", + "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + + HelpExampleRpc( + "listunspent", + "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")); } ObserveSafeMode(); @@ -3174,16 +3193,40 @@ include_unsafe = request.params[3].get_bool(); } + Amount nMinimumAmount = Amount::zero(); + Amount nMaximumAmount = MAX_MONEY; + Amount nMinimumSumAmount = MAX_MONEY; + uint64_t nMaximumCount = 0; + + if (!request.params[4].isNull()) { + const UniValue &options = request.params[4].get_obj(); + + if (options.exists("minimumAmount")) { + nMinimumAmount = AmountFromValue(options["minimumAmount"]); + } + + if (options.exists("maximumAmount")) { + nMaximumAmount = AmountFromValue(options["maximumAmount"]); + } + + if (options.exists("minimumSumAmount")) { + nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); + } + + if (options.exists("maximumCount")) { + nMaximumCount = options["maximumCount"].get_int64(); + } + } + UniValue results(UniValue::VARR); std::vector vecOutputs; assert(pwallet != nullptr); LOCK2(cs_main, pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, true); - for (const COutput &out : vecOutputs) { - if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) { - continue; - } + pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, + nMinimumAmount, nMaximumAmount, nMinimumSumAmount, + nMaximumCount, nMinDepth, nMaxDepth); + for (const COutput &out : vecOutputs) { CTxDestination address; const CScript &scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); @@ -3611,7 +3654,7 @@ { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly"} }, { "wallet", "listtransactions", listtransactions, {"account","count","skip","include_watchonly"} }, - { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe"} }, + { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwallets", listwallets, {} }, { "wallet", "lockunspent", lockunspent, {"unlock","transactions"} }, { "wallet", "move", movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -773,7 +773,12 @@ */ void AvailableCoins(std::vector &vCoins, bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, - bool fIncludeZeroValue = false) const; + const Amount nMinimumAmount = SATOSHI, + const Amount nMaximumAmount = MAX_MONEY, + const Amount nMinimumSumAmount = MAX_MONEY, + const uint64_t &nMaximumCount = 0, + const int &nMinDepth = 0, + const int &nMaxDepth = 9999999) const; /** * Shuffle and select coins until nTargetValue is reached while avoiding diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2225,8 +2225,13 @@ void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlySafe, const CCoinControl *coinControl, - bool fIncludeZeroValue) const { + const Amount nMinimumAmount, + const Amount nMaximumAmount, + const Amount nMinimumSumAmount, + const uint64_t &nMaximumCount, + const int &nMinDepth, const int &nMaxDepth) const { vCoins.clear(); + Amount nTotal = Amount::zero(); LOCK2(cs_main, cs_wallet); for (std::map::const_iterator it = mapWallet.begin(); @@ -2280,23 +2285,58 @@ continue; } + if (nDepth < nMinDepth || nDepth > nMaxDepth) { + continue; + } + for (uint32_t i = 0; i < pcoin->tx->vout.size(); i++) { + if (pcoin->tx->vout[i].nValue < nMinimumAmount || + pcoin->tx->vout[i].nValue > nMaximumAmount) { + continue; + } + + if (coinControl && coinControl->HasSelected() && + !coinControl->fAllowOtherInputs && + !coinControl->IsSelected(COutPoint((*it).first, i))) { + continue; + } + + if (IsLockedCoin((*it).first, i)) { + continue; + } + + if (IsSpent(wtxid, i)) { + continue; + } + isminetype mine = IsMine(pcoin->tx->vout[i]); - if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && - !IsLockedCoin((*it).first, i) && - (pcoin->tx->vout[i].nValue > Amount::zero() || - fIncludeZeroValue) && - (!coinControl || !coinControl->HasSelected() || - coinControl->fAllowOtherInputs || - coinControl->IsSelected(COutPoint((*it).first, i)))) { - vCoins.push_back(COutput( - pcoin, i, nDepth, - ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || - (coinControl && coinControl->fAllowWatchOnly && - (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO), - (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != - ISMINE_NO, - safeTx)); + + if (mine == ISMINE_NO) { + continue; + } + + bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || + (coinControl && coinControl->fAllowWatchOnly && + (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); + bool fSolvableIn = + (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != + ISMINE_NO; + + vCoins.push_back( + COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + + // Checks the sum amount of all UTXO's. + if (nMinimumSumAmount != MAX_MONEY) { + nTotal += pcoin->tx->vout[i].nValue; + + if (nTotal >= nMinimumSumAmount) { + return; + } + } + + // Checks the maximum number of UTXO's. + if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { + return; } } }