Coverage Report

Created: 2026-09-14 20:36

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