wallet.cpp raw
1 // Copyright (c) 2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2022 The Limenka developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6 #include <limenka-build-config.h> // IWYU pragma: keep
7
8 #include <core_io.h>
9 #include <key_io.h>
10 #include <rpc/server.h>
11 #include <rpc/util.h>
12 #include <util/translation.h>
13 #include <wallet/context.h>
14 #include <wallet/receive.h>
15 #include <wallet/rpc/wallet.h>
16 #include <wallet/rpc/util.h>
17 #include <wallet/wallet.h>
18 #include <wallet/walletutil.h>
19
20 #include <optional>
21
22 #include <univalue.h>
23
24
25 namespace wallet {
26
27 static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
28 {WALLET_FLAG_AVOID_REUSE,
29 "You need to rescan the blockchain in order to correctly mark used "
30 "destinations in the past. Until this is done, some destinations may "
31 "be considered unused, even if the opposite is the case."},
32 {WALLET_FLAG_EXTERNAL_SIGNER,
33 "Wallet must be unloaded and loaded for change to take effect. "
34 "The ability to toggle this flag may be removed in a future update."},
35 };
36
37 /** Checks if a CKey is in the given CWallet compressed or otherwise*/
38 bool HaveKey(const SigningProvider& wallet, const CKey& key)
39 {
40 CKey key2;
41 key2.Set(key.begin(), key.end(), !key.IsCompressed());
42 return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
43 }
44
45 static RPCHelpMan getwalletinfo()
46 {
47 return RPCHelpMan{"getwalletinfo",
48 "Returns an object containing various wallet state info.\n",
49 {},
50 RPCResult{
51 RPCResult::Type::OBJ, "", "",
52 {
53 {
54 {RPCResult::Type::STR, "walletname", "the wallet name"},
55 {RPCResult::Type::NUM, "walletversion", "the wallet version"},
56 {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
57 {RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
58 {RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
59 {RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
60 {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
61 {RPCResult::Type::NUM_TIME, "keypoololdest", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only."},
62 {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
63 {RPCResult::Type::NUM, "keypoolsize_hd_internal", /*optional=*/true, "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
64 {RPCResult::Type::NUM_TIME, "unlocked_until", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
65 {RPCResult::Type::STR_AMOUNT, "mintxfee", "the minimum transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
66 {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
67 {RPCResult::Type::STR_HEX, "hdseedid", /*optional=*/true, "the Hash160 of the HD seed (only present when HD is enabled)"},
68 {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
69 {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
70 {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
71 {
72 {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
73 {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
74 }, /*skip_type_check=*/true},
75 {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
76 {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
77 {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
78 {RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."},
79 RESULT_LAST_PROCESSED_BLOCK,
80 }},
81 },
82 RPCExamples{
83 HelpExampleCli("getwalletinfo", "")
84 + HelpExampleRpc("getwalletinfo", "")
85 },
86 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
87 {
88 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
89 if (!pwallet) return UniValue::VNULL;
90
91 // Make sure the results are valid at least up to the most recent block
92 // the user could have gotten from another RPC command prior to now
93 pwallet->BlockUntilSyncedToCurrentChain();
94
95 LOCK(pwallet->cs_wallet);
96
97 UniValue obj(UniValue::VOBJ);
98
99 size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
100 const auto bal = GetBalance(*pwallet);
101 obj.pushKV("walletname", pwallet->GetName());
102 obj.pushKV("walletversion", pwallet->GetVersion());
103 obj.pushKV("format", pwallet->GetDatabase().Format());
104 obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
105 obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
106 obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
107 obj.pushKV("txcount", (int)pwallet->mapWallet.size());
108 const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
109 if (kp_oldest.has_value()) {
110 obj.pushKV("keypoololdest", kp_oldest.value());
111 }
112 obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
113
114 LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
115 if (spk_man) {
116 CKeyID seed_id = spk_man->GetHDChain().seed_id;
117 if (!seed_id.IsNull()) {
118 obj.pushKV("hdseedid", seed_id.GetHex());
119 }
120 }
121
122 if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
123 obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
124 }
125 if (pwallet->IsCrypted()) {
126 obj.pushKV("unlocked_until", pwallet->nRelockTime);
127 }
128 obj.pushKV("mintxfee", ValueFromAmount(pwallet->m_min_fee.GetFeePerK()));
129 obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
130 obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
131 obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
132 if (pwallet->IsScanning()) {
133 UniValue scanning(UniValue::VOBJ);
134 scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
135 scanning.pushKV("progress", pwallet->ScanningProgress());
136 obj.pushKV("scanning", std::move(scanning));
137 } else {
138 obj.pushKV("scanning", false);
139 }
140 obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
141 obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
142 obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
143 if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
144 obj.pushKV("birthtime", birthtime);
145 }
146
147 AppendLastProcessedBlock(obj, *pwallet);
148 return obj;
149 },
150 };
151 }
152
153 static RPCHelpMan listwalletdir()
154 {
155 return RPCHelpMan{"listwalletdir",
156 "Returns a list of wallets in the wallet directory.\n",
157 {},
158 RPCResult{
159 RPCResult::Type::OBJ, "", "",
160 {
161 {RPCResult::Type::ARR, "wallets", "",
162 {
163 {RPCResult::Type::OBJ, "", "",
164 {
165 {RPCResult::Type::STR, "name", "The wallet name"},
166 }},
167 }},
168 }
169 },
170 RPCExamples{
171 HelpExampleCli("listwalletdir", "")
172 + HelpExampleRpc("listwalletdir", "")
173 },
174 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
175 {
176 UniValue wallets(UniValue::VARR);
177 for (const auto& [path, _] : ListDatabases(GetWalletDir())) {
178 UniValue wallet(UniValue::VOBJ);
179 wallet.pushKV("name", path.utf8string());
180 wallets.push_back(std::move(wallet));
181 }
182
183 UniValue result(UniValue::VOBJ);
184 result.pushKV("wallets", std::move(wallets));
185 return result;
186 },
187 };
188 }
189
190 static RPCHelpMan listwallets()
191 {
192 return RPCHelpMan{"listwallets",
193 "Returns a list of currently loaded wallets.\n"
194 "For full information on the wallet, use \"getwalletinfo\"\n",
195 {},
196 RPCResult{
197 RPCResult::Type::ARR, "", "",
198 {
199 {RPCResult::Type::STR, "walletname", "the wallet name"},
200 }
201 },
202 RPCExamples{
203 HelpExampleCli("listwallets", "")
204 + HelpExampleRpc("listwallets", "")
205 },
206 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
207 {
208 UniValue obj(UniValue::VARR);
209
210 WalletContext& context = EnsureWalletContext(request.context);
211 for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
212 LOCK(wallet->cs_wallet);
213 obj.push_back(wallet->GetName());
214 }
215
216 return obj;
217 },
218 };
219 }
220
221 static RPCHelpMan loadwallet()
222 {
223 return RPCHelpMan{"loadwallet",
224 "\nLoads a wallet from a wallet file or directory."
225 "\nNote that all wallet command-line options used when starting limenkad will be"
226 "\napplied to the new wallet.\n",
227 {
228 {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the directory of the wallet to be loaded, either absolute or relative to the \"wallets\" directory. The \"wallets\" directory is set by the -walletdir option and defaults to the \"wallets\" folder within the data directory."},
229 {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
230 },
231 RPCResult{
232 RPCResult::Type::OBJ, "", "",
233 {
234 {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
235 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
236 {
237 {RPCResult::Type::STR, "", ""},
238 }},
239 }
240 },
241 RPCExamples{
242 "\nLoad wallet from the wallet dir:\n"
243 + HelpExampleCli("loadwallet", "\"walletname\"")
244 + HelpExampleRpc("loadwallet", "\"walletname\"")
245 + "\nLoad wallet using absolute path (Unix):\n"
246 + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
247 + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
248 + "\nLoad wallet using absolute path (Windows):\n"
249 + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
250 + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
251 },
252 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
253 {
254 WalletContext& context = EnsureWalletContext(request.context);
255 const std::string name(request.params[0].get_str());
256
257 {
258 std::string authorized_wallet_name;
259 const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
260 if (have_wallet_restriction && authorized_wallet_name != name) {
261 throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet usage is restricted.");
262 }
263 }
264
265 DatabaseOptions options;
266 DatabaseStatus status;
267 ReadDatabaseArgs(*context.args, options);
268 options.require_existing = true;
269 bilingual_str error;
270 std::vector<bilingual_str> warnings;
271 std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
272
273 {
274 LOCK(context.wallets_mutex);
275 if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
276 throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
277 }
278 }
279
280 std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
281
282 HandleWalletError(wallet, status, error);
283
284 UniValue obj(UniValue::VOBJ);
285 obj.pushKV("name", wallet->GetName());
286 PushWarnings(warnings, obj);
287
288 return obj;
289 },
290 };
291 }
292
293 static RPCHelpMan setwalletflag()
294 {
295 std::string flags;
296 for (auto& it : WALLET_FLAG_MAP)
297 if (it.second & MUTABLE_WALLET_FLAGS)
298 flags += (flags == "" ? "" : ", ") + it.first;
299
300 return RPCHelpMan{"setwalletflag",
301 "\nChange the state of the given wallet flag for a wallet.\n",
302 {
303 {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
304 {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
305 },
306 RPCResult{
307 RPCResult::Type::OBJ, "", "",
308 {
309 {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
310 {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
311 {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
312 }
313 },
314 RPCExamples{
315 HelpExampleCli("setwalletflag", "avoid_reuse")
316 + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
317 },
318 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
319 {
320 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
321 if (!pwallet) return UniValue::VNULL;
322
323 std::string flag_str = request.params[0].get_str();
324 bool value = request.params[1].isNull() || request.params[1].get_bool();
325
326 if (!WALLET_FLAG_MAP.count(flag_str)) {
327 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
328 }
329
330 auto flag = WALLET_FLAG_MAP.at(flag_str);
331
332 if (flag == WALLET_FLAG_EXTERNAL_SIGNER) {
333 #ifdef ENABLE_EXTERNAL_SIGNER
334 if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) || !pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
335 throw JSONRPCError(RPC_WALLET_ERROR, "This flag can only be set on a watch-only descriptor wallet");
336 }
337 #else
338 throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
339 #endif
340 }
341
342 if (!(flag & MUTABLE_WALLET_FLAGS)) {
343 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
344 }
345
346 UniValue res(UniValue::VOBJ);
347
348 if (pwallet->IsWalletFlagSet(flag) == value) {
349 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
350 }
351
352 res.pushKV("flag_name", flag_str);
353 res.pushKV("flag_state", value);
354
355 if (value) {
356 pwallet->SetWalletFlag(flag);
357 } else {
358 pwallet->UnsetWalletFlag(flag);
359 }
360
361 if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
362 res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
363 }
364
365 return res;
366 },
367 };
368 }
369
370 static RPCHelpMan createwallet()
371 {
372 return RPCHelpMan{
373 "createwallet",
374 "\nCreates and loads a new wallet.\n",
375 {
376 {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
377 {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
378 {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
379 {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
380 {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
381 {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."
382 " Setting to \"false\" will create a legacy wallet"},
383 {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
384 {"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
385 },
386 RPCResult{
387 RPCResult::Type::OBJ, "", "",
388 {
389 {RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."},
390 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
391 {
392 {RPCResult::Type::STR, "", ""},
393 }},
394 }
395 },
396 RPCExamples{
397 HelpExampleCli("createwallet", "\"testwallet\"")
398 + HelpExampleRpc("createwallet", "\"testwallet\"")
399 + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
400 + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
401 },
402 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
403 {
404 {
405 std::string authorized_wallet_name;
406 const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
407 if (have_wallet_restriction && authorized_wallet_name != request.params[0].get_str()) {
408 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet usage is restricted.");
409 }
410 }
411
412 WalletContext& context = EnsureWalletContext(request.context);
413 uint64_t flags = 0;
414 if (!request.params[1].isNull() && request.params[1].get_bool()) {
415 flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
416 }
417
418 if (!request.params[2].isNull() && request.params[2].get_bool()) {
419 flags |= WALLET_FLAG_BLANK_WALLET;
420 }
421 SecureString passphrase;
422 passphrase.reserve(100);
423 std::vector<bilingual_str> warnings;
424 if (!request.params[3].isNull()) {
425 passphrase = std::string_view{request.params[3].get_str()};
426 if (passphrase.empty()) {
427 // Empty string means unencrypted
428 warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
429 }
430 }
431
432 if (!request.params[4].isNull() && request.params[4].get_bool()) {
433 flags |= WALLET_FLAG_AVOID_REUSE;
434 }
435 if (self.Arg<bool>("descriptors")) {
436 #ifndef USE_SQLITE
437 throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
438 #endif
439 flags |= WALLET_FLAG_DESCRIPTORS;
440 }
441 if (!request.params[7].isNull() && request.params[7].get_bool()) {
442 #ifdef ENABLE_EXTERNAL_SIGNER
443 flags |= WALLET_FLAG_EXTERNAL_SIGNER;
444 #else
445 throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
446 #endif
447 }
448
449 #ifndef USE_BDB
450 if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
451 throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
452 }
453 #endif
454
455 DatabaseOptions options;
456 DatabaseStatus status;
457 ReadDatabaseArgs(*context.args, options);
458 options.require_create = true;
459 options.create_flags = flags;
460 options.create_passphrase = passphrase;
461 bilingual_str error;
462 std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
463 const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
464 if (!wallet) {
465 RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
466 throw JSONRPCError(code, error.original);
467 }
468
469 UniValue obj(UniValue::VOBJ);
470 obj.pushKV("name", wallet->GetName());
471 PushWarnings(warnings, obj);
472
473 return obj;
474 },
475 };
476 }
477
478 static RPCHelpMan unloadwallet()
479 {
480 return RPCHelpMan{"unloadwallet",
481 "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n"
482 "If both are specified, they must be identical.",
483 {
484 {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."},
485 {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
486 },
487 RPCResult{RPCResult::Type::OBJ, "", "", {
488 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
489 {
490 {RPCResult::Type::STR, "", ""},
491 }},
492 }},
493 RPCExamples{
494 HelpExampleCli("unloadwallet", "wallet_name")
495 + HelpExampleRpc("unloadwallet", "wallet_name")
496 },
497 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
498 {
499 const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string>("wallet_name"))};
500
501 WalletContext& context = EnsureWalletContext(request.context);
502 std::shared_ptr<CWallet> wallet;
503 {
504 std::string authorized_wallet_name;
505 const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
506 if ((!have_wallet_restriction) || authorized_wallet_name == wallet_name) {
507 wallet = GetWallet(context, wallet_name);
508 }
509 }
510 if (!wallet) {
511 throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
512 }
513
514 std::vector<bilingual_str> warnings;
515 {
516 WalletRescanReserver reserver(*wallet);
517 if (!reserver.reserve()) {
518 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
519 }
520
521 // Release the "main" shared pointer and prevent further notifications.
522 // Note that any attempt to load the same wallet would fail until the wallet
523 // is destroyed (see CheckUniqueFileid).
524 std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
525 if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
526 throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
527 }
528 }
529
530 WaitForDeleteWallet(std::move(wallet));
531
532 UniValue result(UniValue::VOBJ);
533 PushWarnings(warnings, result);
534
535 return result;
536 },
537 };
538 }
539
540 static RPCHelpMan sethdseed()
541 {
542 return RPCHelpMan{"sethdseed",
543 "\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n"
544 "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n"
545 "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + HELP_REQUIRING_PASSPHRASE +
546 "Note: This command is only compatible with legacy wallets.\n",
547 {
548 {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
549 "If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
550 "If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n"
551 "keypool will be used until it has been depleted."},
552 {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n"
553 "The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"},
554 },
555 RPCResult{RPCResult::Type::NONE, "", ""},
556 RPCExamples{
557 HelpExampleCli("sethdseed", "")
558 + HelpExampleCli("sethdseed", "false")
559 + HelpExampleCli("sethdseed", "true \"wifkey\"")
560 + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
561 },
562 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
563 {
564 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
565 if (!pwallet) return UniValue::VNULL;
566
567 LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
568
569 if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
570 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
571 }
572
573 LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
574
575 // Do not do anything to non-HD wallets
576 if (!pwallet->CanSupportFeature(FEATURE_HD)) {
577 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set an HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
578 }
579
580 EnsureWalletIsUnlocked(*pwallet);
581
582 bool flush_key_pool = true;
583 if (!request.params[0].isNull()) {
584 flush_key_pool = request.params[0].get_bool();
585 }
586
587 CPubKey master_pub_key;
588 if (request.params[1].isNull()) {
589 master_pub_key = spk_man.GenerateNewSeed();
590 } else {
591 CKey key = DecodeSecret(request.params[1].get_str());
592 if (!key.IsValid()) {
593 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
594 }
595
596 if (HaveKey(spk_man, key)) {
597 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
598 }
599
600 master_pub_key = spk_man.DeriveNewSeed(key);
601 }
602
603 spk_man.SetHDSeed(master_pub_key);
604 if (flush_key_pool) spk_man.NewKeyPool();
605
606 return UniValue::VNULL;
607 },
608 };
609 }
610
611 static RPCHelpMan upgradewallet()
612 {
613 return RPCHelpMan{"upgradewallet",
614 "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
615 "New keys may be generated and a new wallet backup will need to be made.",
616 {
617 {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
618 },
619 RPCResult{
620 RPCResult::Type::OBJ, "", "",
621 {
622 {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
623 {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
624 {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
625 {RPCResult::Type::STR, "result", /*optional=*/true, "Description of result, if no error"},
626 {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}
627 },
628 },
629 RPCExamples{
630 HelpExampleCli("upgradewallet", "169900")
631 + HelpExampleRpc("upgradewallet", "169900")
632 },
633 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
634 {
635 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
636 if (!pwallet) return UniValue::VNULL;
637
638 EnsureWalletIsUnlocked(*pwallet);
639
640 int version = 0;
641 if (!request.params[0].isNull()) {
642 version = request.params[0].getInt<int>();
643 }
644 bilingual_str error;
645 const int previous_version{pwallet->GetVersion()};
646 const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
647 const int current_version{pwallet->GetVersion()};
648 std::string result;
649
650 if (wallet_upgraded) {
651 if (previous_version == current_version) {
652 result = "Already at latest version. Wallet version unchanged.";
653 } else {
654 result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
655 }
656 }
657
658 UniValue obj(UniValue::VOBJ);
659 obj.pushKV("wallet_name", pwallet->GetName());
660 obj.pushKV("previous_version", previous_version);
661 obj.pushKV("current_version", current_version);
662 if (!result.empty()) {
663 obj.pushKV("result", result);
664 } else {
665 CHECK_NONFATAL(!error.empty());
666 obj.pushKV("error", error.original);
667 }
668 return obj;
669 },
670 };
671 }
672
673 RPCHelpMan simulaterawtransaction()
674 {
675 return RPCHelpMan{"simulaterawtransaction",
676 "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
677 {
678 {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
679 {
680 {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
681 },
682 },
683 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
684 {
685 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"},
686 },
687 },
688 },
689 RPCResult{
690 RPCResult::Type::OBJ, "", "",
691 {
692 {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
693 }
694 },
695 RPCExamples{
696 HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
697 + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
698 },
699 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
700 {
701 const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
702 if (!rpc_wallet) return UniValue::VNULL;
703 const CWallet& wallet = *rpc_wallet;
704
705 LOCK(wallet.cs_wallet);
706
707 UniValue include_watchonly(UniValue::VNULL);
708 if (request.params[1].isObject()) {
709 UniValue options = request.params[1];
710 RPCTypeCheckObj(options,
711 {
712 {"include_watchonly", UniValueType(UniValue::VBOOL)},
713 },
714 true, true);
715
716 include_watchonly = options["include_watchonly"];
717 }
718
719 isminefilter filter = ISMINE_SPENDABLE;
720 if (ParseIncludeWatchonly(include_watchonly, wallet)) {
721 filter |= ISMINE_WATCH_ONLY;
722 }
723
724 const auto& txs = request.params[0].get_array();
725 CAmount changes{0};
726 std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
727 std::set<COutPoint> spent;
728
729 for (size_t i = 0; i < txs.size(); ++i) {
730 CMutableTransaction mtx;
731 if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) {
732 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
733 }
734
735 // Fetch previous transactions (inputs)
736 std::map<COutPoint, Coin> coins;
737 for (const CTxIn& txin : mtx.vin) {
738 coins[txin.prevout]; // Create empty map entry keyed by prevout.
739 }
740 wallet.chain().findCoins(coins);
741
742 // Fetch debit; we are *spending* these; if the transaction is signed and
743 // broadcast, we will lose everything in these
744 for (const auto& txin : mtx.vin) {
745 const auto& outpoint = txin.prevout;
746 if (spent.count(outpoint)) {
747 throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
748 }
749 if (new_utxos.count(outpoint)) {
750 changes -= new_utxos.at(outpoint);
751 new_utxos.erase(outpoint);
752 } else {
753 if (coins.at(outpoint).IsSpent()) {
754 throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
755 }
756 changes -= wallet.GetDebit(txin, filter);
757 }
758 spent.insert(outpoint);
759 }
760
761 // Iterate over outputs; we are *receiving* these, if the wallet considers
762 // them "mine"; if the transaction is signed and broadcast, we will receive
763 // everything in these
764 // Also populate new_utxos in case these are spent in later transactions
765
766 const auto& hash = mtx.GetHash();
767 for (size_t i = 0; i < mtx.vout.size(); ++i) {
768 const auto& txout = mtx.vout[i];
769 bool is_mine = 0 < (wallet.IsMine(txout) & filter);
770 changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
771 }
772 }
773
774 UniValue result(UniValue::VOBJ);
775 result.pushKV("balance_change", ValueFromAmount(changes));
776
777 return result;
778 }
779 };
780 }
781
782 static RPCHelpMan migratewallet()
783 {
784 return RPCHelpMan{"migratewallet",
785 "\nMigrate the wallet to a descriptor wallet.\n"
786 "A new wallet backup will need to be made.\n"
787 "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
788 "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
789 "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
790 "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
791 "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
792 {
793 {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
794 {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
795 },
796 RPCResult{
797 RPCResult::Type::OBJ, "", "",
798 {
799 {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
800 {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
801 {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
802 {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
803 }
804 },
805 RPCExamples{
806 HelpExampleCli("migratewallet", "")
807 + HelpExampleRpc("migratewallet", "")
808 },
809 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
810 {
811 // New wallets do not necessarily have the same name as the migrated wallet
812 EnsureNotWalletRestricted(request);
813
814 const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string>("wallet_name"))};
815
816 SecureString wallet_pass;
817 wallet_pass.reserve(100);
818 if (!request.params[1].isNull()) {
819 wallet_pass = std::string_view{request.params[1].get_str()};
820 }
821
822 WalletContext& context = EnsureWalletContext(request.context);
823 util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
824 if (!res) {
825 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
826 }
827
828 UniValue r{UniValue::VOBJ};
829 r.pushKV("wallet_name", res->wallet_name);
830 if (res->watchonly_wallet) {
831 r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
832 }
833 if (res->solvables_wallet) {
834 r.pushKV("solvables_name", res->solvables_wallet->GetName());
835 }
836 r.pushKV("backup_path", res->backup_path.utf8string());
837
838 return r;
839 },
840 };
841 }
842
843 RPCHelpMan gethdkeys()
844 {
845 return RPCHelpMan{
846 "gethdkeys",
847 "\nList all BIP 32 HD keys in the wallet and which descriptors use them.\n",
848 {
849 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
850 {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
851 {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
852 }},
853 },
854 RPCResult{RPCResult::Type::ARR, "", "", {
855 {
856 {RPCResult::Type::OBJ, "", "", {
857 {RPCResult::Type::STR, "xpub", "The extended public key"},
858 {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
859 {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
860 {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
861 {
862 {RPCResult::Type::OBJ, "", "", {
863 {RPCResult::Type::STR, "desc", "Descriptor string representation"},
864 {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
865 }},
866 }},
867 }},
868 }
869 }},
870 RPCExamples{
871 HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
872 + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
873 },
874 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
875 {
876 const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
877 if (!wallet) return UniValue::VNULL;
878
879 if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
880 throw JSONRPCError(RPC_WALLET_ERROR, "gethdkeys is not available for non-descriptor wallets");
881 }
882
883 LOCK(wallet->cs_wallet);
884
885 UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
886 const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
887 const bool priv{options.exists("private") ? options["private"].get_bool() : false};
888 if (priv) {
889 EnsureWalletIsUnlocked(*wallet);
890 }
891
892
893 std::set<ScriptPubKeyMan*> spkms;
894 if (active_only) {
895 spkms = wallet->GetActiveScriptPubKeyMans();
896 } else {
897 spkms = wallet->GetAllScriptPubKeyMans();
898 }
899
900 std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
901 std::map<CExtPubKey, CExtKey> wallet_xprvs;
902 for (auto* spkm : spkms) {
903 auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
904 CHECK_NONFATAL(desc_spkm);
905 LOCK(desc_spkm->cs_desc_man);
906 WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
907
908 // Retrieve the pubkeys from the descriptor
909 std::set<CPubKey> desc_pubkeys;
910 std::set<CExtPubKey> desc_xpubs;
911 w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
912 for (const CExtPubKey& xpub : desc_xpubs) {
913 std::string desc_str;
914 bool ok = desc_spkm->GetDescriptorString(desc_str, false);
915 CHECK_NONFATAL(ok);
916 wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
917 if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
918 wallet_xprvs[xpub] = CExtKey(xpub, *key);
919 }
920 }
921 }
922
923 UniValue response(UniValue::VARR);
924 for (const auto& [xpub, descs] : wallet_xpubs) {
925 bool has_xprv = false;
926 UniValue descriptors(UniValue::VARR);
927 for (const auto& [desc, active, has_priv] : descs) {
928 UniValue d(UniValue::VOBJ);
929 d.pushKV("desc", desc);
930 d.pushKV("active", active);
931 has_xprv |= has_priv;
932
933 descriptors.push_back(std::move(d));
934 }
935 UniValue xpub_info(UniValue::VOBJ);
936 xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
937 xpub_info.pushKV("has_private", has_xprv);
938 if (priv) {
939 xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
940 }
941 xpub_info.pushKV("descriptors", std::move(descriptors));
942
943 response.push_back(std::move(xpub_info));
944 }
945
946 return response;
947 },
948 };
949 }
950
951 static RPCHelpMan createwalletdescriptor()
952 {
953 return RPCHelpMan{"createwalletdescriptor",
954 "Creates the wallet's descriptor for the given address type. "
955 "The address type must be one that the wallet does not already have a descriptor for."
956 + HELP_REQUIRING_PASSPHRASE,
957 {
958 {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
959 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
960 {"internal", RPCArg::Type::BOOL, RPCArg::DefaultHint{"Both external and internal will be generated unless this parameter is specified"}, "Whether to only make one descriptor that is internal (if parameter is true) or external (if parameter is false)"},
961 {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"The HD key used by all other active descriptors"}, "The HD key that the wallet knows the private key of, listed using 'gethdkeys', to use for this descriptor's key"},
962 }},
963 },
964 RPCResult{
965 RPCResult::Type::OBJ, "", "",
966 {
967 {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
968 {{RPCResult::Type::STR, "", ""}}
969 }
970 },
971 },
972 RPCExamples{
973 HelpExampleCli("createwalletdescriptor", "bech32m")
974 + HelpExampleRpc("createwalletdescriptor", "bech32m")
975 },
976 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
977 {
978 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
979 if (!pwallet) return UniValue::VNULL;
980
981 // Make sure wallet is a descriptor wallet
982 if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
983 throw JSONRPCError(RPC_WALLET_ERROR, "createwalletdescriptor is not available for non-descriptor wallets");
984 }
985
986 std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());
987 if (!output_type) {
988 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
989 }
990
991 UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
992 UniValue internal_only{options["internal"]};
993 UniValue hdkey{options["hdkey"]};
994
995 std::vector<bool> internals;
996 if (internal_only.isNull()) {
997 internals.push_back(false);
998 internals.push_back(true);
999 } else {
1000 internals.push_back(internal_only.get_bool());
1001 }
1002
1003 LOCK(pwallet->cs_wallet);
1004 EnsureWalletIsUnlocked(*pwallet);
1005
1006 CExtPubKey xpub;
1007 if (hdkey.isNull()) {
1008 std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys();
1009 if (active_xpubs.size() != 1) {
1010 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
1011 }
1012 xpub = *active_xpubs.begin();
1013 } else {
1014 xpub = DecodeExtPubKey(hdkey.get_str());
1015 if (!xpub.pubkey.IsValid()) {
1016 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
1017 }
1018 }
1019
1020 std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
1021 if (!key) {
1022 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
1023 }
1024 CExtKey active_hdkey(xpub, *key);
1025
1026 std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
1027 WalletBatch batch{pwallet->GetDatabase()};
1028 for (bool internal : internals) {
1029 WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal);
1030 uint256 w_id = DescriptorID(*w_desc.descriptor);
1031 if (!pwallet->GetScriptPubKeyMan(w_id)) {
1032 spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal));
1033 }
1034 }
1035 if (spkms.empty()) {
1036 throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
1037 }
1038
1039 // Fetch each descspkm from the wallet in order to get the descriptor strings
1040 UniValue descs{UniValue::VARR};
1041 for (const auto& spkm : spkms) {
1042 std::string desc_str;
1043 bool ok = spkm.get().GetDescriptorString(desc_str, false);
1044 CHECK_NONFATAL(ok);
1045 descs.push_back(desc_str);
1046 }
1047 UniValue out{UniValue::VOBJ};
1048 out.pushKV("descs", std::move(descs));
1049 return out;
1050 }
1051 };
1052 }
1053
1054 // addresses
1055 RPCHelpMan getaddressinfo();
1056 RPCHelpMan getnewaddress();
1057 RPCHelpMan getnewstealthaddress();
1058 RPCHelpMan listctreceipts();
1059 RPCHelpMan sendtostealth();
1060 RPCHelpMan mintct();
1061 RPCHelpMan createctpsbt();
1062 RPCHelpMan finalizectpsbt();
1063 RPCHelpMan createstealthpsbt();
1064 RPCHelpMan getrawchangeaddress();
1065 RPCHelpMan setlabel();
1066 RPCHelpMan listaddressgroupings();
1067 RPCHelpMan addmultisigaddress();
1068 RPCHelpMan keypoolrefill();
1069 RPCHelpMan newkeypool();
1070 RPCHelpMan getaddressesbylabel();
1071 RPCHelpMan listlabels();
1072 #ifdef ENABLE_EXTERNAL_SIGNER
1073 RPCHelpMan walletdisplayaddress();
1074 #endif // ENABLE_EXTERNAL_SIGNER
1075
1076 // backup
1077 RPCHelpMan dumpprivkey();
1078 RPCHelpMan dumpmasterprivkey();
1079 RPCHelpMan importprivkey();
1080 RPCHelpMan importaddress();
1081 RPCHelpMan importpubkey();
1082 RPCHelpMan dumpwallet();
1083 RPCHelpMan importwallet();
1084 RPCHelpMan importprunedfunds();
1085 RPCHelpMan removeprunedfunds();
1086 RPCHelpMan importmulti();
1087 RPCHelpMan importdescriptors();
1088 RPCHelpMan listdescriptors();
1089 RPCHelpMan backupwallet();
1090 RPCHelpMan restorewallet();
1091
1092 // coins
1093 RPCHelpMan getreceivedbyaddress();
1094 RPCHelpMan getreceivedbylabel();
1095 RPCHelpMan getbalance();
1096 RPCHelpMan getunconfirmedbalance();
1097 RPCHelpMan lockunspent();
1098 RPCHelpMan listlockunspent();
1099 RPCHelpMan getbalances();
1100 RPCHelpMan listunspent();
1101
1102 // encryption
1103 RPCHelpMan walletpassphrase();
1104 RPCHelpMan walletpassphrasechange();
1105 RPCHelpMan walletlock();
1106 RPCHelpMan encryptwallet();
1107
1108 // spend
1109 RPCHelpMan sendtoaddress();
1110 RPCHelpMan sendmany();
1111 RPCHelpMan setfeerate();
1112 RPCHelpMan settxfee();
1113 RPCHelpMan fundrawtransaction();
1114 RPCHelpMan bumpfee();
1115 RPCHelpMan psbtbumpfee();
1116 RPCHelpMan send();
1117 RPCHelpMan sendall();
1118 RPCHelpMan walletprocesspsbt();
1119 RPCHelpMan walletcreatefundedpsbt();
1120 RPCHelpMan signrawtransactionwithwallet();
1121
1122 // signmessage
1123 RPCHelpMan signmessage();
1124
1125 // transactions
1126 RPCHelpMan listreceivedbyaddress();
1127 RPCHelpMan listreceivedbylabel();
1128 RPCHelpMan listtransactions();
1129 RPCHelpMan listsinceblock();
1130 RPCHelpMan gettransaction();
1131 RPCHelpMan abandontransaction();
1132 RPCHelpMan rescanblockchain();
1133 RPCHelpMan abortrescan();
1134
1135 Span<const CRPCCommand> GetWalletRPCCommands()
1136 {
1137 static const CRPCCommand commands[]{
1138 {"rawtransactions", &fundrawtransaction},
1139 {"wallet", &abandontransaction},
1140 {"wallet", &abortrescan},
1141 {"wallet", &addmultisigaddress},
1142 {"wallet", &backupwallet},
1143 {"wallet", &bumpfee},
1144 {"wallet", &psbtbumpfee},
1145 {"wallet", &createwallet},
1146 {"wallet", &createwalletdescriptor},
1147 {"wallet", &restorewallet},
1148 {"wallet", &dumpprivkey},
1149 {"wallet", &dumpmasterprivkey},
1150 {"wallet", &dumpwallet},
1151 {"wallet", &encryptwallet},
1152 {"wallet", &getaddressesbylabel},
1153 {"wallet", &getaddressinfo},
1154 {"wallet", &getbalance},
1155 {"wallet", &gethdkeys},
1156 {"wallet", &getnewaddress},
1157 {"wallet", &getnewstealthaddress},
1158 {"wallet", &createctpsbt},
1159 {"wallet", &finalizectpsbt},
1160 {"wallet", &createstealthpsbt},
1161 {"wallet", &getrawchangeaddress},
1162 {"wallet", &getreceivedbyaddress},
1163 {"wallet", &getreceivedbylabel},
1164 {"wallet", &gettransaction},
1165 {"wallet", &getunconfirmedbalance},
1166 {"wallet", &getbalances},
1167 {"wallet", &getwalletinfo},
1168 {"wallet", &importaddress},
1169 {"wallet", &importdescriptors},
1170 {"wallet", &importmulti},
1171 {"wallet", &importprivkey},
1172 {"wallet", &importprunedfunds},
1173 {"wallet", &importpubkey},
1174 {"wallet", &importwallet},
1175 {"wallet", &keypoolrefill},
1176 {"wallet", &listaddressgroupings},
1177 {"wallet", &listdescriptors},
1178 {"wallet", &listctreceipts},
1179 {"wallet", &listlabels},
1180 {"wallet", &mintct},
1181 {"wallet", &listlockunspent},
1182 {"wallet", &listreceivedbyaddress},
1183 {"wallet", &listreceivedbylabel},
1184 {"wallet", &listsinceblock},
1185 {"wallet", &listtransactions},
1186 {"wallet", &listunspent},
1187 {"wallet", &listwalletdir},
1188 {"wallet", &listwallets},
1189 {"wallet", &loadwallet},
1190 {"wallet", &lockunspent},
1191 {"wallet", &migratewallet},
1192 {"wallet", &newkeypool},
1193 {"wallet", &removeprunedfunds},
1194 {"wallet", &rescanblockchain},
1195 {"wallet", &send},
1196 {"wallet", &sendmany},
1197 {"wallet", &sendtoaddress},
1198 {"wallet", &sendtostealth},
1199 {"wallet", &sethdseed},
1200 {"wallet", &setlabel},
1201 {"wallet", &setfeerate},
1202 {"wallet", &settxfee},
1203 {"wallet", &setwalletflag},
1204 {"wallet", &signmessage},
1205 {"wallet", &signrawtransactionwithwallet},
1206 {"wallet", &simulaterawtransaction},
1207 {"wallet", &sendall},
1208 {"wallet", &unloadwallet},
1209 {"wallet", &upgradewallet},
1210 {"wallet", &walletcreatefundedpsbt},
1211
1212 #ifdef ENABLE_EXTERNAL_SIGNER
1213 {"wallet", &walletdisplayaddress},
1214 #endif // ENABLE_EXTERNAL_SIGNER
1215 {"wallet", &walletlock},
1216 {"wallet", &walletpassphrase},
1217 {"wallet", &walletpassphrasechange},
1218 {"wallet", &walletprocesspsbt},
1219 };
1220 return commands;
1221 }
1222 } // namespace wallet
1223