Coverage Report

Created: 2026-08-14 20:23

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