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