Coverage Report

Created: 2026-10-07 13:27

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
947
{
148
947
    return RPCMethod{"listwalletdir",
149
947
                "Returns a list of wallets in the wallet directory.\n",
150
947
                {},
151
947
                RPCResult{
152
947
                    RPCResult::Type::OBJ, "", "",
153
947
                    {
154
947
                        {RPCResult::Type::ARR, "wallets", "",
155
947
                        {
156
947
                            {RPCResult::Type::OBJ, "", "",
157
947
                            {
158
947
                                {RPCResult::Type::STR, "name", "The wallet name"},
159
947
                                {RPCResult::Type::ARR, "warnings", "Warning messages related to loading the wallet (may be empty).",
160
947
                                {
161
947
                                    {RPCResult::Type::STR, "", ""},
162
947
                                }},
163
947
                            }},
164
947
                        }},
165
947
                    }
166
947
                },
167
947
                RPCExamples{
168
947
                    HelpExampleCli("listwalletdir", "")
169
947
            + HelpExampleRpc("listwalletdir", "")
170
947
                },
171
947
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
172
947
{
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
947
    };
190
947
}
191
192
static RPCMethod listwallets()
193
964
{
194
964
    return RPCMethod{"listwallets",
195
964
                "Returns a list of currently loaded wallets.\n"
196
964
                "For full information on the wallet, use \"getwalletinfo\"\n",
197
964
                {},
198
964
                RPCResult{
199
964
                    RPCResult::Type::ARR, "", "",
200
964
                    {
201
964
                        {RPCResult::Type::STR, "walletname", "the wallet name"},
202
964
                    }
203
964
                },
204
964
                RPCExamples{
205
964
                    HelpExampleCli("listwallets", "")
206
964
            + HelpExampleRpc("listwallets", "")
207
964
                },
208
964
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
209
964
{
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
964
    };
221
964
}
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
193
    WalletContext& context = EnsureWalletContext(request.context);
258
193
    const std::string name(request.params[0].get_str());
259
260
193
    DatabaseOptions options;
261
193
    DatabaseStatus status;
262
193
    ReadDatabaseArgs(*context.args, options);
263
193
    options.require_existing = true;
264
193
    bilingual_str error;
265
193
    std::vector<bilingual_str> warnings;
266
193
    std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
267
268
193
    {
269
193
        LOCK(context.wallets_mutex);
270
696
        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
193
    }
274
275
191
    std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
276
277
191
    HandleWalletError(wallet, status, error);
278
279
191
    UniValue obj(UniValue::VOBJ);
280
191
    obj.pushKV("name", wallet->GetName());
281
191
    PushWarnings(warnings, obj);
282
283
191
    return obj;
284
193
},
285
1.06k
    };
