Coverage Report

Created: 2026-10-19 15:22

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