Coverage Report

Created: 2025-06-10 13:21

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