Coverage Report

Created: 2026-05-30 09:47

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