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