LCOV - code coverage report
Current view: top level - src/wallet/rpc - wallet.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 334 812 41.1 %
Date: 2026-06-25 07:23:51 Functions: 19 37 51.4 %

          Line data    Source code
       1             : // Copyright (c) 2010 Satoshi Nakamoto
       2             : // Copyright (c) 2009-2020 The Bitcoin Core developers
       3             : // Copyright (c) 2014-2025 The Dash Core developers
       4             : // Distributed under the MIT software license, see the accompanying
       5             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       6             : 
       7             : #include <chainparams.h>
       8             : #include <common/url.h>
       9             : #include <core_io.h>
      10             : #include <httpserver.h>
      11             : #include <policy/policy.h>
      12             : #include <rpc/blockchain.h>
      13             : #include <rpc/rawtransaction_util.h>
      14             : #include <rpc/server.h>
      15             : #include <rpc/util.h>
      16             : #include <util/bip32.h>
      17             : #include <util/fees.h>
      18             : #include <util/translation.h>
      19             : #include <util/vector.h>
      20             : #include <wallet/context.h>
      21             : #include <wallet/receive.h>
      22             : #include <wallet/rpc/wallet.h>
      23             : #include <wallet/rpc/util.h>
      24             : #include <wallet/scriptpubkeyman.h>
      25             : #include <wallet/spend.h>
      26             : #include <wallet/wallet.h>
      27             : #include <key_io.h>
      28             : 
      29             : #include <coinjoin/client.h>
      30             : #include <coinjoin/options.h>
      31             : 
      32             : #include <optional>
      33             : 
      34             : #include <univalue.h>
      35             : 
      36             : namespace wallet {
      37             : /** Checks if a CKey is in the given CWallet compressed or otherwise*/
      38           0 : bool HaveKey(const SigningProvider& wallet, const CKey& key)
      39             : {
      40           0 :     CKey key2;
      41           0 :     key2.Set(key.begin(), key.end(), !key.IsCompressed());
      42           0 :     return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
      43           0 : }
      44             : 
      45           8 : static RPCHelpMan setcoinjoinrounds()
      46             : {
      47          16 :     return RPCHelpMan{"setcoinjoinrounds",
      48           8 :         "\nSet the number of rounds for CoinJoin.\n",
      49          16 :         {
      50          16 :             {"rounds", RPCArg::Type::NUM, RPCArg::Optional::NO,
      51           8 :                 "The default number of rounds is " + ToString(DEFAULT_COINJOIN_ROUNDS) +
      52           8 :                 " Cannot be more than " + ToString(MAX_COINJOIN_ROUNDS) + " nor less than " + ToString(MIN_COINJOIN_ROUNDS)},
      53             :         },
      54           8 :         RPCResult{RPCResult::Type::NONE, "", ""},
      55           8 :         RPCExamples{
      56           8 :             HelpExampleCli("setcoinjoinrounds", "4")
      57           8 :     + HelpExampleRpc("setcoinjoinrounds", "16")
      58             :         },
      59           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
      60             : {
      61           0 :     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
      62           0 :     if (!wallet) return UniValue::VNULL;
      63             : 
      64           0 :     int nRounds = request.params[0].getInt<int>();
      65             : 
      66           0 :     if (nRounds > MAX_COINJOIN_ROUNDS || nRounds < MIN_COINJOIN_ROUNDS)
      67           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid number of rounds");
      68             : 
      69           0 :     CCoinJoinClientOptions::SetRounds(nRounds);
      70             : 
      71           0 :     return UniValue::VNULL;
      72           0 : },
      73             :     };
      74           0 : }
      75             : 
      76           8 : static RPCHelpMan setcoinjoinamount()
      77             : {
      78          16 :     return RPCHelpMan{"setcoinjoinamount",
      79           8 :         "\nSet the goal amount in " + CURRENCY_UNIT + " for CoinJoin.\n",
      80          16 :         {
      81          16 :             {"amount", RPCArg::Type::NUM, RPCArg::Optional::NO,
      82           8 :                 "The default amount is " + ToString(DEFAULT_COINJOIN_AMOUNT) +
      83           8 :                 " Cannot be more than " + ToString(MAX_COINJOIN_AMOUNT) + " nor less than " + ToString(MIN_COINJOIN_AMOUNT)},
      84             :         },
      85           8 :         RPCResult{RPCResult::Type::NONE, "", ""},
      86           8 :         RPCExamples{
      87           8 :             HelpExampleCli("setcoinjoinamount", "500")
      88           8 :     + HelpExampleRpc("setcoinjoinamount", "208")
      89             :         },
      90           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
      91             : {
      92           0 :     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
      93           0 :     if (!wallet) return UniValue::VNULL;
      94             : 
      95           0 :     int nAmount = request.params[0].getInt<int>();
      96             : 
      97           0 :     if (nAmount > MAX_COINJOIN_AMOUNT || nAmount < MIN_COINJOIN_AMOUNT)
      98           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount of " + CURRENCY_UNIT + " as mixing goal amount");
      99             : 
     100           0 :     CCoinJoinClientOptions::SetAmount(nAmount);
     101             : 
     102           0 :     return UniValue::VNULL;
     103           0 : },
     104             :     };
     105           0 : }
     106             : 
     107           8 : static RPCHelpMan getwalletinfo()
     108             : {
     109          16 :     return RPCHelpMan{"getwalletinfo",
     110           8 :                 "Returns an object containing various wallet state info.\n",
     111           8 :                 {},
     112           8 :                 RPCResult{
     113           8 :                     RPCResult::Type::OBJ, "", "",
     114         192 :                     {
     115           8 :                         {RPCResult::Type::STR, "walletname", "the wallet name"},
     116           8 :                         {RPCResult::Type::NUM, "walletversion", "the wallet version"},
     117           8 :                         {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
     118           8 :                         {RPCResult::Type::NUM, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
     119           8 :                         {RPCResult::Type::NUM, "coinjoin_balance", "DEPRECATED. Identical to getbalances().mine.coinjoin"},
     120           8 :                         {RPCResult::Type::NUM, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
     121           8 :                         {RPCResult::Type::NUM, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
     122           8 :                         {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
     123           8 :                         {RPCResult::Type::NUM_TIME, "timefirstkey", "the " + UNIX_EPOCH_TIME + " of the oldest known key in the wallet"},
     124           8 :                         {RPCResult::Type::NUM_TIME, "keypoololdest", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only"},
     125           8 :                         {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
     126           8 :                         {RPCResult::Type::NUM, "keypoolsize_hd_internal", /*optional=*/ true, "how many new keys are pre-generated for internal use (used for change outputs and mobile coinjoin, only appears if the wallet is using this feature, otherwise external keys are used)"},
     127           8 :                         {RPCResult::Type::NUM, "keys_left", "how many new keys are left since last automatic backup"},
     128           8 :                         {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)"},
     129           8 :                         {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB"},
     130           8 :                         {RPCResult::Type::STR_HEX, "hdchainid", "the ID of the HD chain"},
     131           8 :                         {RPCResult::Type::NUM, "hdaccountcount", "how many accounts of the HD chain are in this wallet"},
     132          16 :                         {RPCResult::Type::ARR, "hdaccounts", "",
     133          16 :                             {
     134          16 :                             {RPCResult::Type::OBJ, "", "",
     135          32 :                                 {
     136           8 :                                     {RPCResult::Type::NUM, "hdaccountindex", "the index of the account"},
     137           8 :                                     {RPCResult::Type::NUM, "hdexternalkeyindex", "current external childkey index"},
     138           8 :                                     {RPCResult::Type::NUM, "hdinternalkeyindex", "current internal childkey index"},
     139             :                             }},
     140             :                         }},
     141           8 :                         {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
     142           8 :                         {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
     143          16 :                         {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
     144          24 :                         {
     145           8 :                             {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
     146           8 :                             {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
     147             :                         }},
     148           8 :                         {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
     149           8 :                         {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
     150           8 :                         RESULT_LAST_PROCESSED_BLOCK,
     151             :                     },
     152             :                 },
     153           8 :                 RPCExamples{
     154           8 :                     HelpExampleCli("getwalletinfo", "")
     155           8 :             + HelpExampleRpc("getwalletinfo", "")
     156             :                 },
     157           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     158             : {
     159           0 :     const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
     160           0 :     if (!pwallet) return UniValue::VNULL;
     161             : 
     162             :     // Make sure the results are valid at least up to the most recent block
     163             :     // the user could have gotten from another RPC command prior to now
     164           0 :     pwallet->BlockUntilSyncedToCurrentChain();
     165             : 
     166           0 :     LOCK(pwallet->cs_wallet);
     167             : 
     168           0 :     LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
     169           0 :     CHDChain hdChainCurrent;
     170           0 :     bool fHDEnabled = spk_man && spk_man->GetHDChain(hdChainCurrent);
     171           0 :     UniValue obj(UniValue::VOBJ);
     172             : 
     173           0 :     const auto bal = GetBalance(*pwallet);
     174           0 :     obj.pushKV("walletname", pwallet->GetName());
     175           0 :     obj.pushKV("walletversion", pwallet->GetVersion());
     176           0 :     obj.pushKV("format", pwallet->GetDatabase().Format());
     177           0 :     obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
     178           0 :     obj.pushKV("coinjoin_balance",       ValueFromAmount(bal.m_anonymized));
     179           0 :     obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
     180           0 :     obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
     181           0 :     obj.pushKV("txcount",       (int)pwallet->mapWallet.size());
     182             :     // TODO: implement timefirstkey for Descriptor KeyMan or explain why it's not provided
     183           0 :     if (spk_man) {
     184           0 :         obj.pushKV("timefirstkey", spk_man->GetTimeFirstKey());
     185           0 :     }
     186           0 :     const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
     187           0 :     if (kp_oldest.has_value()) {
     188           0 :         obj.pushKV("keypoololdest", kp_oldest.value());
     189           0 :     }
     190           0 :     size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
     191           0 :     obj.pushKV("keypoolsize", kpExternalSize);
     192           0 :     obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
     193           0 :     obj.pushKV("keys_left", pwallet->nKeysLeftSinceAutoBackup);
     194           0 :     if (pwallet->IsCrypted()) {
     195           0 :         obj.pushKV("unlocked_until", pwallet->nRelockTime);
     196           0 :     }
     197           0 :     obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
     198           0 :     if (fHDEnabled) {
     199           0 :         obj.pushKV("hdchainid", hdChainCurrent.GetID().GetHex());
     200           0 :         obj.pushKV("hdaccountcount", hdChainCurrent.CountAccounts());
     201           0 :         UniValue accounts(UniValue::VARR);
     202           0 :         for (size_t i = 0; i < hdChainCurrent.CountAccounts(); ++i)
     203             :         {
     204           0 :             CHDAccount acc;
     205           0 :             UniValue account(UniValue::VOBJ);
     206           0 :             account.pushKV("hdaccountindex", i);
     207           0 :             if(hdChainCurrent.GetAccount(i, acc)) {
     208           0 :                 account.pushKV("hdexternalkeyindex", acc.nExternalChainCounter);
     209           0 :                 account.pushKV("hdinternalkeyindex", acc.nInternalChainCounter);
     210           0 :             } else {
     211           0 :                 account.pushKV("error", strprintf("account %d is missing", i));
     212             :             }
     213           0 :             accounts.push_back(account);
     214           0 :         }
     215           0 :         obj.pushKV("hdaccounts", accounts);
     216           0 :     }
     217           0 :     obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
     218           0 :     obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
     219           0 :     if (pwallet->IsScanning()) {
     220           0 :         UniValue scanning(UniValue::VOBJ);
     221           0 :         scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
     222           0 :         scanning.pushKV("progress", pwallet->ScanningProgress());
     223           0 :         obj.pushKV("scanning", scanning);
     224           0 :     } else {
     225           0 :         obj.pushKV("scanning", false);
     226             :     }
     227           0 :     obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
     228           0 :     obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
     229             : 
     230           0 :     AppendLastProcessedBlock(obj, *pwallet);
     231           0 :     return obj;
     232           0 : },
     233             :     };
     234           0 : }
     235             : 
     236           8 : static RPCHelpMan listwalletdir()
     237             : {
     238          16 :     return RPCHelpMan{"listwalletdir",
     239           8 :                 "Returns a list of wallets in the wallet directory.\n",
     240           8 :                 {},
     241           8 :                 RPCResult{
     242           8 :                     RPCResult::Type::OBJ, "", "",
     243          16 :                     {
     244          16 :                         {RPCResult::Type::ARR, "wallets", "",
     245          16 :                         {
     246          16 :                             {RPCResult::Type::OBJ, "", "",
     247          16 :                             {
     248           8 :                                 {RPCResult::Type::STR, "name", "The wallet name"},
     249             :                             }},
     250             :                         }},
     251             :                     }
     252             :                 },
     253           8 :                 RPCExamples{
     254           8 :                     HelpExampleCli("listwalletdir", "")
     255           8 :             + HelpExampleRpc("listwalletdir", "")
     256             :                 },
     257           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     258             : {
     259           0 :     UniValue wallets(UniValue::VARR);
     260           0 :     for (const auto& path : ListDatabases(GetWalletDir())) {
     261           0 :         UniValue wallet(UniValue::VOBJ);
     262           0 :         wallet.pushKV("name", path.utf8string());
     263           0 :         wallets.push_back(wallet);
     264           0 :     }
     265             : 
     266           0 :     UniValue result(UniValue::VOBJ);
     267           0 :     result.pushKV("wallets", wallets);
     268           0 :     return result;
     269           0 : },
     270             :     };
     271           0 : }
     272             : 
     273           8 : static RPCHelpMan listwallets()
     274             : {
     275          16 :     return RPCHelpMan{"listwallets",
     276           8 :                 "Returns a list of currently loaded wallets.\n"
     277             :                 "For full information on the wallet, use \"getwalletinfo\"\n",
     278           8 :                 {},
     279           8 :                 RPCResult{
     280           8 :                     RPCResult::Type::ARR, "", "",
     281          16 :                     {
     282           8 :                         {RPCResult::Type::STR, "walletname", "the wallet name"},
     283             :                     }
     284             :                 },
     285           8 :                 RPCExamples{
     286           8 :                     HelpExampleCli("listwallets", "")
     287           8 :             + HelpExampleRpc("listwallets", "")
     288             :                 },
     289           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     290             : {
     291           0 :     UniValue obj(UniValue::VARR);
     292             : 
     293           0 :     WalletContext& context = EnsureWalletContext(request.context);
     294           0 :     for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
     295           0 :         LOCK(wallet->cs_wallet);
     296           0 :         obj.push_back(wallet->GetName());
     297           0 :     }
     298             : 
     299           0 :     return obj;
     300           0 : },
     301             :     };
     302           0 : }
     303             : 
     304           8 : static RPCHelpMan upgradetohd()
     305             : {
     306          16 :     return RPCHelpMan{"upgradetohd",
     307           8 :         "\nUpgrades non-HD wallets to HD.\n"
     308             :         "\nIf your wallet is encrypted, the wallet passphrase must be supplied. Supplying an incorrect"
     309             :         "\npassphrase may result in your wallet getting locked.\n"
     310             :         "\nWarning: You will need to make a new backup of your wallet after setting the HD wallet mnemonic.\n",
     311          40 :         {
     312           8 :             {"mnemonic", RPCArg::Type::STR, RPCArg::Default{""}, "Mnemonic as defined in BIP39 to use for the new HD wallet. Use an empty string \"\" to generate a new random mnemonic."},
     313           8 :             {"mnemonicpassphrase", RPCArg::Type::STR, RPCArg::Default{""}, "Optional mnemonic passphrase as defined in BIP39"},
     314           8 :             {"walletpassphrase", RPCArg::Type::STR, RPCArg::Default{""}, "If your wallet is encrypted you must have your wallet passphrase here. If your wallet is not encrypted, specifying wallet passphrase will trigger wallet encryption."},
     315           8 :             {"rescan", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false if mnemonic is empty"}, "Whether to rescan the blockchain for missing transactions or not"},
     316             :         },
     317           8 :         RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
     318           8 :         RPCExamples{
     319           8 :             HelpExampleCli("upgradetohd", "")
     320           8 :     + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\"")
     321           8 :     + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"")
     322           8 :     + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"\" \"walletpassphrase\"")
     323           8 :     + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\"")
     324             :         },
     325           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     326             : {
     327           0 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
     328           0 :     if (!pwallet) return UniValue::VNULL;
     329             : 
     330           0 :     bool generate_mnemonic = request.params[0].isNull() || request.params[0].get_str().empty();
     331           0 :     bool mnemonic_passphrase_has_null{false};
     332             :     {
     333           0 :         LOCK(pwallet->cs_wallet);
     334             : 
     335           0 :         SecureString wallet_passphrase;
     336           0 :         wallet_passphrase.reserve(100);
     337             : 
     338           0 :         if (request.params[2].isNull()) {
     339           0 :             if (pwallet->IsCrypted()) {
     340           0 :                 throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet encrypted but passphrase not supplied to RPC.");
     341             :             }
     342           0 :         } else {
     343           0 :             wallet_passphrase = std::string_view{request.params[2].get_str()};
     344             :         }
     345             : 
     346           0 :         SecureString mnemonic;
     347           0 :         mnemonic.reserve(256);
     348           0 :         if (!generate_mnemonic) {
     349           0 :             mnemonic = std::string_view{request.params[0].get_str()};
     350           0 :         }
     351             : 
     352           0 :         SecureString mnemonic_passphrase;
     353           0 :         mnemonic_passphrase.reserve(256);
     354           0 :         if (!request.params[1].isNull()) {
     355           0 :             mnemonic_passphrase = std::string_view{request.params[1].get_str()};
     356           0 :             mnemonic_passphrase_has_null = (mnemonic_passphrase.find('\0') != std::string::npos);
     357           0 :         }
     358             : 
     359             :         // Do not do anything to HD wallets
     360           0 :         if (pwallet->IsHDEnabled()) {
     361           0 :             throw JSONRPCError(RPC_WALLET_ERROR, "Cannot upgrade a wallet to HD if it is already upgraded to HD");
     362             :         }
     363             : 
     364           0 :         if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
     365           0 :             throw JSONRPCError(RPC_WALLET_ERROR, "Private keys are disabled for this wallet");
     366             :         }
     367             : 
     368           0 :         pwallet->WalletLogPrintf("Upgrading wallet to HD\n");
     369           0 :         pwallet->SetMinVersion(FEATURE_HD);
     370             : 
     371           0 :         if (pwallet->IsCrypted()) {
     372           0 :             if (wallet_passphrase.empty()) {
     373           0 :                 throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: Wallet encrypted but supplied empty wallet passphrase");
     374             :             }
     375             : 
     376             :             // We are intentionally re-locking the wallet so we can validate passphrase
     377             :             // by verifying if it can unlock the wallet
     378           0 :             pwallet->Lock();
     379             : 
     380             :             // Unlock the wallet
     381           0 :             if (!pwallet->Unlock(wallet_passphrase)) {
     382             :                 // Check if the passphrase has a null character (see bitcoin#27067 for details)
     383           0 :                 if (wallet_passphrase.find('\0') == std::string::npos) {
     384           0 :                     throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
     385             :                 } else {
     386           0 :                     throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. "
     387             :                                                                         "It contains a null character (ie - a zero byte). "
     388             :                                                                         "If the passphrase was set with a version of this software prior to 23.0, "
     389             :                                                                         "please try again with only the characters up to — but not including — "
     390             :                                                                         "the first null character. If this is successful, please set a new "
     391             :                                                                         "passphrase to avoid this issue in the future.");
     392             :                 }
     393             :             }
     394           0 :         }
     395             : 
     396           0 :         if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
     397           0 :             pwallet->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase);
     398           0 :         } else {
     399           0 :             auto spk_man = pwallet->GetLegacyScriptPubKeyMan();
     400           0 :             if (!spk_man) {
     401           0 :                 throw JSONRPCError(RPC_WALLET_ERROR, "Error: Legacy ScriptPubKeyMan is not available");
     402             :             }
     403             : 
     404           0 :             if (pwallet->IsCrypted()) {
     405           0 :                 pwallet->WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
     406           0 :                         spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase, encryption_key);
     407           0 :                         return true;
     408           0 :                     });
     409           0 :             } else {
     410           0 :                 spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase);
     411             :             }
     412             :         }
     413             : 
     414           0 :         if (pwallet->IsCrypted()) {
     415             :             // Relock encrypted wallet
     416           0 :             pwallet->Lock();
     417           0 :         } else if (!wallet_passphrase.empty()) {
     418             :             // Encrypt non-encrypted wallet
     419           0 :             if (!pwallet->EncryptWallet(wallet_passphrase)) {
     420           0 :                 throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
     421             :             }
     422           0 :         }
     423           0 :     } // pwallet->cs_wallet
     424             : 
     425             :     // If you are generating new mnemonic it is assumed that the addresses have never gotten a transaction before, so you don't need to rescan for transactions
     426           0 :     bool rescan = request.params[3].isNull() ? !generate_mnemonic : request.params[3].get_bool();
     427           0 :     if (rescan) {
     428           0 :         WalletRescanReserver reserver(*pwallet);
     429           0 :         if (!reserver.reserve()) {
     430           0 :             throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
     431             :         }
     432           0 :         CWallet::ScanResult result = pwallet->ScanForWalletTransactions(pwallet->chain().getBlockHash(0), 0, {}, reserver, /*fUpdate=*/true, /*save_progress=*/false);
     433           0 :         switch (result.status) {
     434             :         case CWallet::ScanResult::SUCCESS:
     435           0 :             break;
     436             :         case CWallet::ScanResult::FAILURE:
     437           0 :             throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
     438             :         case CWallet::ScanResult::USER_ABORT:
     439           0 :             throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
     440             :             // no default case, so the compiler can warn about missing cases
     441             :         }
     442           0 :     }
     443             : 
     444             :     // Check if the passphrase has a null character (see #27067 for details)
     445           0 :     if (!mnemonic_passphrase_has_null) {
     446           0 :         return "Make sure that you have backup of your mnemonic.";
     447             :     } else {
     448           0 :         return "Make sure that you have backup of your mnemonic. "
     449             :                "Your mnemonic passphrase contains a null character (ie - a zero byte). "
     450             :                "If the passphrase was created with a version of this software prior to 23.0, "
     451             :                "please try again with only the characters up to — but not including — "
     452             :                "the first null character. If this is successful, please set a new "
     453             :                "passphrase to avoid this issue in the future.";
     454             :     }
     455           0 : },
     456             :     };
     457           0 : }
     458             : 
     459           8 : static RPCHelpMan loadwallet()
     460             : {
     461          16 :     return RPCHelpMan{"loadwallet",
     462           8 :                 "\nLoads a wallet from a wallet file or directory."
     463             :                 "\nNote that all wallet command-line options used when starting dashd will be"
     464             :                 "\napplied to the new wallet (eg, rescan, etc).\n",
     465          24 :                 {
     466           8 :                     {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
     467           8 :                     {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
     468             :                 },
     469           8 :                 RPCResult{
     470           8 :                     RPCResult::Type::OBJ, "", "",
     471          24 :                     {
     472           8 :                         {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
     473           8 :                         {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
     474             :                     }
     475             :                 },
     476           8 :                 RPCExamples{
     477           8 :                     HelpExampleCli("loadwallet", "\"test.dat\"")
     478           8 :             + HelpExampleRpc("loadwallet", "\"test.dat\"")
     479             :                 },
     480           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     481             : {
     482           0 :     WalletContext& context = EnsureWalletContext(request.context);
     483           0 :     const std::string name(request.params[0].get_str());
     484             : 
     485           0 :     DatabaseOptions options;
     486             :     DatabaseStatus status;
     487           0 :     ReadDatabaseArgs(*context.args, options);
     488           0 :     options.require_existing = true;
     489           0 :     bilingual_str error;
     490           0 :     std::vector<bilingual_str> warnings;
     491           0 :     std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
     492             : 
     493             :     {
     494           0 :         LOCK(context.wallets_mutex);
     495           0 :         if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
     496           0 :             throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
     497             :         }
     498           0 :     }
     499             : 
     500           0 :     std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
     501             : 
     502           0 :     HandleWalletError(wallet, status, error);
     503             : 
     504           0 :     UniValue obj(UniValue::VOBJ);
     505           0 :     obj.pushKV("name", wallet->GetName());
     506           0 :     obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
     507             : 
     508           0 :     return obj;
     509           0 : },
     510             :     };
     511           0 : }
     512             : 
     513           8 : static RPCHelpMan setwalletflag()
     514             : {
     515           8 :             std::string flags;
     516          64 :             for (auto& it : WALLET_FLAG_MAP)
     517          56 :                 if (it.second & MUTABLE_WALLET_FLAGS)
     518           8 :                     flags += (flags == "" ? "" : ", ") + it.first;
     519             : 
     520          16 :     return RPCHelpMan{"setwalletflag",
     521           8 :                 "\nChange the state of the given wallet flag for a wallet.\n",
     522          24 :                 {
     523           8 :                     {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
     524           8 :                     {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
     525             :                 },
     526           8 :                 RPCResult{
     527           8 :                     RPCResult::Type::OBJ, "", "",
     528          32 :                     {
     529           8 :                         {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
     530           8 :                         {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
     531           8 :                         {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
     532             :                     }
     533             :                 },
     534           8 :                 RPCExamples{
     535           8 :                     HelpExampleCli("setwalletflag", "avoid_reuse")
     536           8 :                   + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
     537             :                 },
     538           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     539             : {
     540           0 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
     541           0 :     if (!pwallet) return UniValue::VNULL;
     542             : 
     543             : 
     544           0 :     std::string flag_str = request.params[0].get_str();
     545           0 :     bool value = request.params[1].isNull() || request.params[1].get_bool();
     546             : 
     547           0 :     if (!WALLET_FLAG_MAP.count(flag_str)) {
     548           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
     549             :     }
     550             : 
     551           0 :     auto flag = WALLET_FLAG_MAP.at(flag_str);
     552             : 
     553           0 :     if (!(flag & MUTABLE_WALLET_FLAGS)) {
     554           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
     555             :     }
     556             : 
     557           0 :     UniValue res(UniValue::VOBJ);
     558             : 
     559           0 :     if (pwallet->IsWalletFlagSet(flag) == value) {
     560           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
     561             :     }
     562             : 
     563           0 :     res.pushKV("flag_name", flag_str);
     564           0 :     res.pushKV("flag_state", value);
     565             : 
     566           0 :     if (value) {
     567           0 :         pwallet->SetWalletFlag(flag);
     568           0 :     } else {
     569           0 :         pwallet->UnsetWalletFlag(flag);
     570             :     }
     571             : 
     572           0 :     if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
     573           0 :         res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
     574           0 :     }
     575             : 
     576           0 :     return res;
     577           0 : },
     578             :     };
     579           8 : }
     580             : 
     581           8 : static RPCHelpMan createwallet()
     582             : {
     583           8 :     return RPCHelpMan{
     584           8 :         "createwallet",
     585           8 :         "\nCreates and loads a new wallet.\n",
     586          72 :         {
     587           8 :             {"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."},
     588           8 :             {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
     589           8 :             {"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 upgradetohd (by mnemonic) or sethdseed (WIF private key)."},
     590           8 :             {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."},
     591           8 :             {"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."},
     592           8 :             {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."},
     593           8 :             {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
     594           8 :             {"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."},
     595             :         },
     596           8 :         RPCResult{
     597           8 :             RPCResult::Type::OBJ, "", "",
     598          24 :             {
     599           8 :                 {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."},
     600           8 :                 {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
     601             :             }
     602             :         },
     603           8 :         RPCExamples{
     604           8 :             HelpExampleCli("createwallet", "\"testwallet\"")
     605           8 :             + HelpExampleRpc("createwallet", "\"testwallet\"")
     606           8 :             + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
     607           8 :             + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
     608             :         },
     609           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     610             : {
     611           0 :     WalletContext& context = EnsureWalletContext(request.context);
     612           0 :     uint64_t flags = 0;
     613           0 :     if (!request.params[1].isNull() && request.params[1].get_bool()) {
     614           0 :         flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
     615           0 :     }
     616             : 
     617           0 :     if (!request.params[2].isNull() && request.params[2].get_bool()) {
     618           0 :         flags |= WALLET_FLAG_BLANK_WALLET;
     619           0 :     }
     620           0 :     SecureString passphrase;
     621           0 :     passphrase.reserve(100);
     622           0 :     std::vector<bilingual_str> warnings;
     623           0 :     if (!request.params[3].isNull()) {
     624           0 :         passphrase = std::string_view{request.params[3].get_str()};
     625           0 :         if (passphrase.empty()) {
     626             :             // Empty string means unencrypted
     627           0 :             warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
     628           0 :         }
     629           0 :     }
     630             : 
     631           0 :     if (!request.params[4].isNull() && request.params[4].get_bool()) {
     632           0 :         flags |= WALLET_FLAG_AVOID_REUSE;
     633           0 :     }
     634           0 :     if (!request.params[5].isNull() && request.params[6].isNull()) {
     635           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "The createwallet RPC requires specifying the 'load_on_startup' flag when param 'descriptors' is specified. Dash Core v21 introduced this requirement due to breaking changes in the createwallet RPC.");
     636             :     }
     637           0 :     if (request.params[5].isNull() || request.params[5].get_bool()) {
     638             : #ifndef USE_SQLITE
     639             :         throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
     640             : #endif
     641           0 :         flags |= WALLET_FLAG_DESCRIPTORS;
     642           0 :     }
     643           0 :     if (!request.params[7].isNull() && request.params[7].get_bool()) {
     644             : #ifdef ENABLE_EXTERNAL_SIGNER
     645           0 :         flags |= WALLET_FLAG_EXTERNAL_SIGNER;
     646             : #else
     647             :         throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
     648             : #endif
     649           0 :     }
     650             : 
     651             : #ifndef USE_BDB
     652             :     if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
     653             :         throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
     654             :     }
     655             : #endif
     656           0 :     DatabaseOptions options;
     657             :     DatabaseStatus status;
     658           0 :     ReadDatabaseArgs(*context.args, options);
     659           0 :     options.require_create = true;
     660           0 :     options.create_flags = flags;
     661           0 :     options.create_passphrase = passphrase;
     662           0 :     bilingual_str error;
     663           0 :     std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
     664           0 :     const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
     665           0 :     if (!wallet) {
     666           0 :         RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
     667           0 :         throw JSONRPCError(code, error.original);
     668             :     }
     669           0 :     wallet->SetupLegacyScriptPubKeyMan();
     670             : 
     671           0 :     UniValue obj(UniValue::VOBJ);
     672           0 :     obj.pushKV("name", wallet->GetName());
     673           0 :     obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
     674             : 
     675           0 :     return obj;
     676           0 : },
     677             :     };
     678           0 : }
     679             : 
     680           8 : static RPCHelpMan unloadwallet()
     681             : {
     682          16 :     return RPCHelpMan{"unloadwallet",
     683           8 :                 "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n"
     684             :                 "Specifying the wallet name on a wallet endpoint is invalid.",
     685          24 :                 {
     686           8 :                     {"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."},
     687           8 :                     {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
     688             :                 },
     689          16 :                 RPCResult{RPCResult::Type::OBJ, "", "", {
     690           8 :                     {RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
     691             :                 }},
     692           8 :                 RPCExamples{
     693           8 :                     HelpExampleCli("unloadwallet", "wallet_name")
     694           8 :             + HelpExampleRpc("unloadwallet", "wallet_name")
     695             :                 },
     696           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     697             : {
     698           0 :     std::string wallet_name;
     699           0 :     if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
     700           0 :         if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
     701           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
     702             :         }
     703           0 :     } else {
     704           0 :         wallet_name = request.params[0].get_str();
     705             :     }
     706             : 
     707           0 :     WalletContext& context = EnsureWalletContext(request.context);
     708           0 :     std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
     709           0 :     if (!wallet) {
     710           0 :         throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
     711             :     }
     712             : 
     713           0 :     std::vector<bilingual_str> warnings;
     714             :     {
     715           0 :         WalletRescanReserver reserver(*wallet);
     716           0 :         if (!reserver.reserve()) {
     717           0 :             throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
     718             :         }
     719             : 
     720             :         // Release the "main" shared pointer and prevent further notifications.
     721             :         // Note that any attempt to load the same wallet would fail until the wallet
     722             :         // is destroyed (see CheckUniqueFileid).
     723           0 :         std::optional<bool> load_on_start{self.MaybeArg<bool>(1)};
     724           0 :         if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
     725           0 :             throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
     726             :         }
     727           0 :     }
     728             : 
     729           0 :     UnloadWallet(std::move(wallet));
     730             : 
     731           0 :     UniValue result(UniValue::VOBJ);
     732           0 :     result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
     733           0 :     return result;
     734           0 : },
     735             :     };
     736           0 : }
     737             : 
     738           8 : static RPCHelpMan wipewallettxes()
     739             : {
     740          16 :     return RPCHelpMan{"wipewallettxes",
     741           8 :         "\nWipe wallet transactions.\n"
     742             :         "Note: Use \"rescanblockchain\" to initiate the scanning progress and recover wallet transactions.\n",
     743          16 :         {
     744           8 :             {"keep_confirmed", RPCArg::Type::BOOL, RPCArg::Default{false}, "Do not wipe confirmed transactions"},
     745             :         },
     746           8 :         RPCResult{RPCResult::Type::NONE, "", ""},
     747           8 :         RPCExamples{
     748           8 :             HelpExampleCli("wipewallettxes", "")
     749           8 :     + HelpExampleRpc("wipewallettxes", "")
     750             :         },
     751           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     752             : {
     753           0 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     754           0 :     if (!wallet) return UniValue::VNULL;
     755           0 :     CWallet* const pwallet = wallet.get();
     756             : 
     757           0 :     WalletRescanReserver reserver(*pwallet);
     758           0 :     if (!reserver.reserve()) {
     759           0 :         throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort rescan or wait.");
     760             :     }
     761             : 
     762           0 :     LOCK(pwallet->cs_wallet);
     763             : 
     764           0 :     bool keep_confirmed{false};
     765           0 :     if (!request.params[0].isNull()) {
     766           0 :         keep_confirmed = request.params[0].get_bool();
     767           0 :     }
     768             : 
     769           0 :     const size_t WALLET_SIZE{pwallet->mapWallet.size()};
     770           0 :     const size_t STEPS{20};
     771           0 :     const size_t BATCH_SIZE = std::max(WALLET_SIZE / STEPS, size_t(1000));
     772             : 
     773           0 :     pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 0);
     774             : 
     775           0 :     for (size_t progress = 0; progress < STEPS; ++progress) {
     776           0 :         std::vector<uint256> vHashIn;
     777           0 :         std::vector<uint256> vHashOut;
     778           0 :         size_t count{0};
     779             : 
     780           0 :         for (auto& [txid, wtx] : pwallet->mapWallet) {
     781           0 :             if (progress < STEPS - 1 && ++count > BATCH_SIZE) break;
     782           0 :             if (keep_confirmed && wtx.isConfirmed()) continue;
     783           0 :             vHashIn.push_back(txid);
     784             :         }
     785             : 
     786           0 :         if (vHashIn.size() > 0 && pwallet->ZapSelectTx(vHashIn, vHashOut) != DBErrors::LOAD_OK) {
     787           0 :             pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
     788           0 :             throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete transactions.");
     789             :         }
     790             : 
     791           0 :         CHECK_NONFATAL(vHashOut.size() == vHashIn.size());
     792             : 
     793           0 :         if (pwallet->IsAbortingRescan() || pwallet->chain().shutdownRequested()) {
     794           0 :             pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
     795           0 :             throw JSONRPCError(RPC_MISC_ERROR, "Wiping was aborted by user.");
     796             :         }
     797             : 
     798           0 :         pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), std::max(1, std::min(99, int(progress * 100 / STEPS))));
     799           0 :     }
     800             : 
     801           0 :     pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
     802             : 
     803           0 :     return UniValue::VNULL;
     804           0 : },
     805             :     };
     806           0 : }
     807             : 
     808           8 : static RPCHelpMan sethdseed()
     809             : {
     810          16 :     return RPCHelpMan{"sethdseed",
     811             :                 "\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"
     812             :                 "HD can not be updated to a new HD seed.\n"
     813           8 :                 "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + HELP_REQUIRING_PASSPHRASE +
     814             :                 "Note: This command is only compatible with legacy wallets.\n",
     815          24 :                 {
     816           8 :                     {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
     817             :                                          "If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
     818             :                                          "If false, addresses from the existing keypool will be used until it has been depleted."},
     819           8 :                     {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n"
     820             :                                          "The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"},
     821             :                 },
     822           8 :                 RPCResult{RPCResult::Type::NONE, "", ""},
     823           8 :                 RPCExamples{
     824           8 :                     HelpExampleCli("sethdseed", "")
     825           8 :             + HelpExampleCli("sethdseed", "false")
     826           8 :             + HelpExampleCli("sethdseed", "true \"wifkey\"")
     827           8 :             + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
     828             :                 },
     829           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     830             : {
     831           0 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
     832           0 :     if (!pwallet) return UniValue::VNULL;
     833             : 
     834           0 :     LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
     835             : 
     836           0 :     if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
     837           0 :         throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
     838             :     }
     839             : 
     840           0 :     LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
     841             : 
     842             :     // Do not do anything to non-HD wallets
     843           0 :     if (!pwallet->CanSupportFeature(FEATURE_HD)) {
     844           0 :         throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
     845             :     }
     846           0 :     if (pwallet->IsHDEnabled()) {
     847           0 :         throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed. The wallet already has a seed");
     848             :     }
     849             : 
     850           0 :     EnsureWalletIsUnlocked(*pwallet);
     851             : 
     852           0 :     bool flush_key_pool = true;
     853           0 :     if (!request.params[0].isNull()) {
     854           0 :         flush_key_pool = request.params[0].get_bool();
     855           0 :     }
     856             : 
     857           0 :     if (request.params[1].isNull()) {
     858           0 :         spk_man.GenerateNewHDChain("", "");
     859           0 :     } else {
     860           0 :         CKey key = DecodeSecret(request.params[1].get_str());
     861           0 :         if (!key.IsValid()) {
     862           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
     863             :         }
     864           0 :         if (HaveKey(spk_man, key)) {
     865           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
     866             :         }
     867           0 :         CHDChain newHdChain;
     868           0 :         if (!newHdChain.SetSeed(SecureVector(key.begin(), key.end()), true)) {
     869           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key: SetSeed failed");
     870             :         }
     871           0 :         if (!spk_man.AddHDChainSingle(newHdChain)) {
     872           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key: AddHDChainSingle failed");
     873             :         }
     874             :         // add default account
     875           0 :         newHdChain.AddAccount();
     876           0 :     }
     877             : 
     878           0 :     if (flush_key_pool) spk_man.NewKeyPool();
     879             : 
     880           0 :     return UniValue::VNULL;
     881           0 : },
     882             :     };
     883           0 : }
     884             : 
     885           8 : static RPCHelpMan upgradewallet()
     886             : {
     887          16 :     return RPCHelpMan{"upgradewallet",
     888           8 :         "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
     889             :         "New keys may be generated and a new wallet backup will need to be made.\n"
     890             :         "Consider using RPC upgradetohd instead upgradewallet if you have BIP39 mnemonic or want to set a wallet passphrase also (encrypt wallet).",
     891          16 :         {
     892           8 :             {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
     893             :         },
     894           8 :         RPCResult{
     895           8 :             RPCResult::Type::OBJ, "", "",
     896          48 :             {
     897           8 :                 {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
     898           8 :                 {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
     899           8 :                 {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
     900           8 :                 {RPCResult::Type::STR, "result", /*optional=*/true, "Description of result, if no error"},
     901           8 :                 {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}
     902             :             },
     903             :         },
     904           8 :         RPCExamples{
     905           8 :             HelpExampleCli("upgradewallet", "120200")
     906           8 :             + HelpExampleRpc("upgradewallet", "120200")
     907             :         },
     908           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     909             : {
     910           0 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
     911           0 :     if (!pwallet) return UniValue::VNULL;
     912             : 
     913           0 :     RPCTypeCheck(request.params, {UniValue::VNUM}, true);
     914             : 
     915           0 :     EnsureWalletIsUnlocked(*pwallet);
     916             : 
     917           0 :     int version = 0;
     918           0 :     if (!request.params[0].isNull()) {
     919           0 :         version = request.params[0].getInt<int>();
     920           0 :     }
     921           0 :     bilingual_str error;
     922           0 :     const int previous_version{pwallet->GetVersion()};
     923           0 :     const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
     924           0 :     const int current_version{pwallet->GetVersion()};
     925           0 :     std::string result;
     926             : 
     927           0 :     if (wallet_upgraded) {
     928           0 :         if (previous_version == current_version) {
     929           0 :             result = "Already at latest version. Wallet version unchanged.";
     930           0 :         } else {
     931           0 :             result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
     932             :         }
     933           0 :     }
     934             : 
     935           0 :     UniValue obj(UniValue::VOBJ);
     936           0 :     obj.pushKV("wallet_name", pwallet->GetName());
     937           0 :     obj.pushKV("previous_version", previous_version);
     938           0 :     obj.pushKV("current_version", current_version);
     939           0 :     if (!result.empty()) {
     940           0 :         obj.pushKV("result", result);
     941           0 :     } else {
     942           0 :         CHECK_NONFATAL(!error.empty());
     943           0 :         obj.pushKV("error", error.original);
     944             :     }
     945           0 :     return obj;
     946           0 : },
     947             :     };
     948           0 : }
     949             : 
     950           8 : RPCHelpMan simulaterawtransaction()
     951             : {
     952          16 :     return RPCHelpMan{"simulaterawtransaction",
     953           8 :         "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
     954          24 :         {
     955          16 :             {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of hex strings of raw transactions.\n",
     956          16 :                 {
     957           8 :                     {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
     958             :                 },
     959             :             },
     960          16 :             {"options", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED_NAMED_ARG, "Options",
     961          16 :                 {
     962           8 :                     {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"},
     963             :                 },
     964             :             },
     965             :         },
     966           8 :         RPCResult{
     967           8 :             RPCResult::Type::OBJ, "", "",
     968          16 :             {
     969           8 :                 {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
     970             :             }
     971             :         },
     972           8 :         RPCExamples{
     973           8 :             HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
     974           8 :             + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
     975             :         },
     976           8 :     [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     977             : {
     978           0 :     const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
     979           0 :     if (!rpc_wallet) return UniValue::VNULL;
     980           0 :     const CWallet& wallet = *rpc_wallet;
     981             : 
     982           0 :     RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true);
     983             : 
     984           0 :     LOCK(wallet.cs_wallet);
     985             : 
     986           0 :     UniValue include_watchonly(UniValue::VNULL);
     987           0 :     if (request.params[1].isObject()) {
     988           0 :         UniValue options = request.params[1];
     989           0 :         RPCTypeCheckObj(options,
     990           0 :             {
     991           0 :                 {"include_watchonly", UniValueType(UniValue::VBOOL)},
     992             :             },
     993             :             true, true);
     994             : 
     995           0 :         include_watchonly = options["include_watchonly"];
     996           0 :     }
     997             : 
     998           0 :     isminefilter filter = ISMINE_SPENDABLE;
     999           0 :     if (ParseIncludeWatchonly(include_watchonly, wallet)) {
    1000           0 :         filter |= ISMINE_WATCH_ONLY;
    1001           0 :     }
    1002             : 
    1003           0 :     const auto& txs = request.params[0].get_array();
    1004           0 :     CAmount changes{0};
    1005           0 :     std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
    1006           0 :     std::set<COutPoint> spent;
    1007             : 
    1008           0 :     for (size_t i = 0; i < txs.size(); ++i) {
    1009           0 :         CMutableTransaction mtx;
    1010           0 :         if (!DecodeHexTx(mtx, txs[i].get_str())) {
    1011           0 :             throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
    1012             :         }
    1013             : 
    1014             :         // Fetch previous transactions (inputs)
    1015           0 :         std::map<COutPoint, Coin> coins;
    1016           0 :         for (const CTxIn& txin : mtx.vin) {
    1017           0 :             coins[txin.prevout]; // Create empty map entry keyed by prevout.
    1018             :         }
    1019           0 :         wallet.chain().findCoins(coins);
    1020             : 
    1021             :         // Fetch debit; we are *spending* these; if the transaction is signed and
    1022             :         // broadcast, we will lose everything in these
    1023           0 :         for (const auto& txin : mtx.vin) {
    1024           0 :             const auto& outpoint = txin.prevout;
    1025           0 :             if (spent.count(outpoint)) {
    1026           0 :                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
    1027             :             }
    1028           0 :             if (new_utxos.count(outpoint)) {
    1029           0 :                 changes -= new_utxos.at(outpoint);
    1030           0 :                 new_utxos.erase(outpoint);
    1031           0 :             } else {
    1032           0 :                 if (coins.at(outpoint).IsSpent()) {
    1033           0 :                     throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
    1034             :                 }
    1035           0 :                 changes -= wallet.GetDebit(txin, filter);
    1036             :             }
    1037           0 :             spent.insert(outpoint);
    1038             :         }
    1039             : 
    1040             :         // Iterate over outputs; we are *receiving* these, if the wallet considers
    1041             :         // them "mine"; if the transaction is signed and broadcast, we will receive
    1042             :         // everything in these
    1043             :         // Also populate new_utxos in case these are spent in later transactions
    1044             : 
    1045           0 :         const auto& hash = mtx.GetHash();
    1046           0 :         for (size_t i = 0; i < mtx.vout.size(); ++i) {
    1047           0 :             const auto& txout = mtx.vout[i];
    1048           0 :             bool is_mine = 0 < (wallet.IsMine(txout) & filter);
    1049           0 :             changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
    1050           0 :         }
    1051           0 :     }
    1052             : 
    1053           0 :     UniValue result(UniValue::VOBJ);
    1054           0 :     result.pushKV("balance_change", ValueFromAmount(changes));
    1055             : 
    1056           0 :     return result;
    1057           0 : }
    1058             :     };
    1059           0 : }
    1060             : 
    1061           8 : static RPCHelpMan migratewallet()
    1062             : {
    1063          16 :     return RPCHelpMan{"migratewallet",
    1064           8 :         "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n"
    1065             :         "\nMigrate the wallet to a descriptor wallet.\n"
    1066             :         "A new wallet backup will need to be made.\n"
    1067             :         "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
    1068             :         "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
    1069             :         "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
    1070             :         "\nEncrypted wallets must have the passphrase provided as an argument to this call.",
    1071          24 :         {
    1072           8 :             {"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."},
    1073           8 :             {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
    1074             :         },
    1075           8 :         RPCResult{
    1076           8 :             RPCResult::Type::OBJ, "", "",
    1077          40 :             {
    1078           8 :                 {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
    1079           8 :                 {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
    1080           8 :                 {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
    1081           8 :                 {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
    1082             :             }
    1083             :         },
    1084           8 :         RPCExamples{
    1085           8 :             HelpExampleCli("migratewallet", "")
    1086           8 :             + HelpExampleRpc("migratewallet", "")
    1087             :         },
    1088           8 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1089             :         {
    1090           0 :             std::string wallet_name;
    1091           0 :             if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
    1092           0 :                 if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
    1093           0 :                     throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
    1094             :                 }
    1095           0 :             } else {
    1096           0 :                 if (request.params[0].isNull()) {
    1097           0 :                     throw JSONRPCError(RPC_INVALID_PARAMETER, "Either RPC endpoint wallet or wallet_name parameter must be provided");
    1098             :                 }
    1099           0 :                 wallet_name = request.params[0].get_str();
    1100             :             }
    1101             : 
    1102           0 :             SecureString wallet_pass;
    1103           0 :             wallet_pass.reserve(100);
    1104           0 :             if (!request.params[1].isNull()) {
    1105           0 :                 wallet_pass = std::string_view{request.params[1].get_str()};
    1106           0 :             }
    1107             : 
    1108           0 :             WalletContext& context = EnsureWalletContext(request.context);
    1109           0 :             util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
    1110           0 :             if (!res) {
    1111           0 :                 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
    1112             :             }
    1113             : 
    1114           0 :             UniValue r{UniValue::VOBJ};
    1115           0 :             r.pushKV("wallet_name", res->wallet_name);
    1116           0 :             if (res->watchonly_wallet) {
    1117           0 :                 r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
    1118           0 :             }
    1119           0 :             if (res->solvables_wallet) {
    1120           0 :                 r.pushKV("solvables_name", res->solvables_wallet->GetName());
    1121           0 :             }
    1122           0 :             r.pushKV("backup_path", fs::PathToString(res->backup_path));
    1123             : 
    1124           0 :             return r;
    1125           0 :         },
    1126             :     };
    1127           0 : }
    1128             : 
    1129             : // addresses
    1130             : RPCHelpMan getaddressinfo();
    1131             : RPCHelpMan getnewaddress();
    1132             : RPCHelpMan getrawchangeaddress();
    1133             : RPCHelpMan setlabel();
    1134             : RPCHelpMan listaddressgroupings();
    1135             : RPCHelpMan addmultisigaddress();
    1136             : RPCHelpMan keypoolrefill();
    1137             : RPCHelpMan newkeypool();
    1138             : RPCHelpMan getaddressesbylabel();
    1139             : RPCHelpMan listlabels();
    1140             : #ifdef ENABLE_EXTERNAL_SIGNER
    1141             : RPCHelpMan walletdisplayaddress();
    1142             : #endif // ENABLE_EXTERNAL_SIGNER
    1143             : 
    1144             : // backup
    1145             : RPCHelpMan dumpprivkey();
    1146             : RPCHelpMan importprivkey();
    1147             : RPCHelpMan importaddress();
    1148             : RPCHelpMan importpubkey();
    1149             : RPCHelpMan dumpwallet();
    1150             : RPCHelpMan importwallet();
    1151             : RPCHelpMan importprunedfunds();
    1152             : RPCHelpMan removeprunedfunds();
    1153             : RPCHelpMan importmulti();
    1154             : RPCHelpMan importdescriptors();
    1155             : RPCHelpMan listdescriptors();
    1156             : RPCHelpMan backupwallet();
    1157             : RPCHelpMan restorewallet();
    1158             : RPCHelpMan dumphdinfo();
    1159             : RPCHelpMan importelectrumwallet();
    1160             : 
    1161             : // coins
    1162             : RPCHelpMan getreceivedbyaddress();
    1163             : RPCHelpMan getreceivedbylabel();
    1164             : RPCHelpMan listaddressbalances();
    1165             : RPCHelpMan getbalance();
    1166             : RPCHelpMan getunconfirmedbalance();
    1167             : RPCHelpMan lockunspent();
    1168             : RPCHelpMan listlockunspent();
    1169             : RPCHelpMan getbalances();
    1170             : RPCHelpMan listunspent();
    1171             : 
    1172             : // encryption
    1173             : RPCHelpMan walletpassphrase();
    1174             : RPCHelpMan walletpassphrasechange();
    1175             : RPCHelpMan walletlock();
    1176             : RPCHelpMan encryptwallet();
    1177             : 
    1178             : // spend
    1179             : RPCHelpMan sendtoaddress();
    1180             : RPCHelpMan sendmany();
    1181             : RPCHelpMan settxfee();
    1182             : RPCHelpMan fundrawtransaction();
    1183             : RPCHelpMan send();
    1184             : RPCHelpMan sendall();
    1185             : RPCHelpMan walletprocesspsbt();
    1186             : RPCHelpMan walletcreatefundedpsbt();
    1187             : RPCHelpMan signrawtransactionwithwallet();
    1188             : 
    1189             : // signmessage
    1190             : RPCHelpMan signmessage();
    1191             : 
    1192             : // transactions
    1193             : RPCHelpMan listreceivedbyaddress();
    1194             : RPCHelpMan listreceivedbylabel();
    1195             : RPCHelpMan listtransactions();
    1196             : RPCHelpMan listsinceblock();
    1197             : RPCHelpMan gettransaction();
    1198             : RPCHelpMan abandontransaction();
    1199             : RPCHelpMan rescanblockchain();
    1200             : RPCHelpMan abortrescan();
    1201             : 
    1202          14 : Span<const CRPCCommand> GetWalletRPCCommands()
    1203             : {
    1204         302 :     static const CRPCCommand commands[]{
    1205           4 :         {"rawtransactions", &fundrawtransaction},
    1206           4 :         {"wallet", &abandontransaction},
    1207           4 :         {"wallet", &abortrescan},
    1208           4 :         {"wallet", &addmultisigaddress},
    1209           4 :         {"wallet", &backupwallet},
    1210           4 :         {"wallet", &createwallet},
    1211           4 :         {"wallet", &restorewallet},
    1212           4 :         {"wallet", &dumphdinfo},
    1213           4 :         {"wallet", &dumpprivkey},
    1214           4 :         {"wallet", &dumpwallet},
    1215           4 :         {"wallet", &encryptwallet},
    1216           4 :         {"wallet", &getaddressesbylabel},
    1217           4 :         {"wallet", &getaddressinfo},
    1218           4 :         {"wallet", &getbalance},
    1219           4 :         {"wallet", &getnewaddress},
    1220           4 :         {"wallet", &getrawchangeaddress},
    1221           4 :         {"wallet", &getreceivedbyaddress},
    1222           4 :         {"wallet", &getreceivedbylabel},
    1223           4 :         {"wallet", &gettransaction},
    1224           4 :         {"wallet", &getunconfirmedbalance},
    1225           4 :         {"wallet", &getbalances},
    1226           4 :         {"wallet", &getwalletinfo},
    1227           4 :         {"wallet", &importaddress},
    1228           4 :         {"wallet", &importelectrumwallet},
    1229           4 :         {"wallet", &importdescriptors},
    1230           4 :         {"wallet", &importmulti},
    1231           4 :         {"wallet", &importprivkey},
    1232           4 :         {"wallet", &importprunedfunds},
    1233           4 :         {"wallet", &importpubkey},
    1234           4 :         {"wallet", &importwallet},
    1235           4 :         {"wallet", &keypoolrefill},
    1236           4 :         {"wallet", &listaddressbalances},
    1237           4 :         {"wallet", &listaddressgroupings},
    1238           4 :         {"wallet", &listdescriptors},
    1239           4 :         {"wallet", &listlabels},
    1240           4 :         {"wallet", &listlockunspent},
    1241           4 :         {"wallet", &listreceivedbyaddress},
    1242           4 :         {"wallet", &listreceivedbylabel},
    1243           4 :         {"wallet", &listsinceblock},
    1244           4 :         {"wallet", &listtransactions},
    1245           4 :         {"wallet", &listunspent},
    1246           4 :         {"wallet", &listwalletdir},
    1247           4 :         {"wallet", &listwallets},
    1248           4 :         {"wallet", &loadwallet},
    1249           4 :         {"wallet", &lockunspent},
    1250           4 :         {"wallet", &migratewallet},
    1251           4 :         {"wallet", &newkeypool},
    1252           4 :         {"wallet", &removeprunedfunds},
    1253           4 :         {"wallet", &rescanblockchain},
    1254           4 :         {"wallet", &send},
    1255           4 :         {"wallet", &sendmany},
    1256           4 :         {"wallet", &sendtoaddress},
    1257           4 :         {"wallet", &sethdseed},
    1258           4 :         {"wallet", &setcoinjoinrounds},
    1259           4 :         {"wallet", &setcoinjoinamount},
    1260           4 :         {"wallet", &setlabel},
    1261           4 :         {"wallet", &settxfee},
    1262           4 :         {"wallet", &setwalletflag},
    1263           4 :         {"wallet", &signmessage},
    1264           4 :         {"wallet", &signrawtransactionwithwallet},
    1265           4 :         {"wallet", &simulaterawtransaction},
    1266           4 :         {"wallet", &sendall},
    1267           4 :         {"wallet", &unloadwallet},
    1268           4 :         {"wallet", &upgradewallet},
    1269           4 :         {"wallet", &upgradetohd},
    1270             : #ifdef ENABLE_EXTERNAL_SIGNER
    1271           4 :         {"wallet", &walletdisplayaddress},
    1272             : #endif // ENABLE_EXTERNAL_SIGNER
    1273           4 :         {"wallet", &walletlock},
    1274           4 :         {"wallet", &walletpassphrasechange},
    1275           4 :         {"wallet", &walletpassphrase},
    1276           4 :         {"wallet", &walletprocesspsbt},
    1277           4 :         {"wallet", &walletcreatefundedpsbt},
    1278           4 :         {"wallet", &wipewallettxes},
    1279             :     };
    1280          14 :     return commands;
    1281           0 : }
    1282             : } // namespace wallet

Generated by: LCOV version 1.16