Line data Source code
1 : // Copyright (c) 2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2022 The Bitcoin Core 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 <core_io.h>
7 : #include <key_io.h>
8 : #include <rpc/server.h>
9 : #include <rpc/util.h>
10 : #include <util/translation.h>
11 : #include <wallet/context.h>
12 : #include <wallet/receive.h>
13 : #include <wallet/rpc/wallet.h>
14 : #include <wallet/rpc/util.h>
15 : #include <wallet/wallet.h>
16 : #include <wallet/walletutil.h>
17 173 :
18 173 : #include <optional>
19 :
20 : #include <univalue.h>
21 :
22 :
23 : namespace wallet {
24 :
25 173 : static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
26 173 : {WALLET_FLAG_AVOID_REUSE,
27 173 : "You need to rescan the blockchain in order to correctly mark used "
28 519 : "destinations in the past. Until this is done, some destinations may "
29 173 : "be considered unused, even if the opposite is the case."},
30 173 : };
31 :
32 : /** Checks if a CKey is in the given CWallet compressed or otherwise*/
33 0 : bool HaveKey(const SigningProvider& wallet, const CKey& key)
34 : {
35 0 : CKey key2;
36 0 : key2.Set(key.begin(), key.end(), !key.IsCompressed());
37 0 : return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
38 0 : }
39 :
40 0 : static RPCHelpMan getwalletinfo()
41 : {
42 0 : return RPCHelpMan{"getwalletinfo",
43 0 : "Returns an object containing various wallet state info.\n",
44 0 : {},
45 0 : RPCResult{
46 0 : RPCResult::Type::OBJ, "", "",
47 0 : {
48 0 : {
49 0 : {RPCResult::Type::STR, "walletname", "the wallet name"},
50 0 : {RPCResult::Type::NUM, "walletversion", "the wallet version"},
51 0 : {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
52 0 : {RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
53 0 : {RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
54 0 : {RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
55 0 : {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
56 0 : {RPCResult::Type::NUM_TIME, "keypoololdest", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only."},
57 0 : {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
58 0 : {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)"},
59 0 : {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)"},
60 0 : {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
61 0 : {RPCResult::Type::STR_HEX, "hdseedid", /*optional=*/true, "the Hash160 of the HD seed (only present when HD is enabled)"},
62 0 : {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
63 0 : {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
64 0 : {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
65 0 : {
66 0 : {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
67 0 : {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
68 : }, /*skip_type_check=*/true},
69 0 : {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
70 0 : {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
71 0 : {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
72 0 : RESULT_LAST_PROCESSED_BLOCK,
73 : }},
74 173 : },
75 0 : RPCExamples{
76 0 : HelpExampleCli("getwalletinfo", "")
77 0 : + HelpExampleRpc("getwalletinfo", "")
78 : },
79 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
80 : {
81 0 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
82 0 : if (!pwallet) return UniValue::VNULL;
83 :
84 : // Make sure the results are valid at least up to the most recent block
85 : // the user could have gotten from another RPC command prior to now
86 0 : pwallet->BlockUntilSyncedToCurrentChain();
87 :
88 0 : LOCK(pwallet->cs_wallet);
89 :
90 0 : UniValue obj(UniValue::VOBJ);
91 173 :
92 0 : size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
93 0 : const auto bal = GetBalance(*pwallet);
94 0 : obj.pushKV("walletname", pwallet->GetName());
95 0 : obj.pushKV("walletversion", pwallet->GetVersion());
96 0 : obj.pushKV("format", pwallet->GetDatabase().Format());
97 0 : obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
98 0 : obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
99 173 : obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
100 0 : obj.pushKV("txcount", (int)pwallet->mapWallet.size());
101 0 : const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
102 0 : if (kp_oldest.has_value()) {
103 0 : obj.pushKV("keypoololdest", kp_oldest.value());
104 0 : }
105 0 : obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
106 :
107 0 : LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
108 0 : if (spk_man) {
109 0 : CKeyID seed_id = spk_man->GetHDChain().seed_id;
110 0 : if (!seed_id.IsNull()) {
111 0 : obj.pushKV("hdseedid", seed_id.GetHex());
112 0 : }
113 0 : }
114 :
115 0 : if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
116 0 : obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
117 0 : }
118 0 : if (pwallet->IsCrypted()) {
119 0 : obj.pushKV("unlocked_until", pwallet->nRelockTime);
120 0 : }
121 0 : obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
122 0 : obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
123 0 : obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
124 0 : if (pwallet->IsScanning()) {
125 0 : UniValue scanning(UniValue::VOBJ);
126 0 : scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
127 0 : scanning.pushKV("progress", pwallet->ScanningProgress());
128 0 : obj.pushKV("scanning", scanning);
129 0 : } else {
130 0 : obj.pushKV("scanning", false);
131 : }
132 0 : obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
133 0 : obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
134 0 : obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
135 :
136 0 : AppendLastProcessedBlock(obj, *pwallet);
137 0 : return obj;
138 0 : },
139 : };
140 0 : }
141 :
142 0 : static RPCHelpMan listwalletdir()
143 : {
144 0 : return RPCHelpMan{"listwalletdir",
145 0 : "Returns a list of wallets in the wallet directory.\n",
146 0 : {},
147 0 : RPCResult{
148 0 : RPCResult::Type::OBJ, "", "",
149 0 : {
150 0 : {RPCResult::Type::ARR, "wallets", "",
151 0 : {
152 0 : {RPCResult::Type::OBJ, "", "",
153 0 : {
154 0 : {RPCResult::Type::STR, "name", "The wallet name"},
155 : }},
156 : }},
157 : }
158 : },
159 0 : RPCExamples{
160 0 : HelpExampleCli("listwalletdir", "")
161 0 : + HelpExampleRpc("listwalletdir", "")
162 : },
163 173 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
164 173 : {
165 173 : UniValue wallets(UniValue::VARR);
166 173 : for (const auto& path : ListDatabases(GetWalletDir())) {
167 173 : UniValue wallet(UniValue::VOBJ);
168 173 : wallet.pushKV("name", path.u8string());
169 173 : wallets.push_back(wallet);
170 173 : }
171 :
172 0 : UniValue result(UniValue::VOBJ);
173 0 : result.pushKV("wallets", wallets);
174 0 : return result;
175 0 : },
176 : };
177 0 : }
178 :
179 0 : static RPCHelpMan listwallets()
180 : {
181 0 : return RPCHelpMan{"listwallets",
182 0 : "Returns a list of currently loaded wallets.\n"
183 : "For full information on the wallet, use \"getwalletinfo\"\n",
184 0 : {},
185 0 : RPCResult{
186 0 : RPCResult::Type::ARR, "", "",
187 0 : {
188 0 : {RPCResult::Type::STR, "walletname", "the wallet name"},
189 : }
190 : },
191 0 : RPCExamples{
192 0 : HelpExampleCli("listwallets", "")
193 0 : + HelpExampleRpc("listwallets", "")
194 : },
195 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
196 : {
197 0 : UniValue obj(UniValue::VARR);
198 :
199 0 : WalletContext& context = EnsureWalletContext(request.context);
200 0 : for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
201 0 : LOCK(wallet->cs_wallet);
202 0 : obj.push_back(wallet->GetName());
203 0 : }
204 :
205 0 : return obj;
206 0 : },
207 : };
208 0 : }
209 :
210 0 : static RPCHelpMan loadwallet()
211 : {
212 0 : return RPCHelpMan{"loadwallet",
213 0 : "\nLoads a wallet from a wallet file or directory."
214 : "\nNote that all wallet command-line options used when starting bitcoind will be"
215 : "\napplied to the new wallet.\n",
216 0 : {
217 0 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
218 0 : {"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."},
219 : },
220 0 : RPCResult{
221 0 : RPCResult::Type::OBJ, "", "",
222 0 : {
223 0 : {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
224 0 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
225 0 : {
226 0 : {RPCResult::Type::STR, "", ""},
227 : }},
228 : }
229 : },
230 0 : RPCExamples{
231 0 : HelpExampleCli("loadwallet", "\"test.dat\"")
232 0 : + HelpExampleRpc("loadwallet", "\"test.dat\"")
233 : },
234 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
235 : {
236 0 : WalletContext& context = EnsureWalletContext(request.context);
237 0 : const std::string name(request.params[0].get_str());
238 :
239 0 : DatabaseOptions options;
240 : DatabaseStatus status;
241 0 : ReadDatabaseArgs(*context.args, options);
242 0 : options.require_existing = true;
243 0 : bilingual_str error;
244 0 : std::vector<bilingual_str> warnings;
245 0 : std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
246 :
247 : {
248 0 : LOCK(context.wallets_mutex);
249 0 : if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
250 0 : throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
251 : }
252 0 : }
253 :
254 0 : std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
255 :
256 0 : HandleWalletError(wallet, status, error);
257 :
258 0 : UniValue obj(UniValue::VOBJ);
259 0 : obj.pushKV("name", wallet->GetName());
260 0 : PushWarnings(warnings, obj);
261 :
262 0 : return obj;
263 0 : },
264 : };
265 0 : }
266 :
267 0 : static RPCHelpMan setwalletflag()
268 : {
269 0 : std::string flags;
270 173 : for (auto& it : WALLET_FLAG_MAP)
271 0 : if (it.second & MUTABLE_WALLET_FLAGS)
272 0 : flags += (flags == "" ? "" : ", ") + it.first;
273 :
274 0 : return RPCHelpMan{"setwalletflag",
275 0 : "\nChange the state of the given wallet flag for a wallet.\n",
276 0 : {
277 0 : {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
278 0 : {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
279 : },
280 0 : RPCResult{
281 0 : RPCResult::Type::OBJ, "", "",
282 0 : {
283 0 : {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
284 0 : {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
285 0 : {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
286 : }
287 : },
288 0 : RPCExamples{
289 0 : HelpExampleCli("setwalletflag", "avoid_reuse")
290 0 : + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
291 : },
292 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
293 : {
294 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
295 0 : if (!pwallet) return UniValue::VNULL;
296 :
297 0 : std::string flag_str = request.params[0].get_str();
298 0 : bool value = request.params[1].isNull() || request.params[1].get_bool();
299 :
300 0 : if (!WALLET_FLAG_MAP.count(flag_str)) {
301 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
302 : }
303 :
304 0 : auto flag = WALLET_FLAG_MAP.at(flag_str);
305 :
306 0 : if (!(flag & MUTABLE_WALLET_FLAGS)) {
307 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
308 : }
309 :
310 0 : UniValue res(UniValue::VOBJ);
311 :
312 0 : if (pwallet->IsWalletFlagSet(flag) == value) {
313 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
314 : }
315 :
316 0 : res.pushKV("flag_name", flag_str);
317 0 : res.pushKV("flag_state", value);
318 :
319 0 : if (value) {
320 0 : pwallet->SetWalletFlag(flag);
321 0 : } else {
322 0 : pwallet->UnsetWalletFlag(flag);
323 : }
324 :
325 0 : if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
326 0 : res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
327 0 : }
328 :
329 0 : return res;
330 0 : },
331 : };
332 0 : }
333 :
334 0 : static RPCHelpMan createwallet()
335 : {
336 0 : return RPCHelpMan{
337 0 : "createwallet",
338 0 : "\nCreates and loads a new wallet.\n",
339 0 : {
340 0 : {"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."},
341 0 : {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
342 0 : {"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."},
343 0 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
344 0 : {"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."},
345 0 : {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."
346 : " Setting to \"false\" will create a legacy wallet; however, the legacy wallet type is being deprecated and"
347 : " support for creating and opening legacy wallets will be removed in the future."},
348 0 : {"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."},
349 0 : {"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."},
350 : },
351 0 : RPCResult{
352 0 : RPCResult::Type::OBJ, "", "",
353 0 : {
354 0 : {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."},
355 0 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
356 0 : {
357 0 : {RPCResult::Type::STR, "", ""},
358 : }},
359 : }
360 : },
361 0 : RPCExamples{
362 0 : HelpExampleCli("createwallet", "\"testwallet\"")
363 0 : + HelpExampleRpc("createwallet", "\"testwallet\"")
364 0 : + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
365 0 : + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
366 : },
367 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
368 : {
369 0 : WalletContext& context = EnsureWalletContext(request.context);
370 0 : uint64_t flags = 0;
371 0 : if (!request.params[1].isNull() && request.params[1].get_bool()) {
372 0 : flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
373 0 : }
374 :
375 0 : if (!request.params[2].isNull() && request.params[2].get_bool()) {
376 0 : flags |= WALLET_FLAG_BLANK_WALLET;
377 0 : }
378 0 : SecureString passphrase;
379 0 : passphrase.reserve(100);
380 0 : std::vector<bilingual_str> warnings;
381 0 : if (!request.params[3].isNull()) {
382 0 : passphrase = std::string_view{request.params[3].get_str()};
383 0 : if (passphrase.empty()) {
384 : // Empty string means unencrypted
385 0 : warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
386 0 : }
387 0 : }
388 :
389 0 : if (!request.params[4].isNull() && request.params[4].get_bool()) {
390 0 : flags |= WALLET_FLAG_AVOID_REUSE;
391 0 : }
392 0 : if (request.params[5].isNull() || request.params[5].get_bool()) {
393 : #ifndef USE_SQLITE
394 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
395 : #endif
396 0 : flags |= WALLET_FLAG_DESCRIPTORS;
397 0 : }
398 0 : if (!request.params[7].isNull() && request.params[7].get_bool()) {
399 : #ifdef ENABLE_EXTERNAL_SIGNER
400 : flags |= WALLET_FLAG_EXTERNAL_SIGNER;
401 : #else
402 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
403 : #endif
404 : }
405 :
406 : #ifndef USE_BDB
407 0 : if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
408 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
409 : }
410 : #endif
411 :
412 0 : DatabaseOptions options;
413 : DatabaseStatus status;
414 0 : ReadDatabaseArgs(*context.args, options);
415 0 : options.require_create = true;
416 0 : options.create_flags = flags;
417 0 : options.create_passphrase = passphrase;
418 0 : bilingual_str error;
419 0 : std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
420 0 : const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
421 0 : if (!wallet) {
422 0 : RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
423 0 : throw JSONRPCError(code, error.original);
424 : }
425 :
426 0 : UniValue obj(UniValue::VOBJ);
427 0 : obj.pushKV("name", wallet->GetName());
428 0 : PushWarnings(warnings, obj);
429 :
430 0 : return obj;
431 0 : },
432 : };
433 0 : }
434 :
435 0 : static RPCHelpMan unloadwallet()
436 : {
437 0 : return RPCHelpMan{"unloadwallet",
438 0 : "Unloads the wallet referenced by the request endpoint, otherwise unloads the wallet specified in the argument.\n"
439 : "Specifying the wallet name on a wallet endpoint is invalid.",
440 0 : {
441 0 : {"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."},
442 0 : {"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."},
443 : },
444 0 : RPCResult{RPCResult::Type::OBJ, "", "", {
445 0 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
446 0 : {
447 0 : {RPCResult::Type::STR, "", ""},
448 : }},
449 : }},
450 0 : RPCExamples{
451 0 : HelpExampleCli("unloadwallet", "wallet_name")
452 0 : + HelpExampleRpc("unloadwallet", "wallet_name")
453 : },
454 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
455 : {
456 0 : std::string wallet_name;
457 0 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
458 0 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
459 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
460 : }
461 0 : } else {
462 0 : wallet_name = request.params[0].get_str();
463 : }
464 :
465 0 : WalletContext& context = EnsureWalletContext(request.context);
466 0 : std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
467 0 : if (!wallet) {
468 0 : throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
469 : }
470 :
471 0 : std::vector<bilingual_str> warnings;
472 : {
473 0 : WalletRescanReserver reserver(*wallet);
474 0 : if (!reserver.reserve()) {
475 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
476 : }
477 :
478 : // Release the "main" shared pointer and prevent further notifications.
479 : // Note that any attempt to load the same wallet would fail until the wallet
480 : // is destroyed (see CheckUniqueFileid).
481 0 : std::optional<bool> load_on_start{self.MaybeArg<bool>(1)};
482 0 : if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
483 0 : throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
484 : }
485 0 : }
486 :
487 0 : UnloadWallet(std::move(wallet));
488 :
489 0 : UniValue result(UniValue::VOBJ);
490 0 : PushWarnings(warnings, result);
491 :
492 0 : return result;
493 0 : },
494 : };
495 0 : }
496 :
497 0 : static RPCHelpMan sethdseed()
498 : {
499 0 : return RPCHelpMan{"sethdseed",
500 : "\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"
501 : "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n"
502 0 : "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + HELP_REQUIRING_PASSPHRASE +
503 : "Note: This command is only compatible with legacy wallets.\n",
504 0 : {
505 0 : {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
506 : "If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
507 : "If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n"
508 : "keypool will be used until it has been depleted."},
509 0 : {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n"
510 : "The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"},
511 : },
512 0 : RPCResult{RPCResult::Type::NONE, "", ""},
513 0 : RPCExamples{
514 0 : HelpExampleCli("sethdseed", "")
515 0 : + HelpExampleCli("sethdseed", "false")
516 0 : + HelpExampleCli("sethdseed", "true \"wifkey\"")
517 0 : + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
518 : },
519 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
520 : {
521 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
522 0 : if (!pwallet) return UniValue::VNULL;
523 :
524 0 : LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
525 :
526 0 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
527 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
528 : }
529 :
530 0 : LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
531 :
532 : // Do not do anything to non-HD wallets
533 0 : if (!pwallet->CanSupportFeature(FEATURE_HD)) {
534 0 : 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");
535 : }
536 :
537 0 : EnsureWalletIsUnlocked(*pwallet);
538 :
539 0 : bool flush_key_pool = true;
540 0 : if (!request.params[0].isNull()) {
541 0 : flush_key_pool = request.params[0].get_bool();
542 0 : }
543 :
544 0 : CPubKey master_pub_key;
545 0 : if (request.params[1].isNull()) {
546 0 : master_pub_key = spk_man.GenerateNewSeed();
547 0 : } else {
548 0 : CKey key = DecodeSecret(request.params[1].get_str());
549 0 : if (!key.IsValid()) {
550 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
551 : }
552 :
553 0 : if (HaveKey(spk_man, key)) {
554 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
555 : }
556 :
557 0 : master_pub_key = spk_man.DeriveNewSeed(key);
558 0 : }
559 :
560 0 : spk_man.SetHDSeed(master_pub_key);
561 0 : if (flush_key_pool) spk_man.NewKeyPool();
562 :
563 0 : return UniValue::VNULL;
564 0 : },
565 : };
566 0 : }
567 :
568 0 : static RPCHelpMan upgradewallet()
569 : {
570 0 : return RPCHelpMan{"upgradewallet",
571 0 : "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
572 : "New keys may be generated and a new wallet backup will need to be made.",
573 0 : {
574 0 : {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
575 : },
576 0 : RPCResult{
577 0 : RPCResult::Type::OBJ, "", "",
578 0 : {
579 0 : {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
580 0 : {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
581 0 : {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
582 0 : {RPCResult::Type::STR, "result", /*optional=*/true, "Description of result, if no error"},
583 0 : {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}
584 : },
585 : },
586 0 : RPCExamples{
587 0 : HelpExampleCli("upgradewallet", "169900")
588 0 : + HelpExampleRpc("upgradewallet", "169900")
589 : },
590 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
591 : {
592 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
593 0 : if (!pwallet) return UniValue::VNULL;
594 :
595 0 : EnsureWalletIsUnlocked(*pwallet);
596 :
597 0 : int version = 0;
598 0 : if (!request.params[0].isNull()) {
599 0 : version = request.params[0].getInt<int>();
600 0 : }
601 0 : bilingual_str error;
602 0 : const int previous_version{pwallet->GetVersion()};
603 0 : const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
604 0 : const int current_version{pwallet->GetVersion()};
605 0 : std::string result;
606 :
607 0 : if (wallet_upgraded) {
608 0 : if (previous_version == current_version) {
609 0 : result = "Already at latest version. Wallet version unchanged.";
610 0 : } else {
611 0 : result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
612 : }
613 0 : }
614 :
615 0 : UniValue obj(UniValue::VOBJ);
616 0 : obj.pushKV("wallet_name", pwallet->GetName());
617 0 : obj.pushKV("previous_version", previous_version);
618 0 : obj.pushKV("current_version", current_version);
619 0 : if (!result.empty()) {
620 0 : obj.pushKV("result", result);
621 0 : } else {
622 0 : CHECK_NONFATAL(!error.empty());
623 0 : obj.pushKV("error", error.original);
624 : }
625 0 : return obj;
626 0 : },
627 : };
628 0 : }
629 :
630 0 : RPCHelpMan simulaterawtransaction()
631 : {
632 0 : return RPCHelpMan{"simulaterawtransaction",
633 0 : "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
634 0 : {
635 0 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
636 0 : {
637 0 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
638 : },
639 : },
640 0 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
641 0 : {
642 0 : {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"},
643 : },
644 : },
645 : },
646 0 : RPCResult{
647 0 : RPCResult::Type::OBJ, "", "",
648 0 : {
649 0 : {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
650 : }
651 : },
652 0 : RPCExamples{
653 0 : HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
654 0 : + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
655 : },
656 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
657 : {
658 0 : const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
659 0 : if (!rpc_wallet) return UniValue::VNULL;
660 0 : const CWallet& wallet = *rpc_wallet;
661 :
662 0 : LOCK(wallet.cs_wallet);
663 :
664 0 : UniValue include_watchonly(UniValue::VNULL);
665 0 : if (request.params[1].isObject()) {
666 0 : UniValue options = request.params[1];
667 0 : RPCTypeCheckObj(options,
668 0 : {
669 0 : {"include_watchonly", UniValueType(UniValue::VBOOL)},
670 : },
671 : true, true);
672 :
673 0 : include_watchonly = options["include_watchonly"];
674 0 : }
675 :
676 0 : isminefilter filter = ISMINE_SPENDABLE;
677 0 : if (ParseIncludeWatchonly(include_watchonly, wallet)) {
678 0 : filter |= ISMINE_WATCH_ONLY;
679 0 : }
680 :
681 0 : const auto& txs = request.params[0].get_array();
682 0 : CAmount changes{0};
683 0 : std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
684 0 : std::set<COutPoint> spent;
685 :
686 0 : for (size_t i = 0; i < txs.size(); ++i) {
687 0 : CMutableTransaction mtx;
688 0 : if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) {
689 0 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
690 : }
691 :
692 : // Fetch previous transactions (inputs)
693 0 : std::map<COutPoint, Coin> coins;
694 0 : for (const CTxIn& txin : mtx.vin) {
695 0 : coins[txin.prevout]; // Create empty map entry keyed by prevout.
696 : }
697 0 : wallet.chain().findCoins(coins);
698 :
699 : // Fetch debit; we are *spending* these; if the transaction is signed and
700 : // broadcast, we will lose everything in these
701 0 : for (const auto& txin : mtx.vin) {
702 0 : const auto& outpoint = txin.prevout;
703 0 : if (spent.count(outpoint)) {
704 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
705 : }
706 0 : if (new_utxos.count(outpoint)) {
707 0 : changes -= new_utxos.at(outpoint);
708 0 : new_utxos.erase(outpoint);
709 0 : } else {
710 0 : if (coins.at(outpoint).IsSpent()) {
711 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
712 : }
713 0 : changes -= wallet.GetDebit(txin, filter);
714 : }
715 0 : spent.insert(outpoint);
716 : }
717 :
718 : // Iterate over outputs; we are *receiving* these, if the wallet considers
719 : // them "mine"; if the transaction is signed and broadcast, we will receive
720 : // everything in these
721 : // Also populate new_utxos in case these are spent in later transactions
722 :
723 0 : const auto& hash = mtx.GetHash();
724 0 : for (size_t i = 0; i < mtx.vout.size(); ++i) {
725 0 : const auto& txout = mtx.vout[i];
726 0 : bool is_mine = 0 < (wallet.IsMine(txout) & filter);
727 0 : changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
728 0 : }
729 0 : }
730 :
731 0 : UniValue result(UniValue::VOBJ);
732 0 : result.pushKV("balance_change", ValueFromAmount(changes));
733 :
734 0 : return result;
735 0 : }
736 : };
737 0 : }
738 :
739 0 : static RPCHelpMan migratewallet()
740 : {
741 0 : return RPCHelpMan{"migratewallet",
742 0 : "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n"
743 : "\nMigrate the wallet to a descriptor wallet.\n"
744 : "A new wallet backup will need to be made.\n"
745 : "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
746 : "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
747 : "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
748 : "\nEncrypted wallets must have the passphrase provided as an argument to this call.",
749 0 : {
750 0 : {"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."},
751 0 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
752 : },
753 0 : RPCResult{
754 0 : RPCResult::Type::OBJ, "", "",
755 0 : {
756 0 : {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
757 0 : {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
758 0 : {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
759 0 : {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
760 : }
761 : },
762 0 : RPCExamples{
763 0 : HelpExampleCli("migratewallet", "")
764 0 : + HelpExampleRpc("migratewallet", "")
765 : },
766 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
767 : {
768 0 : std::string wallet_name;
769 0 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
770 0 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
771 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
772 : }
773 0 : } else {
774 0 : if (request.params[0].isNull()) {
775 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Either RPC endpoint wallet or wallet_name parameter must be provided");
776 : }
777 0 : wallet_name = request.params[0].get_str();
778 : }
779 :
780 0 : SecureString wallet_pass;
781 0 : wallet_pass.reserve(100);
782 0 : if (!request.params[1].isNull()) {
783 0 : wallet_pass = std::string_view{request.params[1].get_str()};
784 0 : }
785 :
786 0 : WalletContext& context = EnsureWalletContext(request.context);
787 0 : util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
788 0 : if (!res) {
789 0 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
790 : }
791 :
792 0 : UniValue r{UniValue::VOBJ};
793 0 : r.pushKV("wallet_name", res->wallet_name);
794 0 : if (res->watchonly_wallet) {
795 0 : r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
796 0 : }
797 0 : if (res->solvables_wallet) {
798 0 : r.pushKV("solvables_name", res->solvables_wallet->GetName());
799 0 : }
800 0 : r.pushKV("backup_path", res->backup_path.u8string());
801 :
802 0 : return r;
803 0 : },
804 : };
805 0 : }
806 :
807 : // addresses
808 : RPCHelpMan getaddressinfo();
809 : RPCHelpMan getnewaddress();
810 : RPCHelpMan getrawchangeaddress();
811 : RPCHelpMan setlabel();
812 : RPCHelpMan listaddressgroupings();
813 : RPCHelpMan addmultisigaddress();
814 : RPCHelpMan keypoolrefill();
815 : RPCHelpMan newkeypool();
816 : RPCHelpMan getaddressesbylabel();
817 : RPCHelpMan listlabels();
818 : #ifdef ENABLE_EXTERNAL_SIGNER
819 : RPCHelpMan walletdisplayaddress();
820 : #endif // ENABLE_EXTERNAL_SIGNER
821 :
822 : // backup
823 : RPCHelpMan dumpprivkey();
824 : RPCHelpMan importprivkey();
825 : RPCHelpMan importaddress();
826 : RPCHelpMan importpubkey();
827 : RPCHelpMan dumpwallet();
828 : RPCHelpMan importwallet();
829 : RPCHelpMan importprunedfunds();
830 : RPCHelpMan removeprunedfunds();
831 : RPCHelpMan importmulti();
832 : RPCHelpMan importdescriptors();
833 : RPCHelpMan listdescriptors();
834 : RPCHelpMan backupwallet();
835 : RPCHelpMan restorewallet();
836 :
837 : // coins
838 : RPCHelpMan getreceivedbyaddress();
839 : RPCHelpMan getreceivedbylabel();
840 : RPCHelpMan getbalance();
841 : RPCHelpMan getunconfirmedbalance();
842 : RPCHelpMan lockunspent();
843 : RPCHelpMan listlockunspent();
844 : RPCHelpMan getbalances();
845 : RPCHelpMan listunspent();
846 :
847 : // encryption
848 : RPCHelpMan walletpassphrase();
849 : RPCHelpMan walletpassphrasechange();
850 : RPCHelpMan walletlock();
851 : RPCHelpMan encryptwallet();
852 :
853 : // spend
854 : RPCHelpMan sendtoaddress();
855 : RPCHelpMan sendmany();
856 : RPCHelpMan settxfee();
857 : RPCHelpMan fundrawtransaction();
858 : RPCHelpMan bumpfee();
859 : RPCHelpMan psbtbumpfee();
860 : RPCHelpMan send();
861 : RPCHelpMan sendall();
862 : RPCHelpMan walletprocesspsbt();
863 : RPCHelpMan walletcreatefundedpsbt();
864 : RPCHelpMan signrawtransactionwithwallet();
865 :
866 : // signmessage
867 : RPCHelpMan signmessage();
868 :
869 : // transactions
870 : RPCHelpMan listreceivedbyaddress();
871 : RPCHelpMan listreceivedbylabel();
872 : RPCHelpMan listtransactions();
873 : RPCHelpMan listsinceblock();
874 : RPCHelpMan gettransaction();
875 : RPCHelpMan abandontransaction();
876 : RPCHelpMan rescanblockchain();
877 : RPCHelpMan abortrescan();
878 :
879 0 : Span<const CRPCCommand> GetWalletRPCCommands()
880 : {
881 0 : static const CRPCCommand commands[]{
882 0 : {"rawtransactions", &fundrawtransaction},
883 0 : {"wallet", &abandontransaction},
884 0 : {"wallet", &abortrescan},
885 0 : {"wallet", &addmultisigaddress},
886 0 : {"wallet", &backupwallet},
887 0 : {"wallet", &bumpfee},
888 0 : {"wallet", &psbtbumpfee},
889 0 : {"wallet", &createwallet},
890 0 : {"wallet", &restorewallet},
891 0 : {"wallet", &dumpprivkey},
892 0 : {"wallet", &dumpwallet},
893 0 : {"wallet", &encryptwallet},
894 0 : {"wallet", &getaddressesbylabel},
895 0 : {"wallet", &getaddressinfo},
896 0 : {"wallet", &getbalance},
897 0 : {"wallet", &getnewaddress},
898 0 : {"wallet", &getrawchangeaddress},
899 0 : {"wallet", &getreceivedbyaddress},
900 0 : {"wallet", &getreceivedbylabel},
901 0 : {"wallet", &gettransaction},
902 0 : {"wallet", &getunconfirmedbalance},
903 0 : {"wallet", &getbalances},
904 0 : {"wallet", &getwalletinfo},
905 0 : {"wallet", &importaddress},
906 0 : {"wallet", &importdescriptors},
907 0 : {"wallet", &importmulti},
908 0 : {"wallet", &importprivkey},
909 0 : {"wallet", &importprunedfunds},
910 0 : {"wallet", &importpubkey},
911 0 : {"wallet", &importwallet},
912 0 : {"wallet", &keypoolrefill},
913 0 : {"wallet", &listaddressgroupings},
914 0 : {"wallet", &listdescriptors},
915 0 : {"wallet", &listlabels},
916 0 : {"wallet", &listlockunspent},
917 0 : {"wallet", &listreceivedbyaddress},
918 0 : {"wallet", &listreceivedbylabel},
919 0 : {"wallet", &listsinceblock},
920 0 : {"wallet", &listtransactions},
921 0 : {"wallet", &listunspent},
922 0 : {"wallet", &listwalletdir},
923 0 : {"wallet", &listwallets},
924 0 : {"wallet", &loadwallet},
925 0 : {"wallet", &lockunspent},
926 0 : {"wallet", &migratewallet},
927 0 : {"wallet", &newkeypool},
928 0 : {"wallet", &removeprunedfunds},
929 0 : {"wallet", &rescanblockchain},
930 0 : {"wallet", &send},
931 0 : {"wallet", &sendmany},
932 0 : {"wallet", &sendtoaddress},
933 0 : {"wallet", &sethdseed},
934 0 : {"wallet", &setlabel},
935 0 : {"wallet", &settxfee},
936 0 : {"wallet", &setwalletflag},
937 0 : {"wallet", &signmessage},
938 0 : {"wallet", &signrawtransactionwithwallet},
939 0 : {"wallet", &simulaterawtransaction},
940 0 : {"wallet", &sendall},
941 0 : {"wallet", &unloadwallet},
942 0 : {"wallet", &upgradewallet},
943 0 : {"wallet", &walletcreatefundedpsbt},
944 : #ifdef ENABLE_EXTERNAL_SIGNER
945 : {"wallet", &walletdisplayaddress},
946 : #endif // ENABLE_EXTERNAL_SIGNER
947 0 : {"wallet", &walletlock},
948 0 : {"wallet", &walletpassphrase},
949 0 : {"wallet", &walletpassphrasechange},
950 0 : {"wallet", &walletprocesspsbt},
951 : };
952 0 : return commands;
953 0 : }
954 : } // namespace wallet
|