Coverage Report

Created: 2026-07-14 18:13

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