Coverage Report

Created: 2026-10-03 10:33

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.34k
{
46
1.34k
    return RPCMethod{"getwalletinfo",
47
1.34k
                "Returns an object containing various wallet state info.\n",
48
1.34k
                {},
49
1.34k
                RPCResult{
50
1.34k
                    RPCResult::Type::OBJ, "", "",
51
1.34k
                    {
52
1.34k
                        {
53
1.34k
                        {RPCResult::Type::STR, "walletname", "the wallet name"},
54
1.34k
                        {RPCResult::Type::NUM, "walletversion", "(DEPRECATED) only related to unsupported legacy wallet, returns the latest version 169900 for backwards compatibility"},
55
1.34k
                        {RPCResult::Type::STR, "format", "the database format (only sqlite)"},
56
1.34k
                        {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
57
1.34k
                        {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
58
1.34k
                        {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.34k
                        {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.34k
                        {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
61
1.34k
                        {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
62
1.34k
                        {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
63
1.34k
                        {
64
1.34k
                            {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
65
1.34k
                            {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
66
1.34k
                        }, {.skip_type_check=true}, },
67
1.34k
                        {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
68
1.34k
                        {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
69
1.34k
                        {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
70
1.34k
                        {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.34k
                        {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet",
72
1.34k
                        {
73
1.34k
                            {RPCResult::Type::STR, "flag", "The name of the flag"},
74
1.34k
                        }},
75
1.34k
                        RESULT_LAST_PROCESSED_BLOCK,
76
1.34k
                    }},
77
1.34k
                },
78
1.34k
                RPCExamples{
79
1.34k
                    HelpExampleCli("getwalletinfo", "")
80
1.34k
            + HelpExampleRpc("getwalletinfo", "")
81
1.34k
                },
82
1.34k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
83
1.34k
{
84
470
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
85
470
    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
470
    pwallet->BlockUntilSyncedToCurrentChain();
90
91
470
    LOCK(pwallet->cs_wallet);
92
93
470
    UniValue obj(UniValue::VOBJ);
94
95
470
    const int latest_legacy_wallet_minversion{169900};
96
97
470
    size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
98
470
    obj.pushKV("walletname", pwallet->GetName());
99
470
    obj.pushKV("walletversion", latest_legacy_wallet_minversion);
100
470
    obj.pushKV("format", pwallet->GetDatabase().Format());
101
470
    obj.pushKV("txcount", pwallet->mapWallet.size());
102
470
    obj.pushKV("keypoolsize", kpExternalSize);
103
470
    obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
104
105
470
    if (pwallet->HasEncryptionKeys()) {
106
45
        obj.pushKV("unlocked_until", pwallet->nRelockTime);
107
45
    }
108
470
    obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
109
470
    obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
110
470
    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
470
    } else {
116
470
        obj.pushKV("scanning", false);
117
470
    }
118
470
    obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
119
470
    obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
120
470
    obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
121
470
    if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
122
408
        obj.pushKV("birthtime", birthtime);
123
408
    }
124
125
    // Push known flags
126
470
    UniValue flags(UniValue::VARR);
127
470
    uint64_t wallet_flags = pwallet->GetWalletFlags();
128
29.4k
    for (uint64_t i = 0; i < 64; ++i) {
129
28.9k
        uint64_t flag = uint64_t{1} << i;
130
28.9k
        if (flag & wallet_flags) {
131
1.13k
            if (flag & KNOWN_WALLET_FLAGS) {
132
1.13k
                flags.push_back(WALLET_FLAG_TO_STRING.at(WalletFlags{flag}));
133
1.13k
            } else {
134
0
                flags.push_back(strprintf("unknown_flag_%u", i));
135
0
            }
136
1.13k
        }
137
28.9k
    }
138
470
    obj.pushKV("flags", flags);
139
140
470
    AppendLastProcessedBlock(obj, *pwallet);
141
470
    return obj;
142
470
},
143
1.34k
    };
144
1.34k
}
145
146
static RPCMethod listwalletdir()
147
945
{
148
945
    return RPCMethod{"listwalletdir",
149
945
                "Returns a list of wallets in the wallet directory.\n",
150
945
                {},
151
945
                RPCResult{
152
945
                    RPCResult::Type::OBJ, "", "",
153
945
                    {
154
945
                        {RPCResult::Type::ARR, "wallets", "",
155
945
                        {
156
945
                            {RPCResult::Type::OBJ, "", "",
157
945
                            {
158
945
                                {RPCResult::Type::STR, "name", "The wallet name"},
159
945
                                {RPCResult::Type::ARR, "warnings", "Warning messages related to loading the wallet (may be empty).",
160
945
                                {
161
945
                                    {RPCResult::Type::STR, "", ""},
162
945
                                }},
163
945
                            }},
164
945
                        }},
165
945
                    }
166
945
                },
167
945
                RPCExamples{
168
945
                    HelpExampleCli("listwalletdir", "")
169
945
            + HelpExampleRpc("listwalletdir", "")
170
945
                },
171
945
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
172
945
{
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
945
    };
190
945
}
191
192
static RPCMethod listwallets()
193
962
{
194
962
    return RPCMethod{"listwallets",
195
962
                "Returns a list of currently loaded wallets.\n"
196
962
                "For full information on the wallet, use \"getwalletinfo\"\n",
197
962
                {},
198
962
                RPCResult{
199
962
                    RPCResult::Type::ARR, "", "",
200
962
                    {
201
962
                        {RPCResult::Type::STR, "walletname", "the wallet name"},
202
962
                    }
203
962
                },
204
962
                RPCExamples{
205
962
                    HelpExampleCli("listwallets", "")
206
962
            + HelpExampleRpc("listwallets", "")
207
962
                },
208
962
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
209
962
{
210
91
    UniValue obj(UniValue::VARR);
211
212
91
    WalletContext& context = EnsureWalletContext(request.context);
213
575
    for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
214
575
        LOCK(wallet->cs_wallet);
215
575
        obj.push_back(wallet->GetName());
216
575
    }
217
218
91
    return obj;
219
91
},
220
962
    };
221
962
}
222
223
static RPCMethod loadwallet()
224
1.06k
{
225
1.06k
    return RPCMethod{
226
1.06k
        "loadwallet",
227
1.06k
        "Loads a wallet from a wallet file or directory."
228
1.06k
                "\nNote that all wallet command-line options used when starting bitcoind will be"
229
1.06k
                "\napplied to the new wallet.\n",
230
1.06k
                {
231
1.06k
                    {"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.06k
                    {"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.06k
                },
234
1.06k
                RPCResult{
235
1.06k
                    RPCResult::Type::OBJ, "", "",
236
1.06k
                    {
237
1.06k
                        {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
238
1.06k
                        {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
239
1.06k
                        {
240
1.06k
                            {RPCResult::Type::STR, "", ""},
241
1.06k
                        }},
242
1.06k
                    }
243
1.06k
                },
244
1.06k
                RPCExamples{
245
1.06k
                    "\nLoad wallet from the wallet dir:\n"
246
1.06k
                    + HelpExampleCli("loadwallet", "\"walletname\"")
247
1.06k
                    + HelpExampleRpc("loadwallet", "\"walletname\"")
248
1.06k
                    + "\nLoad wallet using absolute path (Unix):\n"
249
1.06k
                    + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
250
1.06k
                    + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
251
1.06k
                    + "\nLoad wallet using absolute path (Windows):\n"
252
1.06k
                    + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
253
1.06k
                    + HelpExampleRpc("loadwallet", R"("DriveLetter:\\path\\to\\walletname")")
254
1.06k
                },
255
1.06k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
256
1.06k
{
257
194
    WalletContext& context = EnsureWalletContext(request.context);
258
194
    const std::string name(request.params[0].get_str());
259
260
194
    DatabaseOptions options;
261
194
    DatabaseStatus status;
262
194
    ReadDatabaseArgs(*context.args, options);
263
194
    options.require_existing = true;
264
194
    bilingual_str error;
265
194
    std::vector<bilingual_str> warnings;
266
194
    std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
267
268
194
    {
269
194
        LOCK(context.wallets_mutex);
270
698
        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
194
    }
274
275
192
    std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
276
277
192
    HandleWalletError(wallet, status, error);
278
279
192
    UniValue obj(UniValue::VOBJ);
280
192
    obj.pushKV("name", wallet->GetName());
281
192
    PushWarnings(warnings, obj);
282
283
192
    return obj;
284
194
},
285
1.06k
    };
286
1.06k
}
287
288
static RPCMethod setwalletflag()
289
879
{
290
879
            std::string flags;
291
879
            for (auto& it : STRING_TO_WALLET_FLAG)
292
6.15k
                if (it.second & MUTABLE_WALLET_FLAGS)
293
879
                    flags += (flags == "" ? "" : ", ") + it.first;
294
295
879
    return RPCMethod{
296
879
        "setwalletflag",
297
879
        "Change the state of the given wallet flag for a wallet.\n",
298
879
                {
299
879
                    {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
300
879
                    {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
301
879
                },
302
879
                RPCResult{
303
879
                    RPCResult::Type::OBJ, "", "",
304
879
                    {
305
879
                        {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
306
879
                        {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
307
879
                        {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
308
879
                    }
309
879
                },
310
879
                RPCExamples{
311
879
                    HelpExampleCli("setwalletflag", "avoid_reuse")
312
879
                  + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
313
879
                },
314
879
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
315
879
{
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
879
    };
354
879
}
355
356
static RPCMethod createwallet()
357
1.54k
{
358
1.54k
    return RPCMethod{
359
1.54k
        "createwallet",
360
1.54k
        "Creates and loads a new wallet.\n",
361
1.54k
        {
362
1.54k
            {"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.54k
            {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
364
1.54k
            {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys."},
365
1.54k
            {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
366
1.54k
            {"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.54k
            {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""},
368
1.54k
            {"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.54k
            {"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.54k
        },
371
1.54k
        RPCResult{
372
1.54k
            RPCResult::Type::OBJ, "", "",
373
1.54k
            {
374
1.54k
                {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.54k
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
376
1.54k
                {
377
1.54k
                    {RPCResult::Type::STR, "", ""},
378
1.54k
                }},
379
1.54k
            }
380
1.54k
        },
381
1.54k
        RPCExamples{
382
1.54k
            HelpExampleCli("createwallet", "\"testwallet\"")
383
1.54k
            + HelpExampleRpc("createwallet", "\"testwallet\"")
384
1.54k
            + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
385
1.54k
            + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
386
1.54k
        },
387
1.54k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
388
1.54k
{
389
678
    WalletContext& context = EnsureWalletContext(request.context);
390
678
    uint64_t flags = 0;
391
678
    if (!request.params[1].isNull() && request.params[1].get_bool()) {
392
114
        flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
393
114
    }
394
395
678
    if (!request.params[2].isNull() && request.params[2].get_bool()) {
396
176
        flags |= WALLET_FLAG_BLANK_WALLET;
397
176
    }
398
678
    SecureString passphrase;
399
678
    passphrase.reserve(100);
400
678
    std::vector<bilingual_str> warnings;
401
678
    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
678
    if (!request.params[4].isNull() && request.params[4].get_bool()) {
410
5
        flags |= WALLET_FLAG_AVOID_REUSE;
411
5
    }
412
678
    flags |= WALLET_FLAG_DESCRIPTORS;
413
678
    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
676
    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
676
    DatabaseOptions options;
425
676
    DatabaseStatus status;
426
676
    ReadDatabaseArgs(*context.args, options);
427
676
    options.require_create = true;
428
676
    options.create_flags = flags;
429
676
    options.create_passphrase = passphrase;
430
676
    bilingual_str error;
431
676
    std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
432
676
    const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
433
676
    HandleWalletError(wallet, status, error);
434
435
676
    UniValue obj(UniValue::VOBJ);
436
676
    obj.pushKV("name", wallet->GetName());
437
676
    PushWarnings(warnings, obj);
438
439
676
    return obj;
440
678
},
441
1.54k
    };
442
1.54k
}
443
444
static RPCMethod unloadwallet()
445
1.22k
{
446
1.22k
    return RPCMethod{"unloadwallet",
447
1.22k
                "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n"
448
1.22k
                "If both are specified, they must be identical.",
449
1.22k
                {
450
1.22k
                    {"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.22k
                    {"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.22k
                },
453
1.22k
                RPCResult{RPCResult::Type::OBJ, "", "", {
454
1.22k
                    {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
455
1.22k
                    {
456
1.22k
                        {RPCResult::Type::STR, "", ""},
457
1.22k
                    }},
458
1.22k
                }},
459
1.22k
                RPCExamples{
460
1.22k
                    HelpExampleCli("unloadwallet", "wallet_name")
461
1.22k
            + HelpExampleRpc("unloadwallet", R"("wallet_name")")
462
1.22k
                },
463
1.22k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
464
1.22k
{
465
356
    const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
466
467
356
    WalletContext& context = EnsureWalletContext(request.context);
468
356
    std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
469
356
    if (!wallet) {
470
4
        throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
471
4
    }
472
473
352
    std::vector<bilingual_str> warnings;
474
352
    {
475
352
        WalletRescanReserver reserver(*wallet);
476
352
        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
352
        std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
484
352
        if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
485
0
            throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
486
0
        }
487
352
    }
488
489
352
    WaitForDeleteWallet(std::move(wallet));
490
491
352
    UniValue result(UniValue::VOBJ);
492
352
    PushWarnings(warnings, result);
493
494
352
    return result;
495
352
},
496
1.22k
    };
497
1.22k
}
498
499
RPCMethod simulaterawtransaction()
500
897
{
501
897
    return RPCMethod{
502
897
        "simulaterawtransaction",
503
897
        "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
504
897
        {
505
897
            {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.\n",
506
897
                {
507
897
                    {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
508
897
                },
509
897
            },
510
897
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
511
897
                {
512
897
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
513
897
                },
514
897
            },
515
897
        },
516
897
        RPCResult{
517
897
            RPCResult::Type::OBJ, "", "",
518
897
            {
519
897
                {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
520
897
            }
521
897
        },
522
897
        RPCExamples{
523
897
            HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
524
897
            + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
525
897
        },
526
897
    [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
527
897
{
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
897
    };
590
897
}
591
592
static RPCMethod migratewallet()
593
928
{
594
928
    return RPCMethod{
595
928
        "migratewallet",
596
928
        "Migrate the wallet to a descriptor wallet.\n"
597
928
        "A new wallet backup will need to be made.\n"
598
928
        "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
599
928
        "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
600
928
        "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
601
928
        "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
602
928
        "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
603
928
        {
604
928
            {"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
928
            {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
606
928
            {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration."},
607
928
        },
608
928
        RPCResult{
609
928
            RPCResult::Type::OBJ, "", "",
610
928
            {
611
928
                {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
612
928
                {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
613
928
                {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
614
928
                {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
615
928
            }
616
928
        },
617
928
        RPCExamples{
618
928
            HelpExampleCli("migratewallet", "")
619
928
            + HelpExampleRpc("migratewallet", "")
620
928
        },
621
928
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
622
928
        {
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
928
    };
652
928
}
653
654
RPCMethod gethdkeys()
655
913
{
656
913
    return RPCMethod{
657
913
        "gethdkeys",
658
913
        "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
659
913
        {
660
913
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
661
913
                {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
662
913
                {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
663
913
            }},
664
913
        },
665
913
        RPCResult{RPCResult::Type::ARR, "", "", {
666
913
            {
667
913
                {RPCResult::Type::OBJ, "", "", {
668
913
                    {RPCResult::Type::STR, "xpub", "The extended public key"},
669
913
                    {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
670
913
                    {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
671
913
                    {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
672
913
                    {
673
913
                        {RPCResult::Type::OBJ, "", "", {
674
913
                            {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
675
913
                            {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
676
913
                        }},
677
913
                    }},
678
913
                }},
679
913
            }
680
913
        }},
681
913
        RPCExamples{
682
913
            HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
683
913
            + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
684
913
        },
685
913
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
686
913
        {
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
913
    };
740
913
}
741
742
static RPCMethod createwalletdescriptor()
743
885
{
744
885
    return RPCMethod{"createwalletdescriptor",
745
885
        "Creates the wallet's descriptor for the given address type. "
746
885
        "The address type must be one that the wallet does not already have a descriptor for."
747
885
        + HELP_REQUIRING_PASSPHRASE,
748
885
        {
749
885
            {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
750
885
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
751
885
                {"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
885
                {"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
885
            }},
754
885
        },
755
885
        RPCResult{
756
885
            RPCResult::Type::OBJ, "", "",
757
885
            {
758
885
                {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
759
885
                    {{RPCResult::Type::STR, "", ""}}
760
885
                }
761
885
            },
762
885
        },
763
885
        RPCExamples{
764
885
            HelpExampleCli("createwalletdescriptor", "bech32m")
765
885
            + HelpExampleRpc("createwalletdescriptor", R"("bech32m")")
766
885
        },
767
885
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
768
885
        {
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
885
    };
837
885
}
838
839
RPCMethod addhdkey()
840
884
{
841
884
    return RPCMethod{
842
884
        "addhdkey",
843
884
        "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
844
884
        {
845
884
            {"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
884
        },
847
884
        RPCResult{
848
884
            RPCResult::Type::OBJ, "", "",
849
884
            {
850
884
                {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
851
884
            },
852
884
        },
853
884
        RPCExamples{
854
884
            HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", R"("xprv")")
855
884
        },
856
884
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
857
884
        {
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
884
    };
894
884
}
895
896
static RPCMethod exportwatchonlywallet()
897
883
{
898
883
    return RPCMethod{"exportwatchonlywallet",
899
883
        "Creates a wallet file at the specified destination containing a watchonly version "
900
883
        "of the current wallet. This watchonly wallet contains the wallet's public descriptors, "
901
883
        "its transactions, and address book data. Descriptors that use hardened derivation will "
902
883
        "only have a limited number of derived keys included in the export due to hardened "
903
883
        "derivation requiring private keys. Descriptors with unhardened derivation do not have "
904
883
        "this limitation. The watchonly wallet can be imported into another node using 'restorewallet'.",
905
883
        {
906
883
            {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the filename the exported watchonly wallet will be saved to"},
907
883
        },
908
883
        RPCResult{
909
883
            RPCResult::Type::OBJ, "", "",
910
883
            {
911
883
                {RPCResult::Type::STR, "exported_file", "The full path that the file has been exported to"},
912
883
            },
913
883
        },
914
883
        RPCExamples{
915
883
            HelpExampleCli("exportwatchonlywallet", "\"/path/to/export.dat\"")
916
883
            + HelpExampleRpc("exportwatchonlywallet", "\"/path/to/export.dat\"")
917
883
        },
918
883
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
919
883
        {
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
883
    };
937
883
}
938
939
RPCMethod derivehdkey()
940
902
{
941
902
    return RPCMethod{
942
902
        "derivehdkey",
943
902
        "Derive extended public or private key from HD key in the wallet at a given path.\n"
944
902
        "Derivation uses wallet private key material.\n"
945
902
        + HELP_REQUIRING_PASSPHRASE,
946
902
        {
947
902
            {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "BIP 32 derivation path with at least one hardened step."},
948
902
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
949
902
                {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private key"},
950
902
                {"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
902
            }},
952
902
        },
953
902
        RPCResult{
954
902
            RPCResult::Type::OBJ, "", "", {
955
902
                {RPCResult::Type::STR, "origin", "Fingerprint and path for use in descriptors"},
956
902
                {RPCResult::Type::STR, "xpub", "The extended public key"},
957
902
                {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
958
902
            },
959
902
        },
960
902
        RPCExamples{
961
902
            HelpExampleCli("derivehdkey", "m/87h/0h/0h") + HelpExampleRpc("derivehdkey", "\"m/87h/0h/0h\"")
962
902
            + HelpExampleCliNamed("derivehdkey", {{"path", "m/87h/0h/0h"}, {"private", "true"}})
963
902
            + HelpExampleRpcNamed("derivehdkey", {{"path", "m/87h/0h/0h"}, {"private", "true"}})
964
902
        },
965
902
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
966
902
        {
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
902
    };
1055
902
}
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 listrawtransactions();
1113
RPCMethod listsinceblock();
1114
RPCMethod gettransaction();
1115
RPCMethod abandontransaction();
1116
RPCMethod rescanblockchain();
1117
RPCMethod abortrescan();
1118
1119
std::span<const CRPCCommand> GetWalletRPCCommands()
1120
445
{
1121
445
    static const CRPCCommand commands[]{
1122
445
        {"rawtransactions", &fundrawtransaction},
1123
445
        {"wallet", &abandontransaction},
1124
445
        {"wallet", &abortrescan},
1125
445
        {"wallet", &addhdkey},
1126
445
        {"wallet", &backupwallet},
1127
445
        {"wallet", &bumpfee},
1128
445
        {"wallet", &psbtbumpfee},
1129
445
        {"wallet", &createwallet},
1130
445
        {"wallet", &createwalletdescriptor},
1131
445
        {"wallet", &derivehdkey},
1132
445
        {"wallet", &restorewallet},
1133
445
        {"wallet", &encryptwallet},
1134
445
        {"wallet", &exportwatchonlywallet},
1135
445
        {"wallet", &getaddressesbylabel},
1136
445
        {"wallet", &getaddressinfo},
1137
445
        {"wallet", &getbalance},
1138
445
        {"wallet", &gethdkeys},
1139
445
        {"wallet", &getnewaddress},
1140
445
        {"wallet", &getrawchangeaddress},
1141
445
        {"wallet", &getreceivedbyaddress},
1142
445
        {"wallet", &getreceivedbylabel},
1143
445
        {"wallet", &gettransaction},
1144
445
        {"wallet", &getbalances},
1145
445
        {"wallet", &getwalletinfo},
1146
445
        {"wallet", &importdescriptors},
1147
445
        {"wallet", &importprunedfunds},
1148
445
        {"wallet", &keypoolrefill},
1149
445
        {"wallet", &listaddressgroupings},
1150
445
        {"wallet", &listdescriptors},
1151
445
        {"wallet", &listlabels},
1152
445
        {"wallet", &listlockunspent},
1153
445
        {"wallet", &listreceivedbyaddress},
1154
445
        {"wallet", &listreceivedbylabel},
1155
445
        {"wallet", &listsinceblock},
1156
445
        {"wallet", &listtransactions},
1157
445
        {"wallet", &listrawtransactions},
1158
445
        {"wallet", &listunspent},
1159
445
        {"wallet", &listwalletdir},
1160
445
        {"wallet", &listwallets},
1161
445
        {"wallet", &loadwallet},
1162
445
        {"wallet", &lockunspent},
1163
445
        {"wallet", &migratewallet},
1164
445
        {"wallet", &removeprunedfunds},
1165
445
        {"wallet", &rescanblockchain},
1166
445
        {"wallet", &send},
1167
445
        {"wallet", &sendmany},
1168
445
        {"wallet", &sendtoaddress},
1169
445
        {"wallet", &setlabel},
1170
445
        {"wallet", &setwalletflag},
1171
445
        {"wallet", &signmessage},
1172
445
        {"wallet", &signrawtransactionwithwallet},
1173
445
        {"wallet", &simulaterawtransaction},
1174
445
        {"wallet", &sendall},
1175
445
        {"wallet", &unloadwallet},
1176
445
        {"wallet", &walletcreatefundedpsbt},
1177
445
#ifdef ENABLE_EXTERNAL_SIGNER
1178
445
        {"wallet", &walletdisplayaddress},
1179
445
#endif // ENABLE_EXTERNAL_SIGNER
1180
445
        {"wallet", &walletlock},
1181
445
        {"wallet", &walletpassphrase},
1182
445
        {"wallet", &walletpassphrasechange},
1183
445
        {"wallet", &walletprocesspsbt},
1184
445
    };
1185
445
    return commands;
1186
445
}
1187
} // namespace wallet