Coverage Report

Created: 2025-02-21 14:37

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