wallet.cpp raw

   1  // Copyright (c) 2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-present 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 <bitcoin-build-config.h> // IWYU pragma: keep
   7  
   8  #include <wallet/rpc/wallet.h>
   9  
  10  #include <coins.h>
  11  #include <core_io.h>
  12  #include <key_io.h>
  13  #include <rpc/server.h>
  14  #include <rpc/util.h>
  15  #include <univalue.h>
  16  #include <util/translation.h>
  17  #include <wallet/context.h>
  18  #include <wallet/export.h>
  19  #include <wallet/receive.h>
  20  #include <wallet/rpc/util.h>
  21  #include <wallet/wallet.h>
  22  #include <wallet/walletutil.h>
  23  
  24  #include <optional>
  25  #include <string_view>
  26  
  27  
  28  namespace wallet {
  29  
  30  static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
  31      {WALLET_FLAG_AVOID_REUSE,
  32       "You need to rescan the blockchain in order to correctly mark used "
  33       "destinations in the past. Until this is done, some destinations may "
  34       "be considered unused, even if the opposite is the case."},
  35  };
  36  
  37  static RPCMethod getwalletinfo()
  38  {
  39      return RPCMethod{"getwalletinfo",
  40                  "Returns an object containing various wallet state info.\n",
  41                  {},
  42                  RPCResult{
  43                      RPCResult::Type::OBJ, "", "",
  44                      {
  45                          {
  46                          {RPCResult::Type::STR, "walletname", "the wallet name"},
  47                          {RPCResult::Type::NUM, "walletversion", "(DEPRECATED) only related to unsupported legacy wallet, returns the latest version 169900 for backwards compatibility"},
  48                          {RPCResult::Type::STR, "format", "the database format (only sqlite)"},
  49                          {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
  50                          {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
  51                          {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)"},
  52                          {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)"},
  53                          {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
  54                          {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
  55                          {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
  56                          {
  57                              {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
  58                              {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
  59                          }, {.skip_type_check=true}, },
  60                          {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
  61                          {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
  62                          {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
  63                          {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."},
  64                          {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet",
  65                          {
  66                              {RPCResult::Type::STR, "flag", "The name of the flag"},
  67                          }},
  68                          RESULT_LAST_PROCESSED_BLOCK,
  69                      }},
  70                  },
  71                  RPCExamples{
  72                      HelpExampleCli("getwalletinfo", "")
  73              + HelpExampleRpc("getwalletinfo", "")
  74                  },
  75          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
  76  {
  77      const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
  78      if (!pwallet) return UniValue::VNULL;
  79  
  80      // Make sure the results are valid at least up to the most recent block
  81      // the user could have gotten from another RPC command prior to now
  82      pwallet->BlockUntilSyncedToCurrentChain();
  83  
  84      LOCK(pwallet->cs_wallet);
  85  
  86      UniValue obj(UniValue::VOBJ);
  87  
  88      const int latest_legacy_wallet_minversion{169900};
  89  
  90      size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
  91      obj.pushKV("walletname", pwallet->GetName());
  92      obj.pushKV("walletversion", latest_legacy_wallet_minversion);
  93      obj.pushKV("format", pwallet->GetDatabase().Format());
  94      obj.pushKV("txcount", pwallet->mapWallet.size());
  95      obj.pushKV("keypoolsize", kpExternalSize);
  96      obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
  97  
  98      if (pwallet->HasEncryptionKeys()) {
  99          obj.pushKV("unlocked_until", pwallet->nRelockTime);
 100      }
 101      obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
 102      obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
 103      if (pwallet->IsScanning()) {
 104          UniValue scanning(UniValue::VOBJ);
 105          scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
 106          scanning.pushKV("progress", pwallet->ScanningProgress());
 107          obj.pushKV("scanning", std::move(scanning));
 108      } else {
 109          obj.pushKV("scanning", false);
 110      }
 111      obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
 112      obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
 113      obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
 114      if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
 115          obj.pushKV("birthtime", birthtime);
 116      }
 117  
 118      // Push known flags
 119      UniValue flags(UniValue::VARR);
 120      uint64_t wallet_flags = pwallet->GetWalletFlags();
 121      for (uint64_t i = 0; i < 64; ++i) {
 122          uint64_t flag = uint64_t{1} << i;
 123          if (flag & wallet_flags) {
 124              if (flag & KNOWN_WALLET_FLAGS) {
 125                  flags.push_back(WALLET_FLAG_TO_STRING.at(WalletFlags{flag}));
 126              } else {
 127                  flags.push_back(strprintf("unknown_flag_%u", i));
 128              }
 129          }
 130      }
 131      obj.pushKV("flags", flags);
 132  
 133      AppendLastProcessedBlock(obj, *pwallet);
 134      return obj;
 135  },
 136      };
 137  }
 138  
 139  static RPCMethod listwalletdir()
 140  {
 141      return RPCMethod{"listwalletdir",
 142                  "Returns a list of wallets in the wallet directory.\n",
 143                  {},
 144                  RPCResult{
 145                      RPCResult::Type::OBJ, "", "",
 146                      {
 147                          {RPCResult::Type::ARR, "wallets", "",
 148                          {
 149                              {RPCResult::Type::OBJ, "", "",
 150                              {
 151                                  {RPCResult::Type::STR, "name", "The wallet name"},
 152                                  {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
 153                                  {
 154                                      {RPCResult::Type::STR, "", ""},
 155                                  }},
 156                              }},
 157                          }},
 158                      }
 159                  },
 160                  RPCExamples{
 161                      HelpExampleCli("listwalletdir", "")
 162              + HelpExampleRpc("listwalletdir", "")
 163                  },
 164          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 165  {
 166      UniValue wallets(UniValue::VARR);
 167      for (const auto& [path, db_type] : ListDatabases(GetWalletDir())) {
 168          UniValue wallet(UniValue::VOBJ);
 169          wallet.pushKV("name", path.utf8string());
 170                  UniValue warnings(UniValue::VARR);
 171          if (db_type == "bdb") {
 172              warnings.push_back("This wallet is a legacy wallet and will need to be migrated with migratewallet before it can be loaded");
 173          }
 174          wallet.pushKV("warnings", warnings);
 175          wallets.push_back(std::move(wallet));
 176      }
 177  
 178      UniValue result(UniValue::VOBJ);
 179      result.pushKV("wallets", std::move(wallets));
 180      return result;
 181  },
 182      };
 183  }
 184  
 185  static RPCMethod listwallets()
 186  {
 187      return RPCMethod{"listwallets",
 188                  "Returns a list of currently loaded wallets.\n"
 189                  "For full information on the wallet, use \"getwalletinfo\"\n",
 190                  {},
 191                  RPCResult{
 192                      RPCResult::Type::ARR, "", "",
 193                      {
 194                          {RPCResult::Type::STR, "walletname", "the wallet name"},
 195                      }
 196                  },
 197                  RPCExamples{
 198                      HelpExampleCli("listwallets", "")
 199              + HelpExampleRpc("listwallets", "")
 200                  },
 201          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 202  {
 203      UniValue obj(UniValue::VARR);
 204  
 205      WalletContext& context = EnsureWalletContext(request.context);
 206      for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
 207          LOCK(wallet->cs_wallet);
 208          obj.push_back(wallet->GetName());
 209      }
 210  
 211      return obj;
 212  },
 213      };
 214  }
 215  
 216  static RPCMethod loadwallet()
 217  {
 218      return RPCMethod{
 219          "loadwallet",
 220          "Loads a wallet from a wallet file or directory."
 221                  "\nNote that all wallet command-line options used when starting bitcoind will be"
 222                  "\napplied to the new wallet.\n",
 223                  {
 224                      {"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."},
 225                      {"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."},
 226                  },
 227                  RPCResult{
 228                      RPCResult::Type::OBJ, "", "",
 229                      {
 230                          {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
 231                          {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
 232                          {
 233                              {RPCResult::Type::STR, "", ""},
 234                          }},
 235                      }
 236                  },
 237                  RPCExamples{
 238                      "\nLoad wallet from the wallet dir:\n"
 239                      + HelpExampleCli("loadwallet", "\"walletname\"")
 240                      + HelpExampleRpc("loadwallet", "\"walletname\"")
 241                      + "\nLoad wallet using absolute path (Unix):\n"
 242                      + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
 243                      + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
 244                      + "\nLoad wallet using absolute path (Windows):\n"
 245                      + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
 246                      + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
 247                  },
 248          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 249  {
 250      WalletContext& context = EnsureWalletContext(request.context);
 251      const std::string name(request.params[0].get_str());
 252  
 253      DatabaseOptions options;
 254      DatabaseStatus status;
 255      ReadDatabaseArgs(*context.args, options);
 256      options.require_existing = true;
 257      bilingual_str error;
 258      std::vector<bilingual_str> warnings;
 259      std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
 260  
 261      {
 262          LOCK(context.wallets_mutex);
 263          if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
 264              throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
 265          }
 266      }
 267  
 268      std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
 269  
 270      HandleWalletError(wallet, status, error);
 271  
 272      UniValue obj(UniValue::VOBJ);
 273      obj.pushKV("name", wallet->GetName());
 274      PushWarnings(warnings, obj);
 275  
 276      return obj;
 277  },
 278      };
 279  }
 280  
 281  static RPCMethod setwalletflag()
 282  {
 283              std::string flags;
 284              for (auto& it : STRING_TO_WALLET_FLAG)
 285                  if (it.second & MUTABLE_WALLET_FLAGS)
 286                      flags += (flags == "" ? "" : ", ") + it.first;
 287  
 288      return RPCMethod{
 289          "setwalletflag",
 290          "Change the state of the given wallet flag for a wallet.\n",
 291                  {
 292                      {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
 293                      {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
 294                  },
 295                  RPCResult{
 296                      RPCResult::Type::OBJ, "", "",
 297                      {
 298                          {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
 299                          {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
 300                          {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
 301                      }
 302                  },
 303                  RPCExamples{
 304                      HelpExampleCli("setwalletflag", "avoid_reuse")
 305                    + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
 306                  },
 307          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 308  {
 309      std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
 310      if (!pwallet) return UniValue::VNULL;
 311  
 312      std::string flag_str = request.params[0].get_str();
 313      bool value = request.params[1].isNull() || request.params[1].get_bool();
 314  
 315      if (!STRING_TO_WALLET_FLAG.contains(flag_str)) {
 316          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
 317      }
 318  
 319      auto flag = STRING_TO_WALLET_FLAG.at(flag_str);
 320  
 321      if (!(flag & MUTABLE_WALLET_FLAGS)) {
 322          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
 323      }
 324  
 325      UniValue res(UniValue::VOBJ);
 326  
 327      if (pwallet->IsWalletFlagSet(flag) == value) {
 328          throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
 329      }
 330  
 331      res.pushKV("flag_name", flag_str);
 332      res.pushKV("flag_state", value);
 333  
 334      if (value) {
 335          pwallet->SetWalletFlag(flag);
 336      } else {
 337          pwallet->UnsetWalletFlag(flag);
 338      }
 339  
 340      if (flag && value && WALLET_FLAG_CAVEATS.contains(flag)) {
 341          res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
 342      }
 343  
 344      return res;
 345  },
 346      };
 347  }
 348  
 349  static RPCMethod createwallet()
 350  {
 351      return RPCMethod{
 352          "createwallet",
 353          "Creates and loads a new wallet.\n",
 354          {
 355              {"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."},
 356              {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
 357              {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys."},
 358              {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
 359              {"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."},
 360              {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""},
 361              {"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."},
 362              {"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."},
 363          },
 364          RPCResult{
 365              RPCResult::Type::OBJ, "", "",
 366              {
 367                  {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."},
 368                  {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
 369                  {
 370                      {RPCResult::Type::STR, "", ""},
 371                  }},
 372              }
 373          },
 374          RPCExamples{
 375              HelpExampleCli("createwallet", "\"testwallet\"")
 376              + HelpExampleRpc("createwallet", "\"testwallet\"")
 377              + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
 378              + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
 379          },
 380          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 381  {
 382      WalletContext& context = EnsureWalletContext(request.context);
 383      uint64_t flags = 0;
 384      if (!request.params[1].isNull() && request.params[1].get_bool()) {
 385          flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
 386      }
 387  
 388      if (!request.params[2].isNull() && request.params[2].get_bool()) {
 389          flags |= WALLET_FLAG_BLANK_WALLET;
 390      }
 391      SecureString passphrase;
 392      passphrase.reserve(100);
 393      std::vector<bilingual_str> warnings;
 394      if (!request.params[3].isNull()) {
 395          passphrase = std::string_view{request.params[3].get_str()};
 396          if (passphrase.empty()) {
 397              // Empty string means unencrypted
 398              warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
 399          }
 400      }
 401  
 402      if (!request.params[4].isNull() && request.params[4].get_bool()) {
 403          flags |= WALLET_FLAG_AVOID_REUSE;
 404      }
 405      flags |= WALLET_FLAG_DESCRIPTORS;
 406      if (!self.Arg<bool>("descriptors")) {
 407          throw JSONRPCError(RPC_WALLET_ERROR, "descriptors argument must be set to \"true\"; it is no longer possible to create a legacy wallet.");
 408      }
 409      if (!request.params[7].isNull() && request.params[7].get_bool()) {
 410  #ifdef ENABLE_EXTERNAL_SIGNER
 411          flags |= WALLET_FLAG_EXTERNAL_SIGNER;
 412  #else
 413          throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
 414  #endif
 415      }
 416  
 417      DatabaseOptions options;
 418      DatabaseStatus status;
 419      ReadDatabaseArgs(*context.args, options);
 420      options.require_create = true;
 421      options.create_flags = flags;
 422      options.create_passphrase = passphrase;
 423      bilingual_str error;
 424      std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
 425      const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
 426      HandleWalletError(wallet, status, error);
 427  
 428      UniValue obj(UniValue::VOBJ);
 429      obj.pushKV("name", wallet->GetName());
 430      PushWarnings(warnings, obj);
 431  
 432      return obj;
 433  },
 434      };
 435  }
 436  
 437  static RPCMethod unloadwallet()
 438  {
 439      return RPCMethod{"unloadwallet",
 440                  "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n"
 441                  "If both are specified, they must be identical.",
 442                  {
 443                      {"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."},
 444                      {"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."},
 445                  },
 446                  RPCResult{RPCResult::Type::OBJ, "", "", {
 447                      {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
 448                      {
 449                          {RPCResult::Type::STR, "", ""},
 450                      }},
 451                  }},
 452                  RPCExamples{
 453                      HelpExampleCli("unloadwallet", "wallet_name")
 454              + HelpExampleRpc("unloadwallet", "wallet_name")
 455                  },
 456          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 457  {
 458      const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
 459  
 460      WalletContext& context = EnsureWalletContext(request.context);
 461      std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
 462      if (!wallet) {
 463          throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
 464      }
 465  
 466      std::vector<bilingual_str> warnings;
 467      {
 468          WalletRescanReserver reserver(*wallet);
 469          if (!reserver.reserve()) {
 470              throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
 471          }
 472  
 473          // Release the "main" shared pointer and prevent further notifications.
 474          // Note that any attempt to load the same wallet would fail until the wallet
 475          // is destroyed (see CheckUniqueFileid).
 476          std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
 477          if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
 478              throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
 479          }
 480      }
 481  
 482      WaitForDeleteWallet(std::move(wallet));
 483  
 484      UniValue result(UniValue::VOBJ);
 485      PushWarnings(warnings, result);
 486  
 487      return result;
 488  },
 489      };
 490  }
 491  
 492  RPCMethod simulaterawtransaction()
 493  {
 494      return RPCMethod{
 495          "simulaterawtransaction",
 496          "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
 497          {
 498              {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
 499                  {
 500                      {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
 501                  },
 502              },
 503              {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
 504                  {
 505                      {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
 506                  },
 507              },
 508          },
 509          RPCResult{
 510              RPCResult::Type::OBJ, "", "",
 511              {
 512                  {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
 513              }
 514          },
 515          RPCExamples{
 516              HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
 517              + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
 518          },
 519      [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 520  {
 521      const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
 522      if (!rpc_wallet) return UniValue::VNULL;
 523      const CWallet& wallet = *rpc_wallet;
 524  
 525      LOCK(wallet.cs_wallet);
 526  
 527      const auto& txs = request.params[0].get_array();
 528      CAmount changes{0};
 529      std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
 530      std::set<COutPoint> spent;
 531  
 532      for (size_t i = 0; i < txs.size(); ++i) {
 533          CMutableTransaction mtx;
 534          if (!DecodeHexTx(mtx, txs[i].get_str(), /*try_no_witness=*/ true, /*try_witness=*/ true)) {
 535              throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
 536          }
 537  
 538          // Fetch previous transactions (inputs)
 539          std::map<COutPoint, Coin> coins;
 540          for (const CTxIn& txin : mtx.vin) {
 541              coins[txin.prevout]; // Create empty map entry keyed by prevout.
 542          }
 543          wallet.chain().findCoins(coins);
 544  
 545          // Fetch debit; we are *spending* these; if the transaction is signed and
 546          // broadcast, we will lose everything in these
 547          for (const auto& txin : mtx.vin) {
 548              const auto& outpoint = txin.prevout;
 549              if (spent.contains(outpoint)) {
 550                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
 551              }
 552              if (new_utxos.contains(outpoint)) {
 553                  changes -= new_utxos.at(outpoint);
 554                  new_utxos.erase(outpoint);
 555              } else {
 556                  if (coins.at(outpoint).IsSpent()) {
 557                      throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
 558                  }
 559                  changes -= wallet.GetDebit(txin);
 560              }
 561              spent.insert(outpoint);
 562          }
 563  
 564          // Iterate over outputs; we are *receiving* these, if the wallet considers
 565          // them "mine"; if the transaction is signed and broadcast, we will receive
 566          // everything in these
 567          // Also populate new_utxos in case these are spent in later transactions
 568  
 569          const auto& hash = mtx.GetHash();
 570          for (size_t i = 0; i < mtx.vout.size(); ++i) {
 571              const auto& txout = mtx.vout[i];
 572              bool is_mine = wallet.IsMine(txout);
 573              changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
 574          }
 575      }
 576  
 577      UniValue result(UniValue::VOBJ);
 578      result.pushKV("balance_change", ValueFromAmount(changes));
 579  
 580      return result;
 581  }
 582      };
 583  }
 584  
 585  static RPCMethod migratewallet()
 586  {
 587      return RPCMethod{
 588          "migratewallet",
 589          "Migrate the wallet to a descriptor wallet.\n"
 590          "A new wallet backup will need to be made.\n"
 591          "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
 592          "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
 593          "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
 594          "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
 595          "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
 596          {
 597              {"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."},
 598              {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
 599              {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration."},
 600          },
 601          RPCResult{
 602              RPCResult::Type::OBJ, "", "",
 603              {
 604                  {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
 605                  {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
 606                  {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
 607                  {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
 608              }
 609          },
 610          RPCExamples{
 611              HelpExampleCli("migratewallet", "")
 612              + HelpExampleRpc("migratewallet", "")
 613          },
 614          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 615          {
 616              const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
 617  
 618              SecureString wallet_pass;
 619              wallet_pass.reserve(100);
 620              if (!request.params[1].isNull()) {
 621                  wallet_pass = std::string_view{request.params[1].get_str()};
 622              }
 623  
 624              const bool loadwallet = self.Arg<bool>("load_wallet");
 625  
 626              WalletContext& context = EnsureWalletContext(request.context);
 627              util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context, loadwallet);
 628              if (!res) {
 629                  throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
 630              }
 631  
 632              UniValue r{UniValue::VOBJ};
 633              r.pushKV("wallet_name", res->wallet_name);
 634              if (res->watchonly_wallet_name.has_value()) {
 635                  r.pushKV("watchonly_name", res->watchonly_wallet_name.value());
 636              }
 637              if (res->solvables_wallet_name.has_value()) {
 638                  r.pushKV("solvables_name", res->solvables_wallet_name.value());
 639              }
 640              r.pushKV("backup_path", res->backup_path.utf8string());
 641  
 642              return r;
 643          },
 644      };
 645  }
 646  
 647  RPCMethod gethdkeys()
 648  {
 649      return RPCMethod{
 650          "gethdkeys",
 651          "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
 652          {
 653              {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
 654                  {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
 655                  {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
 656              }},
 657          },
 658          RPCResult{RPCResult::Type::ARR, "", "", {
 659              {
 660                  {RPCResult::Type::OBJ, "", "", {
 661                      {RPCResult::Type::STR, "xpub", "The extended public key"},
 662                      {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
 663                      {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
 664                      {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
 665                      {
 666                          {RPCResult::Type::OBJ, "", "", {
 667                              {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
 668                              {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
 669                          }},
 670                      }},
 671                  }},
 672              }
 673          }},
 674          RPCExamples{
 675              HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
 676              + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
 677          },
 678          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 679          {
 680              const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
 681              if (!wallet) return UniValue::VNULL;
 682  
 683              LOCK(wallet->cs_wallet);
 684  
 685              UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
 686              const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
 687              const bool priv{options.exists("private") ? options["private"].get_bool() : false};
 688              if (priv) {
 689                  EnsureWalletIsUnlocked(*wallet);
 690              }
 691  
 692  
 693              std::set<ScriptPubKeyMan*> spkms;
 694              if (active_only) {
 695                  spkms = wallet->GetActiveScriptPubKeyMans();
 696              } else {
 697                  spkms = wallet->GetAllScriptPubKeyMans();
 698              }
 699  
 700              std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
 701              std::map<CExtPubKey, CExtKey> wallet_xprvs;
 702              for (auto* spkm : spkms) {
 703                  auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
 704                  CHECK_NONFATAL(desc_spkm);
 705                  LOCK(desc_spkm->cs_desc_man);
 706                  WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
 707  
 708                  // Retrieve the pubkeys from the descriptor
 709                  std::set<CPubKey> desc_pubkeys;
 710                  std::set<CExtPubKey> desc_xpubs;
 711                  w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
 712                  for (const CExtPubKey& xpub : desc_xpubs) {
 713                      std::string desc_str;
 714                      bool ok = desc_spkm->GetDescriptorString(desc_str, /*priv=*/false);
 715                      CHECK_NONFATAL(ok);
 716                      wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
 717                      if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
 718                          wallet_xprvs[xpub] = CExtKey(xpub, *key);
 719                      }
 720                  }
 721              }
 722  
 723              UniValue response(UniValue::VARR);
 724              for (const auto& [xpub, descs] : wallet_xpubs) {
 725                  bool has_xprv = false;
 726                  UniValue descriptors(UniValue::VARR);
 727                  for (const auto& [desc, active, has_priv] : descs) {
 728                      UniValue d(UniValue::VOBJ);
 729                      d.pushKV("desc", desc);
 730                      d.pushKV("active", active);
 731                      has_xprv |= has_priv;
 732  
 733                      descriptors.push_back(std::move(d));
 734                  }
 735                  UniValue xpub_info(UniValue::VOBJ);
 736                  xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
 737                  xpub_info.pushKV("has_private", has_xprv);
 738                  if (priv && has_xprv) {
 739                      xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
 740                  }
 741                  xpub_info.pushKV("descriptors", std::move(descriptors));
 742  
 743                  response.push_back(std::move(xpub_info));
 744              }
 745  
 746              return response;
 747          },
 748      };
 749  }
 750  
 751  static RPCMethod createwalletdescriptor()
 752  {
 753      return RPCMethod{"createwalletdescriptor",
 754          "Creates the wallet's descriptor for the given address type. "
 755          "The address type must be one that the wallet does not already have a descriptor for."
 756          + HELP_REQUIRING_PASSPHRASE,
 757          {
 758              {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
 759              {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
 760                  {"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)"},
 761                  {"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"},
 762              }},
 763          },
 764          RPCResult{
 765              RPCResult::Type::OBJ, "", "",
 766              {
 767                  {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
 768                      {{RPCResult::Type::STR, "", ""}}
 769                  }
 770              },
 771          },
 772          RPCExamples{
 773              HelpExampleCli("createwalletdescriptor", "bech32m")
 774              + HelpExampleRpc("createwalletdescriptor", "bech32m")
 775          },
 776          [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 777          {
 778              std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
 779              if (!pwallet) return UniValue::VNULL;
 780  
 781              std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());
 782              if (!output_type) {
 783                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
 784              }
 785  
 786              UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
 787              UniValue internal_only{options["internal"]};
 788              UniValue hdkey{options["hdkey"]};
 789  
 790              std::vector<bool> internals;
 791              if (internal_only.isNull()) {
 792                  internals.push_back(false);
 793                  internals.push_back(true);
 794              } else {
 795                  internals.push_back(internal_only.get_bool());
 796              }
 797  
 798              LOCK(pwallet->cs_wallet);
 799              EnsureWalletIsUnlocked(*pwallet);
 800  
 801              CExtPubKey xpub;
 802              if (hdkey.isNull()) {
 803                  std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys();
 804                  if (active_xpubs.size() != 1) {
 805                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
 806                  }
 807                  xpub = *active_xpubs.begin();
 808              } else {
 809                  xpub = DecodeExtPubKey(hdkey.get_str());
 810                  if (!xpub.pubkey.IsValid()) {
 811                      throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
 812                  }
 813              }
 814  
 815              std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
 816              if (!key) {
 817                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
 818              }
 819              CExtKey active_hdkey(xpub, *key);
 820  
 821              std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
 822              WalletBatch batch{pwallet->GetDatabase()};
 823              for (bool internal : internals) {
 824                  WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal);
 825                  uint256 w_id = DescriptorID(*w_desc.descriptor);
 826                  if (!pwallet->GetScriptPubKeyMan(w_id)) {
 827                      spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal));
 828                  }
 829              }
 830              if (spkms.empty()) {
 831                  throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
 832              }
 833  
 834              // Fetch each descspkm from the wallet in order to get the descriptor strings
 835              UniValue descs{UniValue::VARR};
 836              for (const auto& spkm : spkms) {
 837                  std::string desc_str;
 838                  bool ok = spkm.get().GetDescriptorString(desc_str, false);
 839                  CHECK_NONFATAL(ok);
 840                  descs.push_back(desc_str);
 841              }
 842              UniValue out{UniValue::VOBJ};
 843              out.pushKV("descs", std::move(descs));
 844              return out;
 845          }
 846      };
 847  }
 848  
 849  RPCMethod addhdkey()
 850  {
 851      return RPCMethod{
 852          "addhdkey",
 853          "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
 854          {
 855              {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"Automatically generated new key"}, "The BIP 32 extended private key to add. If none is provided, a randomly generated one will be added."},
 856          },
 857          RPCResult{
 858              RPCResult::Type::OBJ, "", "",
 859              {
 860                  {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
 861              },
 862          },
 863          RPCExamples{
 864              HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
 865          },
 866          [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 867          {
 868              std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
 869              if (!wallet) return UniValue::VNULL;
 870  
 871              if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
 872                  throw JSONRPCError(RPC_WALLET_ERROR, "addhdkey is not available for wallets without private keys");
 873              }
 874  
 875              EnsureWalletIsUnlocked(*wallet);
 876  
 877              CExtKey hdkey;
 878              if (request.params[0].isNull()) {
 879                  CKey seed_key = GenerateRandomKey();
 880                  hdkey.SetSeed(seed_key);
 881              } else {
 882                  hdkey = DecodeExtKey(request.params[0].get_str());
 883                  if (!hdkey.key.IsValid()) {
 884                      // Check if the user gave us an xpub and give a more descriptive error if so
 885                      CExtPubKey xpub = DecodeExtPubKey(request.params[0].get_str());
 886                      if (xpub.pubkey.IsValid()) {
 887                          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extended public key (xpub) provided, but extended private key (xprv) is required");
 888                      } else {
 889                          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not parse HD key");
 890                      }
 891                  }
 892              }
 893  
 894              LOCK(wallet->cs_wallet);
 895              std::string desc_str = "unused(" + EncodeExtKey(hdkey) + ")";
 896              FlatSigningProvider keys;
 897              std::string error;
 898              std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_str, keys, error, false);
 899              CHECK_NONFATAL(!descs.empty());
 900              WalletDescriptor w_desc(std::move(descs.at(0)), GetTime(), 0, 0, 0);
 901              if (wallet->GetDescriptorScriptPubKeyMan(w_desc) != nullptr) {
 902                  throw JSONRPCError(RPC_WALLET_ERROR, "HD key already exists");
 903              }
 904  
 905              auto spkm = wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", /*internal=*/false);
 906              if (!spkm) {
 907                  throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(spkm).original);
 908              }
 909  
 910              UniValue response(UniValue::VOBJ);
 911              const DescriptorScriptPubKeyMan& desc_spkm = spkm->get();
 912              LOCK(desc_spkm.cs_desc_man);
 913              std::set<CPubKey> pubkeys;
 914              std::set<CExtPubKey> extpubs;
 915              desc_spkm.GetWalletDescriptor().descriptor->GetPubKeys(pubkeys, extpubs);
 916              CHECK_NONFATAL(pubkeys.size() == 0);
 917              CHECK_NONFATAL(extpubs.size() == 1);
 918              response.pushKV("xpub", EncodeExtPubKey(*extpubs.begin()));
 919  
 920              return response;
 921          },
 922      };
 923  }
 924  
 925  static RPCMethod exportwatchonlywallet()
 926  {
 927      return RPCMethod{"exportwatchonlywallet",
 928          "Creates a wallet file at the specified destination containing a watchonly version "
 929          "of the current wallet. This watchonly wallet contains the wallet's public descriptors, "
 930          "its transactions, and address book data. Descriptors that use hardened derivation will "
 931          "only have a limited number of derived keys included in the export due to hardened "
 932          "derivation requiring private keys. Descriptors with unhardened derivation do not have "
 933          "this limitation. The watchonly wallet can be imported into another node using 'restorewallet'.",
 934          {
 935              {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the filename the exported watchonly wallet will be saved to"},
 936          },
 937          RPCResult{
 938              RPCResult::Type::OBJ, "", "",
 939              {
 940                  {RPCResult::Type::STR, "exported_file", "The full path that the file has been exported to"},
 941              },
 942          },
 943          RPCExamples{
 944              HelpExampleCli("exportwatchonlywallet", "\"/path/to/export.dat\"")
 945              + HelpExampleRpc("exportwatchonlywallet", "\"/path/to/export.dat\"")
 946          },
 947          [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 948          {
 949              std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
 950              if (!pwallet) return UniValue::VNULL;
 951              WalletContext& context = EnsureWalletContext(request.context);
 952  
 953              std::string dest = request.params[0].get_str();
 954  
 955              LOCK(pwallet->cs_wallet);
 956              pwallet->TopUpKeyPool();
 957              util::Result<std::string> exported = ExportWatchOnlyWallet(*pwallet, fs::PathFromString(dest), context);
 958              if (!exported) {
 959                  throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(exported).original);
 960              }
 961              UniValue out{UniValue::VOBJ};
 962              out.pushKV("exported_file", *exported);
 963              return out;
 964          }
 965      };
 966  }
 967  
 968  // addresses
 969  RPCMethod getaddressinfo();
 970  RPCMethod getnewaddress();
 971  RPCMethod getrawchangeaddress();
 972  RPCMethod setlabel();
 973  RPCMethod listaddressgroupings();
 974  RPCMethod keypoolrefill();
 975  RPCMethod getaddressesbylabel();
 976  RPCMethod listlabels();
 977  #ifdef ENABLE_EXTERNAL_SIGNER
 978  RPCMethod walletdisplayaddress();
 979  #endif // ENABLE_EXTERNAL_SIGNER
 980  
 981  // backup
 982  RPCMethod importprunedfunds();
 983  RPCMethod removeprunedfunds();
 984  RPCMethod importdescriptors();
 985  RPCMethod listdescriptors();
 986  RPCMethod backupwallet();
 987  RPCMethod restorewallet();
 988  
 989  // coins
 990  RPCMethod getreceivedbyaddress();
 991  RPCMethod getreceivedbylabel();
 992  RPCMethod getbalance();
 993  RPCMethod lockunspent();
 994  RPCMethod listlockunspent();
 995  RPCMethod getbalances();
 996  RPCMethod listunspent();
 997  
 998  // encryption
 999  RPCMethod walletpassphrase();
1000  RPCMethod walletpassphrasechange();
1001  RPCMethod walletlock();
1002  RPCMethod encryptwallet();
1003  
1004  // spend
1005  RPCMethod sendtoaddress();
1006  RPCMethod sendmany();
1007  RPCMethod fundrawtransaction();
1008  RPCMethod bumpfee();
1009  RPCMethod psbtbumpfee();
1010  RPCMethod send();
1011  RPCMethod sendall();
1012  RPCMethod walletprocesspsbt();
1013  RPCMethod walletcreatefundedpsbt();
1014  RPCMethod signrawtransactionwithwallet();
1015  
1016  // signmessage
1017  RPCMethod signmessage();
1018  
1019  // transactions
1020  RPCMethod listreceivedbyaddress();
1021  RPCMethod listreceivedbylabel();
1022  RPCMethod listtransactions();
1023  RPCMethod listsinceblock();
1024  RPCMethod gettransaction();
1025  RPCMethod abandontransaction();
1026  RPCMethod rescanblockchain();
1027  RPCMethod abortrescan();
1028  
1029  std::span<const CRPCCommand> GetWalletRPCCommands()
1030  {
1031      static const CRPCCommand commands[]{
1032          {"rawtransactions", &fundrawtransaction},
1033          {"wallet", &abandontransaction},
1034          {"wallet", &abortrescan},
1035          {"wallet", &addhdkey},
1036          {"wallet", &backupwallet},
1037          {"wallet", &bumpfee},
1038          {"wallet", &psbtbumpfee},
1039          {"wallet", &createwallet},
1040          {"wallet", &createwalletdescriptor},
1041          {"wallet", &restorewallet},
1042          {"wallet", &encryptwallet},
1043          {"wallet", &exportwatchonlywallet},
1044          {"wallet", &getaddressesbylabel},
1045          {"wallet", &getaddressinfo},
1046          {"wallet", &getbalance},
1047          {"wallet", &gethdkeys},
1048          {"wallet", &getnewaddress},
1049          {"wallet", &getrawchangeaddress},
1050          {"wallet", &getreceivedbyaddress},
1051          {"wallet", &getreceivedbylabel},
1052          {"wallet", &gettransaction},
1053          {"wallet", &getbalances},
1054          {"wallet", &getwalletinfo},
1055          {"wallet", &importdescriptors},
1056          {"wallet", &importprunedfunds},
1057          {"wallet", &keypoolrefill},
1058          {"wallet", &listaddressgroupings},
1059          {"wallet", &listdescriptors},
1060          {"wallet", &listlabels},
1061          {"wallet", &listlockunspent},
1062          {"wallet", &listreceivedbyaddress},
1063          {"wallet", &listreceivedbylabel},
1064          {"wallet", &listsinceblock},
1065          {"wallet", &listtransactions},
1066          {"wallet", &listunspent},
1067          {"wallet", &listwalletdir},
1068          {"wallet", &listwallets},
1069          {"wallet", &loadwallet},
1070          {"wallet", &lockunspent},
1071          {"wallet", &migratewallet},
1072          {"wallet", &removeprunedfunds},
1073          {"wallet", &rescanblockchain},
1074          {"wallet", &send},
1075          {"wallet", &sendmany},
1076          {"wallet", &sendtoaddress},
1077          {"wallet", &setlabel},
1078          {"wallet", &setwalletflag},
1079          {"wallet", &signmessage},
1080          {"wallet", &signrawtransactionwithwallet},
1081          {"wallet", &simulaterawtransaction},
1082          {"wallet", &sendall},
1083          {"wallet", &unloadwallet},
1084          {"wallet", &walletcreatefundedpsbt},
1085  #ifdef ENABLE_EXTERNAL_SIGNER
1086          {"wallet", &walletdisplayaddress},
1087  #endif // ENABLE_EXTERNAL_SIGNER
1088          {"wallet", &walletlock},
1089          {"wallet", &walletpassphrase},
1090          {"wallet", &walletpassphrasechange},
1091          {"wallet", &walletprocesspsbt},
1092      };
1093      return commands;
1094  }
1095  } // namespace wallet
1096