286
1.06k
}
287
288
static RPCMethod setwalletflag()
289
881
{
290
881
            std::string flags;
291
881
            for (auto& it : STRING_TO_WALLET_FLAG)
292
6.16k
                if (it.second & MUTABLE_WALLET_FLAGS)
293
881
                    flags += (flags == "" ? "" : ", ") + it.first;
294
295
881
    return RPCMethod{
296
881
        "setwalletflag",
297
881
        "Change the state of the given wallet flag for a wallet.\n",
298
881
                {
299
881
                    {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
300
881
                    {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
301
881
                },
302
881
                RPCResult{
303
881
                    RPCResult::Type::OBJ, "", "",
304
881
                    {
305
881
                        {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
306
881
                        {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
307
881
                        {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
308
881
                    }
309
881
                },
310
881
                RPCExamples{
311
881
                    HelpExampleCli("setwalletflag", "avoid_reuse")
312
881
                  + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
313
881
                },
314
881
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
315
881
{
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
881
    };
354
881
}
355
356
static RPCMethod createwallet()
357
1.55k
{
358
1.55k
    return RPCMethod{
359
1.55k
        "createwallet",
360
1.55k
        "Creates and loads a new wallet.\n",
361
1.55k
        {
362
1.55k
            {"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.55k
            {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
364
1.55k
            {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys."},
365
1.55k
            {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
366
1.55k
            {"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.55k
            {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""},
368
1.55k
            {"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.55k
            {"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.55k
        },
371
1.55k
        RPCResult{
372
1.55k
            RPCResult::Type::OBJ, "", "",
373
1.55k
            {
374
1.55k
                {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.55k
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
376
1.55k
                {
377
1.55k
                    {RPCResult::Type::STR, "", ""},
378
1.55k
                }},
379
1.55k
            }
380
1.55k
        },
381
1.55k
        RPCExamples{
382
1.55k
            HelpExampleCli("createwallet", "\"testwallet\"")
383
1.55k
            + HelpExampleRpc("createwallet", "\"testwallet\"")
384
1.55k
            + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
385
1.55k
            + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
386
1.55k
        },
387
1.55k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
388
1.55k
{
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.55k
    };
442
1.55k
}
443
444
static RPCMethod unloadwallet()
445
1.23k
{
446
1.23k
    return RPCMethod{"unloadwallet",
447
1.23k
                "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n"
448
1.23k
                "If both are specified, they must be identical.",
449
1.23k
                {
450
1.23k
                    {"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.23k
                    {"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.23k
                },
453
1.23k
                RPCResult{RPCResult::Type::OBJ, "", "", {
454
1.23k
                    {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
455
1.23k
                    {
456
1.23k
                        {RPCResult::Type::STR, "", ""},
457
1.23k
                    }},
458
1.23k
                }},
459
1.23k
                RPCExamples{
460
1.23k
                    HelpExampleCli("unloadwallet", "wallet_name")
461
1.23k
            + HelpExampleRpc("unloadwallet", R"("wallet_name")")
462
1.23k
                },
463
1.23k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
464
1.23k
{
465
357
    const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
466
467
357
    WalletContext& context = EnsureWalletContext(request.context);
468
357
    std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
469
357
    if (!wallet) {
470
4
        throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
471
4
    }
472
473
353
    std::vector<bilingual_str> warnings;
474
353
    {
475
353
        WalletRescanReserver reserver(*wallet);
476
353
        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
353
        std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
484
353
        if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
485
0
            throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
486
0
        }
487
353
    }
488
489
353
    WaitForDeleteWallet(std::move(wallet));
490
491
353
    UniValue result(UniValue::VOBJ);
492
353
    PushWarnings(warnings, result);
493
494
353
    return result;
495
353
},
496
1.23k
    };
497
1.23k
}
498
499
RPCMethod simulaterawtransaction()
500
899
{
501
899
    return RPCMethod{
502
899
        "simulaterawtransaction",
503
899
        "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
504
899
        {
505
899
            {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.\n",
506
899
                {
507
899
                    {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
508
899
                },
509
899
            },
510
899
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
511
899
                {
512
899
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
513
899
                },
514
899
            },
515
899
        },
516
899
        RPCResult{
517
899
            RPCResult::Type::OBJ, "", "",
518
899
            {
519
899
                {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
520
899
            }
521
899
        },
522
899
        RPCExamples{
523
899
            HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
524
899
            + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
525
899
        },
526
899
    [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
527
899
{
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
899
    };
590
899
}
591
592
static RPCMethod migratewallet()
593
930
{
594
930
    return RPCMethod{
595
930
        "migratewallet",
596
930
        "Migrate the wallet to a descriptor wallet.\n"
597
930
        "A new wallet backup will need to be made.\n"
598
930
        "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
599
930
        "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
600
930
        "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
601
930
        "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
602
930
        "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
603
930
        {
604
930
            {"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
930
            {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
606
930
            {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration."},
607
930
        },
608
930
        RPCResult{
609
930
            RPCResult::Type::OBJ, "", "",
610
930
            {
611
930
                {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
612
930
                {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
613
930
                {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
614
930
                {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
615
930
            }
616
930
        },
617
930
        RPCExamples{
618
930
            HelpExampleCli("migratewallet", "")
619
930
            + HelpExampleRpc("migratewallet", "")
620
930
        },
621
930
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
622
930
        {
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
930
    };
652
930
}
653
654
RPCMethod gethdkeys()
655
915
{
656
915
    return RPCMethod{
657
915
        "gethdkeys",
658
915
        "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
659
915
        {
660
915
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
661
915
                {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
662
915
                {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
663
915
            }},
664
915
        },
665
915
        RPCResult{RPCResult::Type::ARR, "", "", {
666
915
            {
667
915
                {RPCResult::Type::OBJ, "", "", {
668
915
                    {RPCResult::Type::STR, "xpub", "The extended public key"},
669
915
                    {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
670
915
                    {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
671
915
                    {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
672
915
                    {
673
915
                        {RPCResult::Type::OBJ, "", "", {
674
915
                            {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
675
915
                            {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
676
915
                        }},
677
915
                    }},
678
915
                }},
679
915
            }
680
915
        }},
681
915
        RPCExamples{
682
915
            HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
683
915
            + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
684
915
        },
685
915
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
686
915
        {
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
915
    };
740
915
}
741
742
static RPCMethod createwalletdescriptor()
743
887
{
744
887
    return RPCMethod{"createwalletdescriptor",
745
887
        "Creates the wallet's descriptor for the given address type. "
746
887
        "The address type must be one that the wallet does not already have a descriptor for."
747
887
        + HELP_REQUIRING_PASSPHRASE,
748
887
        {
749
887
            {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
750
887
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
751
887
                {"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
887
                {"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
887
            }},
754
887
        },
755
887
        RPCResult{
756
887
            RPCResult::Type::OBJ, "", "",
757
887
            {
758
887
                {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
759
887
                    {{RPCResult::Type::STR, "", ""}}
760
887
                }
761
887
            },
762
887
        },
763
887
        RPCExamples{
764
887
            HelpExampleCli("createwalletdescriptor", "bech32m")
765
887
            + HelpExampleRpc("createwalletdescriptor", R"("bech32m")")
766
887
        },
767
887
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
768
887
        {
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
887
    };
837
887
}
838
839
RPCMethod addhdkey()
840
886
{
841
886
    return RPCMethod{
842
886
        "addhdkey",
843
886
        "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
844
886
        {
845
886
            {"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
886
        },
847
886
        RPCResult{
848
886
            RPCResult::Type::OBJ, "", "",
849
886
            {
850
886
                {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
851
886
            },
852
886
        },
853
886
        RPCExamples{
854
886
            HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", R"("xprv")")
855
886
        },
856
886
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
857
886
        {
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
886
    };
894
886
}
895
896
static RPCMethod exportwatchonlywallet()
897
885
{
898
885
    return RPCMethod{"exportwatchonlywallet",
899
885
        "Creates a wallet file at the specified destination containing a watchonly version "
900
885
        "of the current wallet. This watchonly wallet contains the wallet's public descriptors, "
901
885
        "its transactions, and address book data. Descriptors that use hardened derivation will "
902
885
        "only have a limited number of derived keys included in the export due to hardened "
903
885
        "derivation requiring private keys. Descriptors with unhardened derivation do not have "
904
885
        "this limitation. The watchonly wallet can be imported into another node using 'restorewallet'.",
905
885
        {
906
885
            {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the filename the exported watchonly wallet will be saved to"},
907
885
        },
908
885
        RPCResult{
909
885
            RPCResult::Type::OBJ, "", "",
910
885
            {
911
885
                {RPCResult::Type::STR, "exported_file", "The full path that the file has been exported to"},
912
885
            },
913
885
        },
914
885
        RPCExamples{
915
885
            HelpExampleCli("exportwatchonlywallet", "\"/path/to/export.dat\"")
916
885
            + HelpExampleRpc("exportwatchonlywallet", "\"/path/to/export.dat\"")
917
885
        },
918
885
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
919
885
        {
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
885
    };
937
885
}
938
939
RPCMethod derivehdkey()
940
904
{
941
904
    return RPCMethod{
942
904
        "derivehdkey",
943
904
        "Derive extended public or private key from HD key in the wallet at a given path.\n"
944
904
        "Derivation uses wallet private key material.\n"
945
904
        + HELP_REQUIRING_PASSPHRASE,
946
904
        {
947
904
            {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "BIP 32 derivation path with at least one hardened step."},
948
904
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
949
904
                {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private key"},
950
904
                {"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
904
            }},
952
904
        },
953
904
        RPCResult{
954
904
            RPCResult::Type::OBJ, "", "", {
955
904
                {RPCResult::Type::STR, "origin", "Fingerprint and path for use in descriptors"},
956
904
                {RPCResult::Type::STR, "xpub", "The extended public key"},
957
904
                {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
958
904
            },
959
904
        },
960
904
        RPCExamples{
961
904
            HelpExampleCli("derivehdkey", "m/87h/0h/0h") + HelpExampleRpc("derivehdkey", "\"m/87h/0h/0h\"")
962
904
            + HelpExampleCliNamed("derivehdkey", {{"path", "m/87h/0h/0h"}, {"private", "true"}})
963
904
            + HelpExampleRpcNamed("derivehdkey", {{"path", "m/87h/0h/0h"}, {"private", "true"}})
964
904
        },
965
904
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
966
904
        {
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
904
    };
1055
904
}
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
446
{
1121
446
    static const CRPCCommand commands[]{
1122
446
        {"rawtransactions", &fundrawtransaction},
1123
446
        {"wallet", &abandontransaction},
1124
446
        {"wallet", &abortrescan},
1125
446
        {"wallet", &addhdkey},
1126
446
        {"wallet", &backupwallet},
1127
446
        {"wallet", &bumpfee},
1128
446
        {"wallet", &psbtbumpfee},
1129
446
        {"wallet", &createwallet},
1130
446
        {"wallet", &createwalletdescriptor},
1131
446
        {"wallet", &derivehdkey},
1132
446
        {"wallet", &restorewallet},
1133
446
        {"wallet", &encryptwallet},
1134
446
        {"wallet", &exportwatchonlywallet},
1135
446
        {"wallet", &getaddressesbylabel},
1136
446
        {"wallet", &getaddressinfo},
1137
446
        {"wallet", &getbalance},
1138
446
        {"wallet", &gethdkeys},
1139
446
        {"wallet", &getnewaddress},
1140
446
        {"wallet", &getrawchangeaddress},
1141
446
        {"wallet", &getreceivedbyaddress},
1142
446
        {"wallet", &getreceivedbylabel},
1143
446
        {"wallet", &gettransaction},
1144
446
        {"wallet", &getbalances},
1145
446
        {"wallet", &getwalletinfo},
1146
446
        {"wallet", &importdescriptors},
1147
446
        {"wallet", &importprunedfunds},
1148
446
        {"wallet", &keypoolrefill},
1149
446
        {"wallet", &listaddressgroupings},
1150
446
        {"wallet", &listdescriptors},
1151
446
        {"wallet", &listlabels},
1152
446
        {"wallet", &listlockunspent},
1153
446
        {"wallet", &listreceivedbyaddress},
1154
446
        {"wallet", &listreceivedbylabel},
1155
446
        {"wallet", &listsinceblock},
1156
446
        {"wallet", &listtransactions},
1157
446
        {"wallet", &listrawtransactions},
1158
446
        {"wallet", &listunspent},
1159
446
        {"wallet", &listwalletdir},
1160
446
        {"wallet", &listwallets},
1161
446
        {"wallet", &loadwallet},
1162
446
        {"wallet", &lockunspent},
1163
446
        {"wallet", &migratewallet},
1164
446
        {"wallet", &removeprunedfunds},
1165
446
        {"wallet", &rescanblockchain},
1166
446
        {"wallet", &send},
1167
446
        {"wallet", &sendmany},
1168
446
        {"wallet", &sendtoaddress},
1169
446
        {"wallet", &setlabel},
1170
446
        {"wallet", &setwalletflag},
1171
446
        {"wallet", &signmessage},
1172
446
        {"wallet", &signrawtransactionwithwallet},
1173
446
        {"wallet", &simulaterawtransaction},
1174
446
        {"wallet", &sendall},
1175
446
        {"wallet", &unloadwallet},
1176
446
        {"wallet", &walletcreatefundedpsbt},
1177
446
#ifdef ENABLE_EXTERNAL_SIGNER
1178
446
        {"wallet", &walletdisplayaddress},
1179
446
#endif // ENABLE_EXTERNAL_SIGNER
1180
446
        {"wallet", &walletlock},
1181
446
        {"wallet", &walletpassphrase},
1182
446
        {"wallet", &walletpassphrasechange},
1183
446
        {"wallet", &walletprocesspsbt},
1184
446
    };
1185
446
    return commands;
1186
446
}
1187
} // namespace wallet