Coverage Report

Created: 2026-09-02 14:16

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