Coverage Report

Created: 2026-07-23 20:35

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