Coverage Report

Created: 2026-06-16 16:41

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.25k
{
38
1.25k
    return RPCMethod{"getwalletinfo",
39
1.25k
                "Returns an object containing various wallet state info.\n",
40
1.25k
                {},
41
1.25k
                RPCResult{
42
1.25k
                    RPCResult::Type::OBJ, "", "",
43
1.25k
                    {
44
1.25k
                        {
45
1.25k
                        {RPCResult::Type::STR, "walletname", "the wallet name"},
46
1.25k
                        {RPCResult::Type::NUM, "walletversion", "(DEPRECATED) only related to unsupported legacy wallet, returns the latest version 169900 for backwards compatibility"},
47
1.25k
                        {RPCResult::Type::STR, "format", "the database format (only sqlite)"},
48
1.25k
                        {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
49
1.25k
                        {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
50
1.25k
                        {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.25k
                        {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.25k
                        {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
53
1.25k
                        {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
54
1.25k
                        {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
55
1.25k
                        {
56
1.25k
                            {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
57
1.25k
                            {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
58
1.25k
                        }, {.skip_type_check=true}, },
59
1.25k
                        {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
60
1.25k
                        {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
61
1.25k
                        {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
62
1.25k
                        {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.25k
                        {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet",
64
1.25k
                        {
65
1.25k
                            {RPCResult::Type::STR, "flag", "The name of the flag"},
66
1.25k
                        }},
67
1.25k
                        RESULT_LAST_PROCESSED_BLOCK,
68
1.25k
                    }},
69
1.25k
                },
70
1.25k
                RPCExamples{
71
1.25k
                    HelpExampleCli("getwalletinfo", "")
72
1.25k
            + HelpExampleRpc("getwalletinfo", "")
73
1.25k
                },
74
1.25k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
75
1.25k
{
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.25k
    };
136
1.25k
}
137
138
static RPCMethod listwalletdir()
139
887
{
140
887
    return RPCMethod{"listwalletdir",
141
887
                "Returns a list of wallets in the wallet directory.\n",
142
887
                {},
143
887
                RPCResult{
144
887
                    RPCResult::Type::OBJ, "", "",
145
887
                    {
146
887
                        {RPCResult::Type::ARR, "wallets", "",
147
887
                        {
148
887
                            {RPCResult::Type::OBJ, "", "",
149
887
                            {
150
887
                                {RPCResult::Type::STR, "name", "The wallet name"},
151
887
                                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
152
887
                                {
153
887
                                    {RPCResult::Type::STR, "", ""},
154
887
                                }},
155
887
                            }},
156
887
                        }},
157
887
                    }
158
887
                },
159
887
                RPCExamples{
160
887
                    HelpExampleCli("listwalletdir", "")
161
887
            + HelpExampleRpc("listwalletdir", "")
162
887
                },
163
887
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
164
887
{
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
887
    };
182
887
}
183
184
static RPCMethod listwallets()
185
888
{
186
888
    return RPCMethod{"listwallets",
187
888
                "Returns a list of currently loaded wallets.\n"
188
888
                "For full information on the wallet, use \"getwalletinfo\"\n",
189
888
                {},
190
888
                RPCResult{
191
888
                    RPCResult::Type::ARR, "", "",
192
888
                    {
193
888
                        {RPCResult::Type::STR, "walletname", "the wallet name"},
194
888
                    }
195
888
                },
196
888
                RPCExamples{
197
888
                    HelpExampleCli("listwallets", "")
198
888
            + HelpExampleRpc("listwallets", "")
199
888
                },
200
888
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
201
888
{
202
75
    UniValue obj(UniValue::VARR);
203
204
75
    WalletContext& context = EnsureWalletContext(request.context);
205
526
    for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
206
526
        LOCK(wallet->cs_wallet);
207
526
        obj.push_back(wallet->GetName());
208
526
    }
209
210
75
    return obj;
211
75
},
212
888
    };
213
888
}
214
215
static RPCMethod loadwallet()
216
968
{
217
968
    return RPCMethod{
218
968
        "loadwallet",
219
968
        "Loads a wallet from a wallet file or directory."
220
968
                "\nNote that all wallet command-line options used when starting bitcoind will be"
221
968
                "\napplied to the new wallet.\n",
222
968
                {
223
968
                    {"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
968
                    {"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
968
                },
226
968
                RPCResult{
227
968
                    RPCResult::Type::OBJ, "", "",
228
968
                    {
229
968
                        {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
230
968
                        {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
231
968
                        {
232
968
                            {RPCResult::Type::STR, "", ""},
233
968
                        }},
234
968
                    }
235
968
                },
236
968
                RPCExamples{
237
968
                    "\nLoad wallet from the wallet dir:\n"
238
968
                    + HelpExampleCli("loadwallet", "\"walletname\"")
239
968
                    + HelpExampleRpc("loadwallet", "\"walletname\"")
240
968
                    + "\nLoad wallet using absolute path (Unix):\n"
241
968
                    + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
242
968
                    + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
243
968
                    + "\nLoad wallet using absolute path (Windows):\n"
244
968
                    + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
245
968
                    + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
246
968
                },
247
968
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
248
968
{
249
155
    WalletContext& context = EnsureWalletContext(request.context);
250
155
    const std::string name(request.params[0].get_str());
251
252
155
    DatabaseOptions options;
253
155
    DatabaseStatus status;
254
155
    ReadDatabaseArgs(*context.args, options);
255
155
    options.require_existing = true;
256
155
    bilingual_str error;
257
155
    std::vector<bilingual_str> warnings;
258
155
    std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
259
260
155
    {
261
155
        LOCK(context.wallets_mutex);
262
638
        if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
263
2
            throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
264
2
        }
265
155
    }
266
267
153
    std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
268
269
153
    HandleWalletError(wallet, status, error);
270
271
153
    UniValue obj(UniValue::VOBJ);
272
153
    obj.pushKV("name", wallet->GetName());
273
153
    PushWarnings(warnings, obj);
274
275
153
    return obj;
276
155
},
277
968
    };
278
968
}
279
280
static RPCMethod setwalletflag()
281
821
{
282
821
            std::string flags;
283
821
            for (auto& it : STRING_TO_WALLET_FLAG)
284
5.74k
                if (it.second & MUTABLE_WALLET_FLAGS)
285
821
                    flags += (flags == "" ? "" : ", ") + it.first;
286
287
821
    return RPCMethod{
288
821
        "setwalletflag",
289
821
        "Change the state of the given wallet flag for a wallet.\n",
290
821
                {
291
821
                    {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
292
821
                    {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
293
821
                },
294
821
                RPCResult{
295
821
                    RPCResult::Type::OBJ, "", "",
296
821
                    {
297
821
                        {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
298
821
                        {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
299
821
                        {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
300
821
                    }
301
821
                },
302
821
                RPCExamples{
303
821
                    HelpExampleCli("setwalletflag", "avoid_reuse")
304
821
                  + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
305
821
                },
306
821
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
307
821
{
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
821
    };
346
821
}
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
631
    WalletContext& context = EnsureWalletContext(request.context);
382
631
    uint64_t flags = 0;
383
631
    if (!request.params[1].isNull() && request.params[1].get_bool()) {
384
110
        flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
385
110
    }
386
387
631
    if (!request.params[2].isNull() && request.params[2].get_bool()) {
388
166
        flags |= WALLET_FLAG_BLANK_WALLET;
389
166
    }
390
631
    SecureString passphrase;
391
631
    passphrase.reserve(100);
392
631
    std::vector<bilingual_str> warnings;
393
631
    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
631
    if (!request.params[4].isNull() && request.params[4].get_bool()) {
402
3
        flags |= WALLET_FLAG_AVOID_REUSE;
403
3
    }
404
631
    flags |= WALLET_FLAG_DESCRIPTORS;
405
631
    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
629
    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
629
    DatabaseOptions options;
417
629
    DatabaseStatus status;
418
629
    ReadDatabaseArgs(*context.args, options);
419
629
    options.require_create = true;
420
629
    options.create_flags = flags;
421
629
    options.create_passphrase = passphrase;
422
629
    bilingual_str error;
423
629
    std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
424
629
    const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
425
629
    HandleWalletError(wallet, status, error);
426
427
629
    UniValue obj(UniValue::VOBJ);
428
629
    obj.pushKV("name", wallet->GetName());
429
629
    PushWarnings(warnings, obj);
430
431
629
    return obj;
432
631
},
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
839
{
493
839
    return RPCMethod{
494
839
        "simulaterawtransaction",
495
839
        "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
496
839
        {
497
839
            {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
498
839
                {
499
839
                    {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
500
839
                },
501
839
            },
502
839
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
503
839
                {
504
839
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
505
839
                },
506
839
            },
507
839
        },
508
839
        RPCResult{
509
839
            RPCResult::Type::OBJ, "", "",
510
839
            {
511
839
                {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
512
839
            }
513
839
        },
514
839
        RPCExamples{
515
839
            HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
516
839
            + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
517
839
        },
518
839
    [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
519
839
{
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
839
    };
582
839
}
583
584
static RPCMethod migratewallet()
585
862
{
586
862
    return RPCMethod{
587
862
        "migratewallet",
588
862
        "Migrate the wallet to a descriptor wallet.\n"
589
862
        "A new wallet backup will need to be made.\n"
590
862
        "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
591
862
        "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
592
862
        "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
593
862
        "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
594
862
        "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
595
862
        {
596
862
            {"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
862
            {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
598
862
        },
599
862
        RPCResult{
600
862
            RPCResult::Type::OBJ, "", "",
601
862
            {
602
862
                {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
603
862
                {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
604
862
                {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
605
862
                {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
606
862
            }
607
862
        },
608
862
        RPCExamples{
609
862
            HelpExampleCli("migratewallet", "")
610
862
            + HelpExampleRpc("migratewallet", "")
611
862
        },
612
862
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
613
862
        {
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
862
    };
641
862
}
642
643
RPCMethod gethdkeys()
644
853
{
645
853
    return RPCMethod{
646
853
        "gethdkeys",
647
853
        "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
648
853
        {
649
853
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
650
853
                {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
651
853
                {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
652
853
            }},
653
853
        },
654
853
        RPCResult{RPCResult::Type::ARR, "", "", {
655
853
            {
656
853
                {RPCResult::Type::OBJ, "", "", {
657
853
                    {RPCResult::Type::STR, "xpub", "The extended public key"},
658
853
                    {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
659
853
                    {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
660
853
                    {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
661
853
                    {
662
853
                        {RPCResult::Type::OBJ, "", "", {
663
853
                            {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
664
853
                            {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
665
853
                        }},
666
853
                    }},
667
853
                }},
668
853
            }
669
853
        }},
670
853
        RPCExamples{
671
853
            HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
672
853
            + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
673
853
        },
674
853
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
675
853
        {
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
853
    };
745
853
}
746
747
static RPCMethod createwalletdescriptor()
748
826
{
749
826
    return RPCMethod{"createwalletdescriptor",
750
826
        "Creates the wallet's descriptor for the given address type. "
751
826
        "The address type must be one that the wallet does not already have a descriptor for."
752
826
        + HELP_REQUIRING_PASSPHRASE,
753
826
        {
754
826
            {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
755
826
            {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
756
826
                {"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
826
                {"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
826
            }},
759
826
        },
760
826
        RPCResult{
761
826
            RPCResult::Type::OBJ, "", "",
762
826
            {
763
826
                {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
764
826
                    {{RPCResult::Type::STR, "", ""}}
765
826
                }
766
826
            },
767
826
        },
768
826
        RPCExamples{
769
826
            HelpExampleCli("createwalletdescriptor", "bech32m")
770
826
            + HelpExampleRpc("createwalletdescriptor", "bech32m")
771
826
        },
772
826
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
773
826
        {
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
826
    };
843
826
}
844
845
RPCMethod addhdkey()
846
818
{
847
818
    return RPCMethod{
848
818
        "addhdkey",
849
818
        "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
850
818
        {
851
818
            {"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
818
        },
853
818
        RPCResult{
854
818
            RPCResult::Type::OBJ, "", "",
855
818
            {
856
818
                {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
857
818
            },
858
818
        },
859
818
        RPCExamples{
860
818
            HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
861
818
        },
862
818
        [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
863
818
        {
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
818
    };
919
818
}
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
415
{
984
415
    static const CRPCCommand commands[]{
985
415
        {"rawtransactions", &fundrawtransaction},
986
415
        {"wallet", &abandontransaction},
987
415
        {"wallet", &abortrescan},
988
415
        {"wallet", &addhdkey},
989
415
        {"wallet", &backupwallet},
990
415
        {"wallet", &bumpfee},
991
415
        {"wallet", &psbtbumpfee},
992
415
        {"wallet", &createwallet},
993
415
        {"wallet", &createwalletdescriptor},
994
415
        {"wallet", &restorewallet},
995
415
        {"wallet", &encryptwallet},
996
415
        {"wallet", &getaddressesbylabel},
997
415
        {"wallet", &getaddressinfo},
998
415
        {"wallet", &getbalance},
999
415
        {"wallet", &gethdkeys},
1000
415
        {"wallet", &getnewaddress},
1001
415
        {"wallet", &getrawchangeaddress},
1002
415
        {"wallet", &getreceivedbyaddress},
1003
415
        {"wallet", &getreceivedbylabel},
1004
415
        {"wallet", &gettransaction},
1005
415
        {"wallet", &getbalances},
1006
415
        {"wallet", &getwalletinfo},
1007
415
        {"wallet", &importdescriptors},
1008
415
        {"wallet", &importprunedfunds},
1009
415
        {"wallet", &keypoolrefill},
1010
415
        {"wallet", &listaddressgroupings},
1011
415
        {"wallet", &listdescriptors},
1012
415
        {"wallet", &listlabels},
1013
415
        {"wallet", &listlockunspent},
1014
415
        {"wallet", &listreceivedbyaddress},
1015
415
        {"wallet", &listreceivedbylabel},
1016
415
        {"wallet", &listsinceblock},
1017
415
        {"wallet", &listtransactions},
1018
415
        {"wallet", &listunspent},
1019
415
        {"wallet", &listwalletdir},
1020
415
        {"wallet", &listwallets},
1021
415
        {"wallet", &loadwallet},
1022
415
        {"wallet", &lockunspent},
1023
415
        {"wallet", &migratewallet},
1024
415
        {"wallet", &removeprunedfunds},
1025
415
        {"wallet", &rescanblockchain},
1026
415
        {"wallet", &send},
1027
415
        {"wallet", &sendmany},
1028
415
        {"wallet", &sendtoaddress},
1029
415
        {"wallet", &setlabel},
1030
415
        {"wallet", &setwalletflag},
1031
415
        {"wallet", &signmessage},
1032
415
        {"wallet", &signrawtransactionwithwallet},
1033
415
        {"wallet", &simulaterawtransaction},
1034
415
        {"wallet", &sendall},
1035
415
        {"wallet", &unloadwallet},
1036
415
        {"wallet", &walletcreatefundedpsbt},
1037
415
#ifdef ENABLE_EXTERNAL_SIGNER
1038
415
        {"wallet", &walletdisplayaddress},
1039
415
#endif // ENABLE_EXTERNAL_SIGNER
1040
415
        {"wallet", &walletlock},
1041
415
        {"wallet", &walletpassphrase},
1042
415
        {"wallet", &walletpassphrasechange},
1043
415
        {"wallet", &walletprocesspsbt},
1044
415
    };
1045
415
    return commands;
1046
415
}
1047
} // namespace wallet