coins.cpp raw
1 // Copyright (c) 2011-2022 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <core_io.h>
6 #include <hash.h>
7 #include <key_io.h>
8 #include <rpc/util.h>
9 #include <script/script.h>
10 #include <util/moneystr.h>
11 #include <wallet/coincontrol.h>
12 #include <wallet/receive.h>
13 #include <wallet/rpc/util.h>
14 #include <wallet/spend.h>
15 #include <wallet/wallet.h>
16
17 #include <univalue.h>
18
19
20 namespace wallet {
21 static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
22 {
23 std::vector<CTxDestination> addresses;
24 if (by_label) {
25 // Get the set of addresses assigned to label
26 addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])});
27 if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet");
28 } else {
29 // Get the address
30 CTxDestination dest = DecodeDestination(params[0].get_str());
31 if (!IsValidDestination(dest)) {
32 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Limenka address");
33 }
34 addresses.emplace_back(dest);
35 }
36
37 // Filter by own scripts only
38 std::set<CScript> output_scripts;
39 for (const auto& address : addresses) {
40 auto output_script{GetScriptForDestination(address)};
41 if (wallet.IsMine(output_script)) {
42 output_scripts.insert(output_script);
43 }
44 }
45
46 if (output_scripts.empty()) {
47 throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
48 }
49
50 // Minimum confirmations
51 int min_depth = 1;
52 if (!params[1].isNull())
53 min_depth = params[1].getInt<int>();
54
55 const bool include_immature_coinbase{params[2].isNull() ? false : params[2].get_bool()};
56
57 // Tally
58 CAmount amount = 0;
59 for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) {
60 const CWalletTx& wtx = wtx_pair.second;
61 int depth{wallet.GetTxDepthInMainChain(wtx)};
62 if (depth < min_depth
63 // Coinbase with less than 1 confirmation is no longer in the main chain
64 || (wtx.IsCoinBase() && (depth < 1))
65 || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
66 {
67 continue;
68 }
69
70 for (const CTxOut& txout : wtx.tx->vout) {
71 if (output_scripts.count(txout.scriptPubKey) > 0) {
72 amount += txout.nValue;
73 }
74 }
75 }
76
77 return amount;
78 }
79
80
81 RPCHelpMan getreceivedbyaddress()
82 {
83 return RPCHelpMan{"getreceivedbyaddress",
84 "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n",
85 {
86 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The limenka address for transactions."},
87 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "Only include transactions confirmed at least this many times."},
88 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
89 },
90 RPCResult{
91 RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received at this address."
92 },
93 RPCExamples{
94 "\nThe amount from transactions with at least 1 confirmation\n"
95 + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
96 "\nThe amount including unconfirmed transactions, zero confirmations\n"
97 + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0") +
98 "\nThe amount with at least 6 confirmations\n"
99 + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6") +
100 "\nThe amount with at least 6 confirmations including immature coinbase outputs\n"
101 + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6 true") +
102 "\nAs a JSON-RPC call\n"
103 + HelpExampleRpc("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 6")
104 },
105 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
106 {
107 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
108 if (!pwallet) return UniValue::VNULL;
109
110 // Make sure the results are valid at least up to the most recent block
111 // the user could have gotten from another RPC command prior to now
112 pwallet->BlockUntilSyncedToCurrentChain();
113
114 LOCK(pwallet->cs_wallet);
115
116 return ValueFromAmount(GetReceived(*pwallet, request.params, /*by_label=*/false));
117 },
118 };
119 }
120
121
122 RPCHelpMan getreceivedbylabel()
123 {
124 return RPCHelpMan{"getreceivedbylabel",
125 "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n",
126 {
127 {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The selected label, may be the default label using \"\"."},
128 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "Only include transactions confirmed at least this many times."},
129 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
130 },
131 RPCResult{
132 RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received for this label."
133 },
134 RPCExamples{
135 "\nAmount received by the default label with at least 1 confirmation\n"
136 + HelpExampleCli("getreceivedbylabel", "\"\"") +
137 "\nAmount received at the tabby label including unconfirmed amounts with zero confirmations\n"
138 + HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") +
139 "\nThe amount with at least 6 confirmations\n"
140 + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") +
141 "\nThe amount with at least 6 confirmations including immature coinbase outputs\n"
142 + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6 true") +
143 "\nAs a JSON-RPC call\n"
144 + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6, true")
145 },
146 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
147 {
148 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
149 if (!pwallet) return UniValue::VNULL;
150
151 // Make sure the results are valid at least up to the most recent block
152 // the user could have gotten from another RPC command prior to now
153 pwallet->BlockUntilSyncedToCurrentChain();
154
155 LOCK(pwallet->cs_wallet);
156
157 return ValueFromAmount(GetReceived(*pwallet, request.params, /*by_label=*/true));
158 },
159 };
160 }
161
162
163 RPCHelpMan getbalance()
164 {
165 return RPCHelpMan{"getbalance",
166 "\nReturns the total available balance.\n"
167 "The available balance is what the wallet considers currently spendable, and is\n"
168 "thus affected by options which limit spendability such as -spendzeroconfchange.\n",
169 {
170 {"dummy|account", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Set to null to only account for trusted transactions, or \"*\" to account for all transactions."},
171 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "Only include incoming transactions confirmed at least this many times. (Requires dummy=\"*\")"},
172 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also include balance in watch-only addresses (see 'importaddress')"},
173 {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."},
174 },
175 RPCResult{
176 RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received for this wallet."
177 },
178 RPCExamples{
179 "\nThe total amount in the wallet with 0 or more confirmations\n"
180 + HelpExampleCli("getbalance", "") +
181 "\nThe total amount in the wallet with at least 6 confirmations\n"
182 + HelpExampleCli("getbalance", "\"*\" 6") +
183 "\nAs a JSON-RPC call\n"
184 + HelpExampleRpc("getbalance", "\"*\", 6")
185 },
186 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
187 {
188 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
189 if (!pwallet) return UniValue::VNULL;
190
191 // Make sure the results are valid at least up to the most recent block
192 // the user could have gotten from another RPC command prior to now
193 pwallet->BlockUntilSyncedToCurrentChain();
194
195 LOCK(pwallet->cs_wallet);
196
197 const auto dummy_value{self.MaybeArg<std::string>("dummy")};
198 if (dummy_value && *dummy_value != "*") {
199 throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\".");
200 }
201
202 const auto min_depth{dummy_value ? self.Arg<int>("minconf") : 0};
203
204 bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
205
206 bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]);
207
208 if (dummy_value) {
209 if (avoid_reuse) throw JSONRPCError(RPC_INVALID_PARAMETER, "getbalance avoid_reuse flag is not supported if dummy is set to \"*\"");
210 isminefilter filter = ISMINE_SPENDABLE;
211 if (include_watchonly) filter = filter | ISMINE_WATCH_ONLY;
212 return ValueFromAmount(pwallet->GetLegacyBalance(filter, min_depth));
213 }
214
215 if (!request.params[1].isNull()) {
216 throw JSONRPCError(RPC_INVALID_PARAMETER, "getbalance minconf option is only currently supported if dummy is set to \"*\"");
217 }
218
219 const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse);
220
221 return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
222 },
223 };
224 }
225
226 RPCHelpMan getunconfirmedbalance()
227 {
228 return RPCHelpMan{"getunconfirmedbalance",
229 "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
230 {},
231 RPCResult{RPCResult::Type::NUM, "", "The balance"},
232 RPCExamples{""},
233 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
234 {
235 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
236 if (!pwallet) return UniValue::VNULL;
237
238 // Make sure the results are valid at least up to the most recent block
239 // the user could have gotten from another RPC command prior to now
240 pwallet->BlockUntilSyncedToCurrentChain();
241
242 LOCK(pwallet->cs_wallet);
243
244 return ValueFromAmount(GetBalance(*pwallet).m_mine_untrusted_pending);
245 },
246 };
247 }
248
249 RPCHelpMan lockunspent()
250 {
251 return RPCHelpMan{"lockunspent",
252 "\nUpdates list of temporarily unspendable outputs.\n"
253 "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n"
254 "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n"
255 "A locked transaction output will not be chosen by automatic coin selection, when spending limenkas.\n"
256 "Manually selected coins are automatically unlocked.\n"
257 "Locks are stored in memory only, unless persistent=true, in which case they will be written to the\n"
258 "wallet database and loaded on node start. Unwritten (persistent=false) locks are always cleared\n"
259 "(by virtue of process exit) when a node stops or fails. Unlocking will clear both persistent and not.\n"
260 "Also see the listunspent call\n",
261 {
262 {"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Whether to unlock (true) or lock (false) the specified transactions"},
263 {"transactions", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The transaction outputs and within each, the txid (string) vout (numeric).",
264 {
265 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
266 {
267 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
268 {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
269 },
270 },
271 },
272 },
273 {"persistent", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to write/erase this lock in the wallet database, or keep the change in memory only. Ignored for unlocking."},
274 },
275 RPCResult{
276 RPCResult::Type::BOOL, "", "Whether the command was successful or not"
277 },
278 RPCExamples{
279 "\nList the unspent transactions\n"
280 + HelpExampleCli("listunspent", "") +
281 "\nLock an unspent transaction\n"
282 + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
283 "\nList the locked transactions\n"
284 + HelpExampleCli("listlockunspent", "") +
285 "\nUnlock the transaction again\n"
286 + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
287 "\nLock the transaction persistently in the wallet database\n"
288 + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\" true") +
289 "\nAs a JSON-RPC call\n"
290 + HelpExampleRpc("lockunspent", "false, [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\",\"vout\":1}]")
291 },
292 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
293 {
294 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
295 if (!pwallet) return UniValue::VNULL;
296
297 // Make sure the results are valid at least up to the most recent block
298 // the user could have gotten from another RPC command prior to now
299 pwallet->BlockUntilSyncedToCurrentChain();
300
301 LOCK(pwallet->cs_wallet);
302
303 bool fUnlock = request.params[0].get_bool();
304
305 const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()};
306
307 if (request.params[1].isNull()) {
308 if (fUnlock) {
309 if (!pwallet->UnlockAllCoins())
310 throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coins failed");
311 }
312 return true;
313 }
314
315 const UniValue& output_params = request.params[1].get_array();
316
317 // Create and validate the COutPoints first.
318
319 std::vector<COutPoint> outputs;
320 outputs.reserve(output_params.size());
321
322 for (unsigned int idx = 0; idx < output_params.size(); idx++) {
323 const UniValue& o = output_params[idx].get_obj();
324
325 RPCTypeCheckObj(o,
326 {
327 {"txid", UniValueType(UniValue::VSTR)},
328 {"vout", UniValueType(UniValue::VNUM)},
329 });
330
331 const Txid txid = Txid::FromUint256(ParseHashO(o, "txid"));
332 const int nOutput = o.find_value("vout").getInt<int>();
333 if (nOutput < 0) {
334 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
335 }
336
337 const COutPoint outpt(txid, nOutput);
338
339 const auto it = pwallet->mapWallet.find(outpt.hash);
340 if (it == pwallet->mapWallet.end()) {
341 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction");
342 }
343
344 const CWalletTx& trans = it->second;
345
346 if (outpt.n >= trans.tx->vout.size()) {
347 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
348 }
349
350 if (pwallet->IsSpent(outpt)) {
351 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
352 }
353
354 const bool is_locked = pwallet->IsLockedCoin(outpt);
355
356 if (fUnlock && !is_locked) {
357 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
358 }
359
360 if (!fUnlock && is_locked && !persistent) {
361 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
362 }
363
364 outputs.push_back(outpt);
365 }
366
367 std::unique_ptr<WalletBatch> batch = nullptr;
368 // Unlock is always persistent
369 if (fUnlock || persistent) batch = std::make_unique<WalletBatch>(pwallet->GetDatabase());
370
371 // Atomically set (un)locked status for the outputs.
372 for (const COutPoint& outpt : outputs) {
373 if (fUnlock) {
374 if (!pwallet->UnlockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
375 } else {
376 if (!pwallet->LockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
377 }
378 }
379
380 return true;
381 },
382 };
383 }
384
385 RPCHelpMan listlockunspent()
386 {
387 return RPCHelpMan{"listlockunspent",
388 "\nReturns list of temporarily unspendable outputs.\n"
389 "See the lockunspent call to lock and unlock transactions for spending.\n",
390 {},
391 RPCResult{
392 RPCResult::Type::ARR, "", "",
393 {
394 {RPCResult::Type::OBJ, "", "",
395 {
396 {RPCResult::Type::STR_HEX, "txid", "The transaction id locked"},
397 {RPCResult::Type::NUM, "vout", "The vout value"},
398 }},
399 }
400 },
401 RPCExamples{
402 "\nList the unspent transactions\n"
403 + HelpExampleCli("listunspent", "") +
404 "\nLock an unspent transaction\n"
405 + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
406 "\nList the locked transactions\n"
407 + HelpExampleCli("listlockunspent", "") +
408 "\nUnlock the transaction again\n"
409 + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
410 "\nAs a JSON-RPC call\n"
411 + HelpExampleRpc("listlockunspent", "")
412 },
413 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
414 {
415 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
416 if (!pwallet) return UniValue::VNULL;
417
418 LOCK(pwallet->cs_wallet);
419
420 std::vector<COutPoint> vOutpts;
421 pwallet->ListLockedCoins(vOutpts);
422
423 UniValue ret(UniValue::VARR);
424
425 for (const COutPoint& outpt : vOutpts) {
426 UniValue o(UniValue::VOBJ);
427
428 o.pushKV("txid", outpt.hash.GetHex());
429 o.pushKV("vout", (int)outpt.n);
430 ret.push_back(std::move(o));
431 }
432
433 return ret;
434 },
435 };
436 }
437
438 RPCHelpMan getbalances()
439 {
440 return RPCHelpMan{
441 "getbalances",
442 "Returns an object with all balances in " + CURRENCY_UNIT + ".\n",
443 {},
444 RPCResult{
445 RPCResult::Type::OBJ, "", "",
446 {
447 {RPCResult::Type::OBJ, "mine", "balances from outputs that the wallet can sign",
448 {
449 {RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"},
450 {RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"},
451 {RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
452 {RPCResult::Type::STR, "precise", "trusted balance in lambda with full 128-bit precision (sub-satoshi fractions and confidential outputs included)"},
453 {RPCResult::Type::STR_AMOUNT, "used", /*optional=*/true, "(only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating)"},
454 }},
455 {RPCResult::Type::OBJ, "watchonly", /*optional=*/true, "watchonly balances (not present if wallet does not watch anything)",
456 {
457 {RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"},
458 {RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"},
459 {RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
460 }},
461 RESULT_LAST_PROCESSED_BLOCK,
462 }
463 },
464 RPCExamples{
465 HelpExampleCli("getbalances", "") +
466 HelpExampleRpc("getbalances", "")},
467 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
468 {
469 const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
470 if (!rpc_wallet) return UniValue::VNULL;
471 const CWallet& wallet = *rpc_wallet;
472
473 // Make sure the results are valid at least up to the most recent block
474 // the user could have gotten from another RPC command prior to now
475 wallet.BlockUntilSyncedToCurrentChain();
476
477 LOCK(wallet.cs_wallet);
478
479 const auto bal = GetBalance(wallet);
480 // Full 128-bit precision (sub-satoshi + confidential receipts) as a
481 // string: JSON numbers cannot carry 26 decimal places.
482 const std::string balance_precise = AttosatsToString(wallet.GetPreciseBalanceAttosats());
483 UniValue balances{UniValue::VOBJ};
484 {
485 UniValue balances_mine{UniValue::VOBJ};
486 balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted));
487 balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending));
488 balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature));
489 balances_mine.pushKV("precise", balance_precise);
490 if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
491 // If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get
492 // the total balance, and then subtract bal to get the reused address balance.
493 const auto full_bal = GetBalance(wallet, 0, false);
494 balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending));
495 }
496 balances.pushKV("mine", std::move(balances_mine));
497 }
498 auto spk_man = wallet.GetLegacyScriptPubKeyMan();
499 if (spk_man && spk_man->HaveWatchOnly()) {
500 UniValue balances_watchonly{UniValue::VOBJ};
501 balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted));
502 balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending));
503 balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
504 balances.pushKV("watchonly", std::move(balances_watchonly));
505 }
506
507 AppendLastProcessedBlock(balances, wallet);
508 return balances;
509 },
510 };
511 }
512
513 RPCHelpMan listunspent()
514 {
515 return RPCHelpMan{
516 "listunspent",
517 "\nReturns array of unspent transaction outputs\n"
518 "with between minconf and maxconf (inclusive) confirmations.\n"
519 "Optionally filter to only include txouts paid to specified addresses.\n",
520 {
521 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum confirmations to filter"},
522 {"maxconf", RPCArg::Type::NUM, RPCArg::Default{9999999}, "The maximum confirmations to filter"},
523 {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The limenka addresses to filter",
524 {
525 {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "limenka address"},
526 },
527 },
528 {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include outputs that are not safe to spend\n"
529 "See description of \"safe\" attribute below."},
530 {"query_options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
531 {
532 {"minimumAmount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)}, "Minimum value of each UTXO in " + CURRENCY_UNIT + ""},
533 {"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
534 {"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"},
535 {"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""},
536 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase UTXOs"}
537 },
538 RPCArgOptions{.oneline_description="query_options"}},
539 },
540 RPCResult{
541 RPCResult::Type::ARR, "", "",
542 {
543 {RPCResult::Type::OBJ, "", "",
544 {
545 {RPCResult::Type::STR_HEX, "txid", "the transaction id"},
546 {RPCResult::Type::NUM, "vout", "the vout value"},
547 {RPCResult::Type::STR, "address", /*optional=*/true, "the limenka address"},
548 {RPCResult::Type::STR, "label", /*optional=*/true, "The associated label, or \"\" for the default label"},
549 {RPCResult::Type::STR, "scriptPubKey", "the output script"},
550 {RPCResult::Type::STR_AMOUNT, "amount", "the transaction output amount in " + CURRENCY_UNIT},
551 {RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
552 {RPCResult::Type::NUM, "ancestorcount", /*optional=*/true, "The number of in-mempool ancestor transactions, including this one (if transaction is in the mempool)"},
553 {RPCResult::Type::NUM, "ancestorsize", /*optional=*/true, "The virtual transaction size of in-mempool ancestors, including this one (if transaction is in the mempool)"},
554 {RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true, "The total fees of in-mempool ancestors (including this one) with fee deltas used for mining priority in " + CURRENCY_ATOM + " (if transaction is in the mempool)"},
555 {RPCResult::Type::STR_HEX, "redeemScript", /*optional=*/true, "The redeem script if the output script is P2SH"},
556 {RPCResult::Type::STR, "witnessScript", /*optional=*/true, "witness script if the output script is P2WSH or P2SH-P2WSH"},
557 {RPCResult::Type::BOOL, "spendable", "Whether we have the private keys to spend this output"},
558 {RPCResult::Type::BOOL, "solvable", "Whether we know how to spend this output, ignoring the lack of keys"},
559 {RPCResult::Type::BOOL, "reused", /*optional=*/true, "(only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)"},
560 {RPCResult::Type::STR, "desc", /*optional=*/true, "(only when solvable) A descriptor for spending this output"},
561 {RPCResult::Type::ARR, "parent_descs", /*optional=*/false, "List of parent descriptors for the output script of this coin.", {
562 {RPCResult::Type::STR, "desc", "The descriptor string."},
563 }},
564 {RPCResult::Type::BOOL, "safe", "Whether this output is considered safe to spend. Unconfirmed transactions\n"
565 "from outside keys and unconfirmed replacement transactions are considered unsafe\n"
566 "and are not eligible for spending by fundrawtransaction and sendtoaddress."},
567 }},
568 }
569 },
570 RPCExamples{
571 HelpExampleCli("listunspent", "")
572 + HelpExampleCli("listunspent", "6 9999999 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"")
573 + HelpExampleRpc("listunspent", "6, 9999999, [\"" + EXAMPLE_ADDRESS[0] + "\",\"" + EXAMPLE_ADDRESS[1] + "\"]")
574 + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'")
575 + HelpExampleRpc("listunspent", "6, 9999999, [], true, { \"minimumAmount\": 0.005 } ")
576 },
577 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
578 {
579 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
580 if (!pwallet) return UniValue::VNULL;
581
582 int nMinDepth = 1;
583 if (!request.params[0].isNull()) {
584 nMinDepth = request.params[0].getInt<int>();
585 }
586
587 int nMaxDepth = 9999999;
588 if (!request.params[1].isNull()) {
589 nMaxDepth = request.params[1].getInt<int>();
590 }
591
592 std::set<CTxDestination> destinations;
593 if (!request.params[2].isNull()) {
594 UniValue inputs = request.params[2].get_array();
595 for (unsigned int idx = 0; idx < inputs.size(); idx++) {
596 const UniValue& input = inputs[idx];
597 CTxDestination dest = DecodeDestination(input.get_str());
598 if (!IsValidDestination(dest)) {
599 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Limenka address: ") + input.get_str());
600 }
601 if (!destinations.insert(dest).second) {
602 throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str());
603 }
604 }
605 }
606
607 bool include_unsafe = true;
608 if (!request.params[3].isNull()) {
609 include_unsafe = request.params[3].get_bool();
610 }
611
612 CoinFilterParams filter_coins;
613 filter_coins.min_amount = 0;
614
615 if (!request.params[4].isNull()) {
616 const UniValue& options = request.params[4].get_obj();
617
618 RPCTypeCheckObj(options,
619 {
620 {"minimumAmount", UniValueType()},
621 {"maximumAmount", UniValueType()},
622 {"minimumSumAmount", UniValueType()},
623 {"maximumCount", UniValueType(UniValue::VNUM)},
624 {"include_immature_coinbase", UniValueType(UniValue::VBOOL)}
625 },
626 true, true);
627
628 if (options.exists("minimumAmount"))
629 filter_coins.min_amount = AmountFromValue(options["minimumAmount"]);
630
631 if (options.exists("maximumAmount"))
632 filter_coins.max_amount = AmountFromValue(options["maximumAmount"]);
633
634 if (options.exists("minimumSumAmount"))
635 filter_coins.min_sum_amount = AmountFromValue(options["minimumSumAmount"]);
636
637 if (options.exists("maximumCount"))
638 filter_coins.max_count = options["maximumCount"].getInt<int64_t>();
639
640 if (options.exists("include_immature_coinbase")) {
641 filter_coins.include_immature_coinbase = options["include_immature_coinbase"].get_bool();
642 }
643 }
644
645 // Make sure the results are valid at least up to the most recent block
646 // the user could have gotten from another RPC command prior to now
647 pwallet->BlockUntilSyncedToCurrentChain();
648
649 UniValue results(UniValue::VARR);
650 std::vector<COutput> vecOutputs;
651 {
652 CCoinControl cctl;
653 cctl.m_avoid_address_reuse = false;
654 cctl.m_min_depth = nMinDepth;
655 cctl.m_max_depth = nMaxDepth;
656 cctl.m_include_unsafe_inputs = include_unsafe;
657 LOCK(pwallet->cs_wallet);
658 vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, filter_coins).All();
659 }
660
661 LOCK(pwallet->cs_wallet);
662
663 const bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
664
665 for (const COutput& out : vecOutputs) {
666 CTxDestination address;
667 const CScript& scriptPubKey = out.txout.scriptPubKey;
668 bool fValidAddress = ExtractDestination(scriptPubKey, address);
669 bool reused = avoid_reuse && pwallet->IsSpentKey(scriptPubKey);
670
671 if (destinations.size() && (!fValidAddress || !destinations.count(address)))
672 continue;
673
674 UniValue entry(UniValue::VOBJ);
675 entry.pushKV("txid", out.outpoint.hash.GetHex());
676 entry.pushKV("vout", (int)out.outpoint.n);
677
678 if (fValidAddress) {
679 entry.pushKV("address", EncodeDestination(address));
680
681 const auto* address_book_entry = pwallet->FindAddressBookEntry(address);
682 if (address_book_entry) {
683 entry.pushKV("label", address_book_entry->GetLabel());
684 }
685
686 std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
687 if (provider) {
688 if (scriptPubKey.IsPayToScriptHash()) {
689 const CScriptID hash = ToScriptID(std::get<ScriptHash>(address));
690 CScript redeemScript;
691 if (provider->GetCScript(hash, redeemScript)) {
692 entry.pushKV("redeemScript", HexStr(redeemScript));
693 // Now check if the redeemScript is actually a P2WSH script
694 CTxDestination witness_destination;
695 if (redeemScript.IsPayToWitnessScriptHash()) {
696 bool extracted = ExtractDestination(redeemScript, witness_destination);
697 CHECK_NONFATAL(extracted);
698 // Also return the witness script
699 const WitnessV0ScriptHash& whash = std::get<WitnessV0ScriptHash>(witness_destination);
700 CScriptID id{RIPEMD160(whash)};
701 CScript witnessScript;
702 if (provider->GetCScript(id, witnessScript)) {
703 entry.pushKV("witnessScript", HexStr(witnessScript));
704 }
705 }
706 }
707 } else if (scriptPubKey.IsPayToWitnessScriptHash()) {
708 const WitnessV0ScriptHash& whash = std::get<WitnessV0ScriptHash>(address);
709 CScriptID id{RIPEMD160(whash)};
710 CScript witnessScript;
711 if (provider->GetCScript(id, witnessScript)) {
712 entry.pushKV("witnessScript", HexStr(witnessScript));
713 }
714 }
715 }
716 }
717
718 entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
719 entry.pushKV("amount", ValueFromAmount(out.txout.nValue));
720 entry.pushKV("confirmations", out.depth);
721 if (!out.depth) {
722 size_t ancestor_count, descendant_count, ancestor_size;
723 CAmount ancestor_fees;
724 pwallet->chain().getTransactionAncestry(out.outpoint.hash, ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
725 if (ancestor_count) {
726 entry.pushKV("ancestorcount", uint64_t(ancestor_count));
727 entry.pushKV("ancestorsize", uint64_t(ancestor_size));
728 entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
729 }
730 }
731 entry.pushKV("spendable", out.spendable);
732 entry.pushKV("solvable", out.solvable);
733 if (out.solvable) {
734 std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
735 if (provider) {
736 auto descriptor = InferDescriptor(scriptPubKey, *provider);
737 entry.pushKV("desc", descriptor->ToString());
738 }
739 }
740 PushParentDescriptors(*pwallet, scriptPubKey, entry);
741 if (avoid_reuse) entry.pushKV("reused", reused);
742 entry.pushKV("safe", out.safe);
743 results.push_back(std::move(entry));
744 }
745
746 return results;
747 },
748 };
749 }
750 } // namespace wallet
751