Coverage Report

Created: 2026-07-29 23:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/wallet/rpc/wallet.cpp
Line
Count
Source
1
// Copyright (c) 2010 Satoshi Nakamoto
2
// Copyright (c) 2009-present The Bitcoin Core developers
3
// Distributed under the MIT software license, see the accompanying
4
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6
#include <bitcoin-build-config.h> // IWYU pragma: keep
7
8
#include <wallet/rpc/wallet.h>
9
10
#include <coins.h>
11
#include <core_io.h>
12
#include <key_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
897
{
141
897
    return RPCMethod{"listwalletdir",
142
897
                "Returns a list of wallets in the wallet directory.\n",
143
897
                {},
144
897
                RPCResult{
145
897
                    RPCResult::Type::OBJ, "", "",
146
897
                    {
147
897
                        {RPCResult::Type::ARR, "wallets", "",
148
897
                        {
149
897
                            {RPCResult::Type::OBJ, "", "",
150
897
                            {
151
897
                                {RPCResult::Type::STR, "name", "The wallet name"},
152
897
                                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
153
897
                                {
154
897
                                    {RPCResult::Type::STR, "", ""},
155
897
                                }},
156
897
                            }},
157
897
                        }},
158
897
                    }
159
897
                },
160
897
                RPCExamples{
161
897
                    HelpExampleCli("listwalletdir", "")
162
897
            + HelpExampleRpc("listwalletdir", "")
163
897
                },
164
897
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
165
897
{
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
897
    };
183
897
}
184
185
static RPCMethod listwallets()
186
906
{
187
906
    return RPCMethod{"listwallets",
188
906
                "Returns a list of currently loaded wallets.\n"
189
906
                "For full information on the wallet, use \"getwalletinfo\"\n",
190
906
                {},
191
906
                RPCResult{
192
906
                    RPCResult::Type::ARR, "", "",
193
906
                    {
194
906
                        {RPCResult::Type::STR, "walletname", "the wallet name"},
195
906
                    }
196
906
                },
197
906
                RPCExamples{
198
906
                    HelpExampleCli("listwallets", "")
199
906
            + HelpExampleRpc("listwallets", "")
200
906
                },
201
906
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
202
906
{
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
906
    };
214
906
}
215
216
static RPCMethod loadwallet()
217
984
{
218
984
    return RPCMethod{
219
984
        "loadwallet",
220
984
        "Loads a wallet from a wallet file or directory."
221
984
                "\nNote that all wallet command-line options used when starting bitcoind will be"
222
984
                "\napplied to the new wallet.\n",
223
984
                {
224
984
                    {"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
984
                    {"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
984
                },
227
984
                RPCResult{
228
984
                    RPCResult::Type::OBJ, "", "",
229
984
                    {
230
984
                        {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
231
984
                        {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
232
984
                        {
233
984
                            {RPCResult::Type::STR, "", ""},
234
984
                        }},
235
984
                    }
236
984
                },
237
984
                RPCExamples{
238
984
                    "\nLoad wallet from the wallet dir:\n"
239
984
                    + HelpExampleCli("loadwallet", "\"walletname\"")
240
984
                    + HelpExampleRpc("loadwallet", "\"walletname\"")
241
984
                    + "\nLoad wallet using absolute path (Unix):\n"
242
984
                    + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
243
984
                    + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
244
984
                    + "\nLoad wallet using absolute path (Windows):\n"
245
984
                    + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
246
984
                    + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
247
984
                },
248
984
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
249
984
{
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
984
    };
279
984
}
280
281
static RPCMethod setwalletflag()
282
831
{
283
831
            std::string flags;
284
831
            for (auto& it : STRING_TO_WALLET_FLAG)
285
5.81k
                if (it.second & MUTABLE_WALLET_FLAGS)
286
831
                    flags += (flags == "" ? "" : ", ") + it.first;
287
288
831
    return RPCMethod{
289
831
        "setwalletflag",
290
831
        "Change the state of the given wallet flag for a wallet.\n",
291
831
                {
292
831
                    {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
293
831
                    {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
294
831
                },
295
831
                RPCResult{
296
831
                    RPCResult::Type::OBJ, "", "",
297
831
                    {
298
831
                        {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
299
831
                        {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
300
831
                        {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
301
831
                    }
302
831
                },
303
831
                RPCExamples{
304
831
                    HelpExampleCli("setwalletflag", "avoid_reuse")
305
831
                  + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
306
831
                },
307
831
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
308
831
{
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
831
    };
347
831
}
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
849
{
494
849
    return RPCMethod{
495
849
        "simulaterawtransaction",
496
849
        "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
497
849
        {
498
849
            {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
499
849
                {
500
849
                    {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
501
849
                },
502
849
            },
503
849
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
504
849
                {
505
849
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
506
849
                },
507
849
            },
508
849
        },
509
849
        RPCResult{
510
849
            RPCResult::Type::OBJ, "", "",
511
849
            {
512
849
                {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
513
849
            }
514
849
        },
515
849
        RPCExamples{
516
849
            HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
517
849
            + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
518
849
        },
519
849
    [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
520
849
{
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
849
    };
583
849
}
584
585
static RPCMethod migratewallet()
586
876
{
587
876
    return RPCMethod{
588
876
        "migratewallet",
589
876
        "Migrate the wallet to a descriptor wallet.\n"
590
876
        "A new wallet backup will need to be made.\n"
591
876
        "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
592
876
        "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
593
876
        "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
594
876
        "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
595
876
        "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
596
876
        {
597
876
            {"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
876
            {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
599
876
            {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration."},
600
876
        },
601
876
        RPCResult{
602
876
            RPCResult::Type::OBJ, "", "",
603
876
            {
604
876
                {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
605
876
                {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
606
876
                {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
607
876
                {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
608
876
            }
609
876
        },
610
876
        RPCExamples{
611
876
            HelpExampleCli("migratewallet", "")
612
876
            + HelpExampleRpc("migratewallet", "")
613
876
        },
614
876
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
615
876
        {
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
876
    };
645
876
}
646
647
RPCMethod gethdkeys()
648
863
{
649
863
    return RPCMethod{
650
863
        "gethdkeys",
651
863
        "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
652
863
        {
653
863
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
654
863
                {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
655
863
                {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
656
863
            }},
657
863
        },
658
863
        RPCResult{RPCResult::Type::ARR, "", "", {
659
863
            {
660
863
                {RPCResult::Type::OBJ, "", "", {
661
863
                    {RPCResult::Type::STR, "xpub", "The extended public key"},
662
863
                    {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
663
863
                    {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
664
863
                    {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
665
863
                    {
666
863
                        {RPCResult::Type::OBJ, "", "", {
667
863
                            {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
668
863
                            {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
669
863
                        }},
670
863
                    }},
671
863
                }},
672
863
            }
673
863
        }},
674
863
        RPCExamples{
675
863
            HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
676
863
            + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
677
863
        },
678
863
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
679
863
        {
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
863
    };
749
863
}
750
751
static RPCMethod createwalletdescriptor()
752
836
{
753
836
    return RPCMethod{"createwalletdescriptor",
754
836
        "Creates the wallet's descriptor for the given address type. "
755
836
        "The address type must be one that the wallet does not already have a descriptor for."
756
836
        + HELP_REQUIRING_PASSPHRASE,
757
836
        {
758
836
            {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
759
836
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
760
836
                {"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
836
                {"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
836
            }},
763
836
        },
764
836
        RPCResult{
765
836
            RPCResult::Type::OBJ, "", "",
766
836
            {
767
836
                {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
768
836
                    {{RPCResult::Type::STR, "", ""}}
769
836
                }
770
836
            },
771
836
        },
772
836
        RPCExamples{
773
836
            HelpExampleCli("createwalletdescriptor", "bech32m")
774
836
            + HelpExampleRpc("createwalletdescriptor", "bech32m")
775
836
        },
776
836
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
777
836
        {
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
836
    };
847
836
}
848
849
RPCMethod addhdkey()
850
828
{
851
828
    return RPCMethod{
852
828
        "addhdkey",
853
828
        "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
854
828
        {
855
828
            {"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
828
        },
857
828
        RPCResult{
858
828
            RPCResult::Type::OBJ, "", "",
859
828
            {
860
828
                {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
861
828
            },
862
828
        },
863
828
        RPCExamples{
864
828
            HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
865
828
        },
866
828
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
867
828
        {
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
828
    };
923
828
}
924
925
static RPCMethod exportwatchonlywallet()
926
835
{
927
835
    return RPCMethod{"exportwatchonlywallet",
928
835
        "Creates a wallet file at the specified destination containing a watchonly version "
929
835
        "of the current wallet. This watchonly wallet contains the wallet's public descriptors, "
930
835
        "its transactions, and address book data. Descriptors that use hardened derivation will "
931
835
        "only have a limited number of derived keys included in the export due to hardened "
932
835
        "derivation requiring private keys. Descriptors with unhardened derivation do not have "
933
835
        "this limitation. The watchonly wallet can be imported into another node using 'restorewallet'.",
934
835
        {
935
835
            {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the filename the exported watchonly wallet will be saved to"},
936
835
        },
937
835
        RPCResult{
938
835
            RPCResult::Type::OBJ, "", "",
939
835
            {
940
835
                {RPCResult::Type::STR, "exported_file", "The full path that the file has been exported to"},
941
835
            },
942
835
        },
943
835
        RPCExamples{
944
835
            HelpExampleCli("exportwatchonlywallet", "\"/path/to/export.dat\"")
945
835
            + HelpExampleRpc("exportwatchonlywallet", "\"/path/to/export.dat\"")
946
835
        },
947
835
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
948
835
        {
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
835
    };
966
835
}
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
420
{
1031
420
    static const CRPCCommand commands[]{
1032
420
        {"rawtransactions", &fundrawtransaction},
1033
420
        {"wallet", &abandontransaction},
1034
420
        {"wallet", &abortrescan},
1035
420
        {"wallet", &addhdkey},
1036
420
        {"wallet", &backupwallet},
1037
420
        {"wallet", &bumpfee},
1038
420
        {"wallet", &psbtbumpfee},
1039
420
        {"wallet", &createwallet},
1040
420
        {"wallet", &createwalletdescriptor},
1041
420
        {"wallet", &restorewallet},
1042
420
        {"wallet", &encryptwallet},
1043
420
        {"wallet", &exportwatchonlywallet},
1044
420
        {"wallet", &getaddressesbylabel},
1045
420
        {"wallet", &getaddressinfo},
1046
420
        {"wallet", &getbalance},
1047
420
        {"wallet", &gethdkeys},
1048
420
        {"wallet", &getnewaddress},
1049
420
        {"wallet", &getrawchangeaddress},
1050
420
        {"wallet", &getreceivedbyaddress},
1051
420
        {"wallet", &getreceivedbylabel},
1052
420
        {"wallet", &gettransaction},
1053
420
        {"wallet", &getbalances},
1054
420
        {"wallet", &getwalletinfo},
1055
420
        {"wallet", &importdescriptors},
1056
420
        {"wallet", &importprunedfunds},
1057
420
        {"wallet", &keypoolrefill},
1058
420
        {"wallet", &listaddressgroupings},
1059
420
        {"wallet", &listdescriptors},
1060
420
        {"wallet", &listlabels},
1061
420
        {"wallet", &listlockunspent},
1062
420
        {"wallet", &listreceivedbyaddress},
1063
420
        {"wallet", &listreceivedbylabel},
1064
420
        {"wallet", &listsinceblock},
1065
420
        {"wallet", &listtransactions},
1066
420
        {"wallet", &listunspent},
1067
420
        {"wallet", &listwalletdir},
1068
420
        {"wallet", &listwallets},
1069
420
        {"wallet", &loadwallet},
1070
420
        {"wallet", &lockunspent},
1071
420
        {"wallet", &migratewallet},
1072
420
        {"wallet", &removeprunedfunds},
1073
420
        {"wallet", &rescanblockchain},
1074
420
        {"wallet", &send},
1075
420
        {"wallet", &sendmany},
1076
420
        {"wallet", &sendtoaddress},
1077
420
        {"wallet", &setlabel},
1078
420
        {"wallet", &setwalletflag},
1079
420
        {"wallet", &signmessage},
1080
420
        {"wallet", &signrawtransactionwithwallet},
1081
420
        {"wallet", &simulaterawtransaction},
1082
420
        {"wallet", &sendall},
1083
420
        {"wallet", &unloadwallet},
1084
420
        {"wallet", &walletcreatefundedpsbt},
1085
420
#ifdef ENABLE_EXTERNAL_SIGNER
1086
420
        {"wallet", &walletdisplayaddress},
1087
420
#endif // ENABLE_EXTERNAL_SIGNER
1088
420
        {"wallet", &walletlock},
1089
420
        {"wallet", &walletpassphrase},
1090
420
        {"wallet", &walletpassphrasechange},
1091
420
        {"wallet", &walletprocesspsbt},
1092
420
    };
1093
420
    return commands;
1094
420
}
1095
} // namespace wallet