wallet.cpp raw

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