Coverage Report

Created: 2026-09-15 16:03

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