Branch data Line data Source code
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 : 1297 : static RPCMethod getwalletinfo()
44 : : {
45 : 1297 : return RPCMethod{"getwalletinfo",
46 [ + - ]: 2594 : "Returns an object containing various wallet state info.\n",
47 : : {},
48 [ + - ]: 2594 : RPCResult{
49 [ + - ]: 2594 : RPCResult::Type::OBJ, "", "",
50 : : {
51 : : {
52 [ + - + - ]: 2594 : {RPCResult::Type::STR, "walletname", "the wallet name"},
53 [ + - + - ]: 2594 : {RPCResult::Type::NUM, "walletversion", "(DEPRECATED) only related to unsupported legacy wallet, returns the latest version 169900 for backwards compatibility"},
54 [ + - + - ]: 2594 : {RPCResult::Type::STR, "format", "the database format (only sqlite)"},
55 [ + - + - ]: 2594 : {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
56 [ + - + - ]: 2594 : {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
57 [ + - + - ]: 2594 : {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 [ + - + - ]: 2594 : {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 [ + - + - ]: 2594 : {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
60 [ + - + - ]: 2594 : {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
61 [ + - + - ]: 2594 : {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
62 : : {
63 [ + - + - ]: 2594 : {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
64 [ + - + - ]: 2594 : {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
65 : : }, {.skip_type_check=true}, },
66 [ + - + - ]: 2594 : {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
67 [ + - + - ]: 2594 : {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
68 [ + - + - ]: 2594 : {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
69 [ + - + - ]: 2594 : {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 [ + - + - ]: 2594 : {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet",
71 : : {
72 [ + - + - ]: 2594 : {RPCResult::Type::STR, "flag", "The name of the flag"},
73 : : }},
74 : : RESULT_LAST_PROCESSED_BLOCK,
75 : : }},
76 [ + - + - : 53177 : },
+ - + - +
+ + + + +
- - - - -
- ]
77 : 1297 : RPCExamples{
78 [ + - + - : 2594 : HelpExampleCli("getwalletinfo", "")
+ - ]
79 [ + - + - : 5188 : + HelpExampleRpc("getwalletinfo", "")
+ - + - ]
80 [ + - ]: 1297 : },
81 : 1297 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
82 : : {
83 [ - + ]: 440 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
84 [ - + ]: 430 : 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 [ + - ]: 430 : pwallet->BlockUntilSyncedToCurrentChain();
89 : :
90 [ + - ]: 430 : LOCK(pwallet->cs_wallet);
91 : :
92 : 430 : UniValue obj(UniValue::VOBJ);
93 : :
94 : 430 : const int latest_legacy_wallet_minversion{169900};
95 : :
96 [ + - ]: 430 : size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
97 [ + - + - : 860 : obj.pushKV("walletname", pwallet->GetName());
+ - ]
98 [ + - + - : 860 : obj.pushKV("walletversion", latest_legacy_wallet_minversion);
+ - ]
99 [ + - + - : 860 : obj.pushKV("format", pwallet->GetDatabase().Format());
+ - + - ]
100 [ + - + - : 860 : obj.pushKV("txcount", pwallet->mapWallet.size());
+ - ]
101 [ + - + - : 860 : obj.pushKV("keypoolsize", kpExternalSize);
+ - ]
102 [ + - + - : 860 : obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
+ - + - ]
103 : :
104 [ + - + + ]: 430 : if (pwallet->HasEncryptionKeys()) {
105 [ + - + - : 68 : obj.pushKV("unlocked_until", pwallet->nRelockTime);
+ - ]
106 : : }
107 [ + - + - : 860 : obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ - + - ]
108 [ + - + - : 860 : obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
+ - + - ]
109 [ - + ]: 430 : 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 : 0 : } else {
115 [ + - + - : 860 : obj.pushKV("scanning", false);
+ - ]
116 : : }
117 [ + - + - : 860 : obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+ - + - ]
118 [ + - + - : 860 : obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
+ - + - ]
119 [ + - + - : 860 : obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
+ - + - ]
120 [ + + ]: 430 : if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
121 [ + - + - : 782 : obj.pushKV("birthtime", birthtime);
+ - ]
122 : : }
123 : :
124 : : // Push known flags
125 : 430 : UniValue flags(UniValue::VARR);
126 [ + - ]: 430 : uint64_t wallet_flags = pwallet->GetWalletFlags();
127 [ + + ]: 27950 : for (uint64_t i = 0; i < 64; ++i) {
128 : 27520 : uint64_t flag = uint64_t{1} << i;
129 [ + + ]: 27520 : if (flag & wallet_flags) {
130 [ + - ]: 1086 : if (flag & KNOWN_WALLET_FLAGS) {
131 [ + - + - : 1086 : flags.push_back(WALLET_FLAG_TO_STRING.at(WalletFlags{flag}));
+ - ]
132 : : } else {
133 [ # # # # : 0 : flags.push_back(strprintf("unknown_flag_%u", i));
# # ]
134 : : }
135 : : }
136 : : }
137 [ + - + - : 860 : obj.pushKV("flags", flags);
+ - ]
138 : :
139 [ + - ]: 430 : AppendLastProcessedBlock(obj, *pwallet);
140 : 430 : return obj;
141 [ + - ]: 1290 : },
142 [ + - + - ]: 5188 : };
143 [ + - + - : 46692 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - -
- - - ]
144 : :
145 : 931 : static RPCMethod listwalletdir()
146 : : {
147 : 931 : return RPCMethod{"listwalletdir",
148 [ + - ]: 1862 : "Returns a list of wallets in the wallet directory.\n",
149 : : {},
150 [ + - ]: 1862 : RPCResult{
151 [ + - ]: 1862 : RPCResult::Type::OBJ, "", "",
152 : : {
153 [ + - + - ]: 1862 : {RPCResult::Type::ARR, "wallets", "",
154 : : {
155 [ + - + - ]: 1862 : {RPCResult::Type::OBJ, "", "",
156 : : {
157 [ + - + - ]: 1862 : {RPCResult::Type::STR, "name", "The wallet name"},
158 [ + - + - ]: 1862 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
159 : : {
160 [ + - + - ]: 1862 : {RPCResult::Type::STR, "", ""},
161 : : }},
162 : : }},
163 : : }},
164 : : }
165 [ + - + - : 13965 : },
+ - + - +
- + + + +
+ + + + -
- - - - -
- - ]
166 : 931 : RPCExamples{
167 [ + - + - : 1862 : HelpExampleCli("listwalletdir", "")
+ - ]
168 [ + - + - : 3724 : + HelpExampleRpc("listwalletdir", "")
+ - + - ]
169 [ + - ]: 931 : },
170 : 931 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
171 : : {
172 : 74 : UniValue wallets(UniValue::VARR);
173 [ + - + - : 1715 : for (const auto& [path, db_type] : ListDatabases(GetWalletDir())) {
+ + ]
174 : 1567 : UniValue wallet(UniValue::VOBJ);
175 [ + - + - : 3134 : wallet.pushKV("name", path.utf8string());
+ - + - ]
176 : 1567 : UniValue warnings(UniValue::VARR);
177 [ + + ]: 1567 : 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 : : }
180 [ + - + - : 3134 : wallet.pushKV("warnings", warnings);
+ - ]
181 [ + - ]: 1567 : wallets.push_back(std::move(wallet));
182 : 1641 : }
183 : :
184 : 74 : UniValue result(UniValue::VOBJ);
185 [ + - + - ]: 148 : result.pushKV("wallets", std::move(wallets));
186 : 74 : return result;
187 : 74 : },
188 [ + - + - ]: 3724 : };
189 [ + - + - : 9310 : }
+ - + - +
- - - ]
190 : :
191 : 927 : static RPCMethod listwallets()
192 : : {
193 : 927 : return RPCMethod{"listwallets",
194 [ + - ]: 1854 : "Returns a list of currently loaded wallets.\n"
195 : : "For full information on the wallet, use \"getwalletinfo\"\n",
196 : : {},
197 [ + - ]: 1854 : RPCResult{
198 [ + - ]: 1854 : RPCResult::Type::ARR, "", "",
199 : : {
200 [ + - + - ]: 1854 : {RPCResult::Type::STR, "walletname", "the wallet name"},
201 : : }
202 [ + - + - : 3708 : },
+ + - - ]
203 : 927 : RPCExamples{
204 [ + - + - : 1854 : HelpExampleCli("listwallets", "")
+ - ]
205 [ + - + - : 3708 : + HelpExampleRpc("listwallets", "")
+ - + - ]
206 [ + - ]: 927 : },
207 : 927 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
208 : : {
209 : 70 : UniValue obj(UniValue::VARR);
210 : :
211 [ + - ]: 70 : WalletContext& context = EnsureWalletContext(request.context);
212 [ + - + + ]: 609 : for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
213 [ + - ]: 539 : LOCK(wallet->cs_wallet);
214 [ + - + - : 539 : obj.push_back(wallet->GetName());
+ - ]
215 : 609 : }
216 : :
217 : 70 : return obj;
218 : 0 : },
219 [ + - + - ]: 3708 : };
220 [ + - ]: 1854 : }
221 : :
222 : 1019 : static RPCMethod loadwallet()
223 : : {
224 : 1019 : return RPCMethod{
225 : 1019 : "loadwallet",
226 [ + - ]: 2038 : "Loads a wallet from a wallet file or directory."
227 : : "\nNote that all wallet command-line options used when starting bitcoind will be"
228 : : "\napplied to the new wallet.\n",
229 : : {
230 [ + - + - ]: 2038 : {"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 [ + - + - ]: 2038 : {"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 : : },
233 [ + - ]: 2038 : RPCResult{
234 [ + - + - ]: 2038 : RPCResult::Type::OBJ, "", "",
235 : : {
236 [ + - + - ]: 2038 : {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
237 [ + - + - ]: 2038 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
238 : : {
239 [ + - + - ]: 2038 : {RPCResult::Type::STR, "", ""},
240 : : }},
241 : : }
242 [ + - + - : 9171 : },
+ - + + +
+ - - -
- ]
243 : 1019 : RPCExamples{
244 : : "\nLoad wallet from the wallet dir:\n"
245 [ + - + - : 2038 : + HelpExampleCli("loadwallet", "\"walletname\"")
+ - + - ]
246 [ + - + - : 4076 : + HelpExampleRpc("loadwallet", "\"walletname\"")
+ - + - ]
247 : 1019 : + "\nLoad wallet using absolute path (Unix):\n"
248 [ + - + - : 4076 : + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"")
+ - + - ]
249 [ + - + - : 4076 : + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
+ - + - ]
250 : 1019 : + "\nLoad wallet using absolute path (Windows):\n"
251 [ + - + - : 4076 : + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
+ - + - ]
252 [ + - + - : 4076 : + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
+ - + - ]
253 [ + - ]: 1019 : },
254 : 1019 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
255 : : {
256 : 162 : WalletContext& context = EnsureWalletContext(request.context);
257 [ - + ]: 162 : const std::string name(request.params[0].get_str());
258 : :
259 [ + - ]: 162 : DatabaseOptions options;
260 : 162 : DatabaseStatus status;
261 [ + - ]: 162 : ReadDatabaseArgs(*context.args, options);
262 : 162 : options.require_existing = true;
263 [ + - ]: 162 : bilingual_str error;
264 : 162 : std::vector<bilingual_str> warnings;
265 [ + - + + : 162 : std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
+ - + - ]
266 : :
267 : 162 : {
268 [ + - ]: 162 : LOCK(context.wallets_mutex);
269 [ + + + + ]: 837 : if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
270 [ + - + - ]: 6 : throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
271 : : }
272 : 2 : }
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 : 142 : UniValue obj(UniValue::VOBJ);
279 [ + - + - : 284 : obj.pushKV("name", wallet->GetName());
+ - ]
280 [ + - ]: 142 : PushWarnings(warnings, obj);
281 : :
282 [ + - ]: 142 : return obj;
283 : 344 : },
284 [ + - + - : 6114 : };
+ + - - ]
285 [ + - + - : 10190 : }
+ - + - +
- - - -
- ]
286 : :
287 : 865 : static RPCMethod setwalletflag()
288 : : {
289 : 865 : std::string flags;
290 [ + + ]: 6920 : for (auto& it : STRING_TO_WALLET_FLAG)
291 [ + + ]: 6055 : if (it.second & MUTABLE_WALLET_FLAGS)
292 [ - + + - ]: 1730 : flags += (flags == "" ? "" : ", ") + it.first;
293 : :
294 : 865 : return RPCMethod{
295 [ + - ]: 1730 : "setwalletflag",
296 [ + - ]: 1730 : "Change the state of the given wallet flag for a wallet.\n",
297 : : {
298 [ + - + - ]: 1730 : {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
299 [ + - + - : 2595 : {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
+ - ]
300 : : },
301 [ + - ]: 1730 : RPCResult{
302 [ + - + - ]: 1730 : RPCResult::Type::OBJ, "", "",
303 : : {
304 [ + - + - ]: 1730 : {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
305 [ + - + - ]: 1730 : {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
306 [ + - + - ]: 1730 : {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
307 : : }
308 [ + - + - : 6920 : },
+ + - - ]
309 : 865 : RPCExamples{
310 [ + - + - : 1730 : HelpExampleCli("setwalletflag", "avoid_reuse")
+ - ]
311 [ + - + - : 3460 : + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
+ - + - ]
312 [ + - ]: 865 : },
313 : 865 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
314 : : {
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
323 : : }
324 : :
325 [ + - ]: 7 : auto flag = STRING_TO_WALLET_FLAG.at(flag_str);
326 : :
327 [ + + ]: 7 : if (!(flag & MUTABLE_WALLET_FLAGS)) {
328 [ + - + - ]: 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
329 : : }
330 : :
331 : 4 : UniValue res(UniValue::VOBJ);
332 : :
333 [ + - + + ]: 4 : if (pwallet->IsWalletFlagSet(flag) == value) {
334 [ + + + - : 5 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
+ - ]
335 : : }
336 : :
337 [ + - + - : 4 : res.pushKV("flag_name", flag_str);
+ - ]
338 [ + - + - : 4 : res.pushKV("flag_state", value);
+ - ]
339 : :
340 [ + + ]: 2 : if (value) {
341 [ + - ]: 1 : pwallet->SetWalletFlag(flag);
342 : : } else {
343 [ + - ]: 1 : pwallet->UnsetWalletFlag(flag);
344 : : }
345 : :
346 [ + - + + : 2 : if (flag && value && WALLET_FLAG_CAVEATS.contains(flag)) {
+ - ]
347 [ + - + - : 2 : res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
+ - + - ]
348 : : }
349 : :
350 : 2 : return res;
351 : 12 : },
352 [ + - + - : 4325 : };
+ + - - ]
353 [ + - + - : 9515 : }
+ - + - +
- - - -
- ]
354 : :
355 : 1517 : static RPCMethod createwallet()
356 : : {
357 : 1517 : return RPCMethod{
358 : 1517 : "createwallet",
359 [ + - ]: 3034 : "Creates and loads a new wallet.\n",
360 : : {
361 [ + - + - ]: 3034 : {"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 [ + - + - : 4551 : {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
+ - ]
363 [ + - + - : 4551 : {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys."},
+ - ]
364 [ + - + - ]: 3034 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
365 [ + - + - : 4551 : {"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 [ + - + - : 4551 : {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""},
+ - ]
367 [ + - + - ]: 3034 : {"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 [ + - + - : 4551 : {"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 : : },
370 [ + - ]: 3034 : RPCResult{
371 [ + - + - ]: 3034 : RPCResult::Type::OBJ, "", "",
372 : : {
373 [ + - + - ]: 3034 : {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 [ + - + - ]: 3034 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
375 : : {
376 [ + - + - ]: 3034 : {RPCResult::Type::STR, "", ""},
377 : : }},
378 : : }
379 [ + - + - : 13653 : },
+ - + + +
+ - - -
- ]
380 : 1517 : RPCExamples{
381 [ + - + - : 3034 : HelpExampleCli("createwallet", "\"testwallet\"")
+ - ]
382 [ + - + - : 6068 : + HelpExampleRpc("createwallet", "\"testwallet\"")
+ - + - ]
383 [ + - + - : 10619 : + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
+ - + - +
+ - - ]
384 [ + - + - : 10619 : + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}})
+ - + - +
+ - - ]
385 [ + - ]: 1517 : },
386 : 1517 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
387 : : {
388 : 660 : WalletContext& context = EnsureWalletContext(request.context);
389 : 660 : uint64_t flags = 0;
390 [ + + + + ]: 660 : if (!request.params[1].isNull() && request.params[1].get_bool()) {
391 : : flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
392 : : }
393 : :
394 [ + + + + ]: 660 : if (!request.params[2].isNull() && request.params[2].get_bool()) {
395 : 175 : flags |= WALLET_FLAG_BLANK_WALLET;
396 : : }
397 [ + - ]: 660 : SecureString passphrase;
398 [ + - ]: 660 : passphrase.reserve(100);
399 : 660 : std::vector<bilingual_str> warnings;
400 [ + - + + ]: 660 : 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 [ + - + - : 8 : warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
+ - ]
405 : : }
406 : : }
407 : :
408 [ + - + + : 660 : if (!request.params[4].isNull() && request.params[4].get_bool()) {
+ - + - +
+ ]
409 : 4 : flags |= WALLET_FLAG_AVOID_REUSE;
410 : : }
411 : 660 : flags |= WALLET_FLAG_DESCRIPTORS;
412 [ + - + + ]: 660 : if (!self.Arg<bool>("descriptors")) {
413 [ + - + - ]: 4 : throw JSONRPCError(RPC_WALLET_ERROR, "descriptors argument must be set to \"true\"; it is no longer possible to create a legacy wallet.");
414 : : }
415 [ + - + + : 658 : if (!request.params[7].isNull() && request.params[7].get_bool()) {
+ - + - +
+ ]
416 : : #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 : : }
422 : :
423 [ + - ]: 658 : DatabaseOptions options;
424 : 658 : DatabaseStatus status;
425 [ + - ]: 658 : ReadDatabaseArgs(*context.args, options);
426 : 658 : options.require_create = true;
427 : 658 : options.create_flags = flags;
428 [ + - ]: 658 : options.create_passphrase = passphrase;
429 [ + - ]: 658 : bilingual_str error;
430 [ + - + + : 658 : std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
+ - + - ]
431 [ + - + - : 658 : const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
+ + ]
432 [ + + ]: 655 : HandleWalletError(wallet, status, error);
433 : :
434 : 620 : UniValue obj(UniValue::VOBJ);
435 [ + - + - : 1240 : obj.pushKV("name", wallet->GetName());
+ - ]
436 [ + - ]: 620 : PushWarnings(warnings, obj);
437 : :
438 [ + - ]: 620 : return obj;
439 : 1353 : },
440 [ + - + - : 18204 : };
+ + - - ]
441 [ + - + - : 36408 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
- - - - -
- - - ]
442 : :
443 : 1175 : static RPCMethod unloadwallet()
444 : : {
445 : 1175 : return RPCMethod{"unloadwallet",
446 [ + - ]: 2350 : "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n"
447 : : "If both are specified, they must be identical.",
448 : : {
449 [ + - + - : 3525 : {"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 [ + - + - ]: 2350 : {"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 : : },
452 [ + - + - : 4700 : RPCResult{RPCResult::Type::OBJ, "", "", {
+ - ]
453 [ + - + - ]: 2350 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
454 : : {
455 [ + - + - ]: 2350 : {RPCResult::Type::STR, "", ""},
456 : : }},
457 [ + - + - : 8225 : }},
+ - + + +
+ - - -
- ]
458 : 1175 : RPCExamples{
459 [ + - + - : 2350 : HelpExampleCli("unloadwallet", "wallet_name")
+ - ]
460 [ + - + - : 4700 : + HelpExampleRpc("unloadwallet", "wallet_name")
+ - + - ]
461 [ + - ]: 1175 : },
462 : 1175 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
463 : : {
464 : 318 : const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
465 : :
466 [ + - ]: 314 : WalletContext& context = EnsureWalletContext(request.context);
467 [ + - ]: 314 : std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
468 [ + + ]: 314 : if (!wallet) {
469 [ + - + - ]: 8 : throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
470 : : }
471 : :
472 : 310 : std::vector<bilingual_str> warnings;
473 : 310 : {
474 : 310 : WalletRescanReserver reserver(*wallet);
475 [ - + ]: 310 : if (!reserver.reserve()) {
476 [ # # # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
477 : : }
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 [ + - ]: 310 : std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
483 [ + - - + ]: 310 : if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
484 [ # # # # ]: 0 : throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
485 : : }
486 : 310 : }
487 : :
488 [ + - ]: 310 : WaitForDeleteWallet(std::move(wallet));
489 : :
490 : 310 : UniValue result(UniValue::VOBJ);
491 [ + - ]: 310 : PushWarnings(warnings, result);
492 : :
493 : 620 : return result;
494 [ - + ]: 314 : },
495 [ + - + - : 7050 : };
+ + - - ]
496 [ + - + - : 9400 : }
+ - + - -
- ]
497 : :
498 : 883 : RPCMethod simulaterawtransaction()
499 : : {
500 : 883 : return RPCMethod{
501 : 883 : "simulaterawtransaction",
502 [ + - ]: 1766 : "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
503 : : {
504 [ + - + - ]: 1766 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
505 : : {
506 [ + - + - ]: 1766 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
507 : : },
508 : : },
509 [ + - + - ]: 1766 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
510 : : {
511 [ + - + - : 2649 : {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
+ - ]
512 : : },
513 : : },
514 : : },
515 [ + - ]: 1766 : RPCResult{
516 [ + - + - ]: 1766 : RPCResult::Type::OBJ, "", "",
517 : : {
518 [ + - + - ]: 1766 : {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
519 : : }
520 [ + - + - : 3532 : },
+ + - - ]
521 : 883 : RPCExamples{
522 [ + - + - : 1766 : HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
+ - ]
523 [ + - + - : 3532 : + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
+ - + - ]
524 [ + - ]: 883 : },
525 : 883 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
526 : : {
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 : : }
543 : :
544 : : // Fetch previous transactions (inputs)
545 : 38 : std::map<COutPoint, Coin> coins;
546 [ + + ]: 67 : for (const CTxIn& txin : mtx.vin) {
547 [ + - ]: 29 : coins[txin.prevout]; // Create empty map entry keyed by prevout.
548 : : }
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 [ + + ]: 57 : for (const auto& txin : mtx.vin) {
554 : 29 : const auto& outpoint = txin.prevout;
555 [ + + ]: 29 : if (spent.contains(outpoint)) {
556 [ + - + - ]: 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
557 : : }
558 [ + + ]: 26 : if (new_utxos.contains(outpoint)) {
559 [ + - ]: 6 : changes -= new_utxos.at(outpoint);
560 : 6 : new_utxos.erase(outpoint);
561 : : } else {
562 [ + - + + ]: 20 : if (coins.at(outpoint).IsSpent()) {
563 [ + - + - ]: 14 : throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
564 : : }
565 [ + - ]: 13 : changes -= wallet.GetDebit(txin);
566 : : }
567 [ + - ]: 19 : spent.insert(outpoint);
568 : : }
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 : : }
581 : 76 : }
582 : :
583 : 16 : UniValue result(UniValue::VOBJ);
584 [ + - + - : 32 : result.pushKV("balance_change", ValueFromAmount(changes));
+ - ]
585 : :
586 : 16 : return result;
587 [ + - ]: 78 : }
588 [ + - + - : 10596 : };
+ - + - +
+ + + + +
- - - - -
- ]
589 [ + - + - : 8830 : }
+ - + - +
- - - ]
590 : :
591 : 914 : static RPCMethod migratewallet()
592 : : {
593 : 914 : return RPCMethod{
594 : 914 : "migratewallet",
595 [ + - ]: 1828 : "Migrate the wallet to a descriptor wallet.\n"
596 : : "A new wallet backup will need to be made.\n"
597 : : "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
598 : : "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
599 : : "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
600 : : "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
601 : : "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
602 : : {
603 [ + - + - : 2742 : {"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 [ + - + - ]: 1828 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
605 [ + - + - : 2742 : {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration."},
+ - ]
606 : : },
607 [ + - ]: 1828 : RPCResult{
608 [ + - + - ]: 1828 : RPCResult::Type::OBJ, "", "",
609 : : {
610 [ + - + - ]: 1828 : {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
611 [ + - + - ]: 1828 : {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
612 [ + - + - ]: 1828 : {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
613 [ + - + - ]: 1828 : {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
614 : : }
615 [ + - + - : 9140 : },
+ + - - ]
616 : 914 : RPCExamples{
617 [ + - + - : 1828 : HelpExampleCli("migratewallet", "")
+ - ]
618 [ + - + - : 3656 : + HelpExampleRpc("migratewallet", "")
+ - + - ]
619 [ + - ]: 914 : },
620 : 914 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
621 : : {
622 : 57 : const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))};
623 : :
624 [ + - ]: 55 : SecureString wallet_pass;
625 [ + - ]: 55 : wallet_pass.reserve(100);
626 [ + - + + ]: 55 : if (!request.params[1].isNull()) {
627 [ + - + - : 5 : wallet_pass = std::string_view{request.params[1].get_str()};
- + + - ]
628 : : }
629 : :
630 [ + - ]: 55 : const bool loadwallet = self.Arg<bool>("load_wallet");
631 : :
632 [ + - ]: 55 : WalletContext& context = EnsureWalletContext(request.context);
633 [ + - ]: 55 : util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context, loadwallet);
634 [ + + ]: 55 : if (!res) {
635 [ + - + - ]: 24 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
636 : : }
637 : :
638 : 43 : UniValue r{UniValue::VOBJ};
639 [ + - + - : 86 : r.pushKV("wallet_name", res->wallet_name);
+ - ]
640 [ + + ]: 43 : if (res->watchonly_wallet_name.has_value()) {
641 [ + - + - : 24 : r.pushKV("watchonly_name", res->watchonly_wallet_name.value());
+ - ]
642 : : }
643 [ + + ]: 43 : if (res->solvables_wallet_name.has_value()) {
644 [ + - + - : 12 : r.pushKV("solvables_name", res->solvables_wallet_name.value());
+ - ]
645 : : }
646 [ + - + - : 86 : r.pushKV("backup_path", res->backup_path.utf8string());
+ - + - ]
647 : :
648 : 43 : return r;
649 : 67 : },
650 [ + - + - : 6398 : };
+ + - - ]
651 [ + - + - : 12796 : }
+ - + - +
- + - + -
- - - - ]
652 : :
653 : 899 : RPCMethod gethdkeys()
654 : : {
655 : 899 : return RPCMethod{
656 : 899 : "gethdkeys",
657 [ + - ]: 1798 : "List all BIP 32 HD keys in the wallet and which descriptors use them.\n",
658 : : {
659 [ + - + - ]: 1798 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
660 [ + - + - : 2697 : {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
+ - ]
661 [ + - + - : 2697 : {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
+ - ]
662 : : }},
663 : : },
664 [ + - + - : 3596 : RPCResult{RPCResult::Type::ARR, "", "", {
+ - ]
665 : : {
666 [ + - + - ]: 1798 : {RPCResult::Type::OBJ, "", "", {
667 [ + - + - ]: 1798 : {RPCResult::Type::STR, "xpub", "The extended public key"},
668 [ + - + - ]: 1798 : {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
669 [ + - + - ]: 1798 : {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
670 [ + - + - ]: 1798 : {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
671 : : {
672 [ + - + - ]: 1798 : {RPCResult::Type::OBJ, "", "", {
673 [ + - + - ]: 1798 : {RPCResult::Type::STR, "desc", "Descriptor string public representation"},
674 [ + - + - ]: 1798 : {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
675 : : }},
676 : : }},
677 : : }},
678 : : }
679 [ + - + - : 18879 : }},
+ - + - +
- + + + +
+ + + + -
- - - - -
- - ]
680 : 899 : RPCExamples{
681 [ + - + - : 1798 : HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
+ - + - +
- + - +
- ]
682 [ + - + - : 8990 : + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
+ - + - +
- + - + -
+ - + + +
+ - - -
- ]
683 [ + - ]: 899 : },
684 : 899 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
685 : : {
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 [ + - + + : 92 : const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
+ - + - +
- ]
693 [ + - + + : 96 : const bool priv{options.exists("private") ? options["private"].get_bool() : false};
+ - + - +
- ]
694 [ + + ]: 42 : if (priv) {
695 [ + + ]: 12 : EnsureWalletIsUnlocked(*wallet);
696 : : }
697 : :
698 [ + + ]: 41 : std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
699 : 41 : std::map<CExtPubKey, CExtKey> wallet_xprvs;
700 [ + + + - : 120 : for (const auto& [xpub, spkms] : wallet->GetHDPubKeys(active_only ? HDKeyFilter::Active : HDKeyFilter::All)) {
+ + ]
701 [ + + ]: 326 : 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 : 280 : }
710 [ + - ]: 560 : }
711 : 0 : }
712 : :
713 : 41 : UniValue response(UniValue::VARR);
714 [ + + ]: 87 : for (const auto& [xpub, descs] : wallet_xpubs) {
715 : 46 : bool has_xprv = false;
716 : 46 : UniValue descriptors(UniValue::VARR);
717 [ + + ]: 326 : for (const auto& [desc, active, has_priv] : descs) {
718 : 280 : UniValue d(UniValue::VOBJ);
719 [ + - + - : 560 : d.pushKV("desc", desc);
+ - ]
720 [ + - + - : 560 : 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 [ + - + - : 92 : xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
+ - + - ]
727 [ + - + - : 92 : xpub_info.pushKV("has_private", has_xprv);
+ - ]
728 [ + + + + ]: 46 : if (priv && has_xprv) {
729 [ + - + - : 22 : xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
+ - + - +
- ]
730 : : }
731 [ + - + - ]: 92 : xpub_info.pushKV("descriptors", std::move(descriptors));
732 : :
733 [ + - ]: 46 : response.push_back(std::move(xpub_info));
734 : 46 : }
735 : :
736 : 41 : return response;
737 [ + - ]: 125 : },
738 [ + - + - : 8091 : };
+ - + + +
+ - - -
- ]
739 [ + - + - : 21576 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- - - - -
- - - - -
- ]
740 : :
741 : 871 : static RPCMethod createwalletdescriptor()
742 : : {
743 : 871 : return RPCMethod{"createwalletdescriptor",
744 : : "Creates the wallet's descriptor for the given address type. "
745 : : "The address type must be one that the wallet does not already have a descriptor for."
746 [ + - ]: 1742 : + HELP_REQUIRING_PASSPHRASE,
747 : : {
748 [ + - + - : 1742 : {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."},
+ - ]
749 [ + - + - ]: 1742 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
750 [ + - + - : 2613 : {"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 [ + - + - : 2613 : {"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 : : }},
753 : : },
754 [ + - ]: 1742 : RPCResult{
755 [ + - + - ]: 1742 : RPCResult::Type::OBJ, "", "",
756 : : {
757 [ + - + - ]: 1742 : {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
758 [ + - + - ]: 1742 : {{RPCResult::Type::STR, "", ""}}
759 : : }
760 : : },
761 [ + - + - : 6097 : },
+ - + + +
+ - - -
- ]
762 : 871 : RPCExamples{
763 [ + - + - : 1742 : HelpExampleCli("createwalletdescriptor", "bech32m")
+ - ]
764 [ + - + - : 3484 : + HelpExampleRpc("createwalletdescriptor", "bech32m")
+ - + - ]
765 [ + - ]: 871 : },
766 : 871 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
767 : : {
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 [ + - + - : 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
+ - + - ]
774 : : }
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 : : } else {
785 [ + - + - ]: 2 : internals.push_back(internal_only.get_bool());
786 : : }
787 : :
788 [ + - ]: 13 : LOCK(pwallet->cs_wallet);
789 [ + + ]: 13 : EnsureWalletIsUnlocked(*pwallet);
790 : :
791 [ + + ]: 12 : CExtPubKey xpub;
792 [ + + ]: 12 : if (hdkey.isNull()) {
793 [ + - ]: 7 : HDPubKeyMap active_xpubs = pwallet->GetHDPubKeys(HDKeyFilter::Active);
794 [ + + ]: 7 : if (active_xpubs.size() != 1) {
795 [ + - + - ]: 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
796 : : }
797 : 5 : xpub = active_xpubs.begin()->first;
798 : 7 : } else {
799 [ + - + - ]: 5 : xpub = DecodeExtPubKey(hdkey.get_str());
800 [ + + ]: 5 : if (!xpub.pubkey.IsValid()) {
801 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
802 : : }
803 : : }
804 : :
805 [ + - + - ]: 9 : std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
806 [ + + ]: 9 : if (!key) {
807 [ + - + - : 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
+ - ]
808 : : }
809 [ + - ]: 8 : CExtKey active_hdkey(xpub, *key);
810 : :
811 : 8 : std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
812 [ + - ]: 8 : WalletBatch batch{pwallet->GetDatabase()};
813 [ + - - + : 44 : 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 : : }
819 : 14 : }
820 [ + + ]: 8 : if (spkms.empty()) {
821 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
822 : : }
823 : :
824 : : // Fetch each descspkm from the wallet in order to get the descriptor strings
825 : 7 : UniValue descs{UniValue::VARR};
826 [ + + ]: 19 : 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 : 7 : UniValue out{UniValue::VOBJ};
833 [ + - + - ]: 14 : out.pushKV("descs", std::move(descs));
834 : 7 : return out;
835 [ + - ]: 61 : }
836 [ + - + - : 8710 : };
+ - + + +
+ - - -
- ]
837 [ + - + - : 10452 : }
+ - + - +
- + - - -
- - ]
838 : :
839 : 867 : RPCMethod addhdkey()
840 : : {
841 : 867 : return RPCMethod{
842 : 867 : "addhdkey",
843 [ + - ]: 1734 : "Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
844 : : {
845 [ + - + - : 2601 : {"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 [ + - ]: 1734 : RPCResult{
848 [ + - + - ]: 1734 : RPCResult::Type::OBJ, "", "",
849 : : {
850 [ + - + - ]: 1734 : {RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
851 : : },
852 [ + - + - : 3468 : },
+ + - - ]
853 : 867 : RPCExamples{
854 [ + - + - : 1734 : HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
+ - + - +
- + - +
- ]
855 [ + - ]: 867 : },
856 : 867 : [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
857 : : {
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "addhdkey is not available for wallets without private keys");
863 : : }
864 : :
865 [ + - ]: 9 : EnsureWalletIsUnlocked(*wallet);
866 : :
867 [ + - ]: 9 : CExtKey hdkey;
868 [ + - + + ]: 9 : if (request.params[0].isNull()) {
869 : 6 : CKey seed_key = GenerateRandomKey();
870 [ + - + - ]: 12 : 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 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extended public key (xpub) provided, but extended private key (xprv) is required");
878 : : } else {
879 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not parse HD key");
880 : : }
881 : 1 : }
882 : : }
883 : :
884 [ + - ]: 8 : LOCK(wallet->cs_wallet);
885 [ + - + - ]: 16 : 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 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "HD key already exists");
893 : : }
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 : : }
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 [ + - + - : 14 : response.pushKV("xpub", EncodeExtPubKey(*extpubs.begin()));
+ - + - ]
909 : :
910 : 7 : return response;
911 [ + - + - ]: 34 : },
912 [ + - + - : 4335 : };
+ + - - ]
913 [ + - + - ]: 3468 : }
914 : :
915 : 869 : static RPCMethod exportwatchonlywallet()
916 : : {
917 : 869 : return RPCMethod{"exportwatchonlywallet",
918 [ + - ]: 1738 : "Creates a wallet file at the specified destination containing a watchonly version "
919 : : "of the current wallet. This watchonly wallet contains the wallet's public descriptors, "
920 : : "its transactions, and address book data. Descriptors that use hardened derivation will "
921 : : "only have a limited number of derived keys included in the export due to hardened "
922 : : "derivation requiring private keys. Descriptors with unhardened derivation do not have "
923 : : "this limitation. The watchonly wallet can be imported into another node using 'restorewallet'.",
924 : : {
925 [ + - + - ]: 1738 : {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the filename the exported watchonly wallet will be saved to"},
926 : : },
927 [ + - ]: 1738 : RPCResult{
928 [ + - + - ]: 1738 : RPCResult::Type::OBJ, "", "",
929 : : {
930 [ + - + - ]: 1738 : {RPCResult::Type::STR, "exported_file", "The full path that the file has been exported to"},
931 : : },
932 [ + - + - : 3476 : },
+ + - - ]
933 : 869 : RPCExamples{
934 [ + - + - : 1738 : HelpExampleCli("exportwatchonlywallet", "\"/path/to/export.dat\"")
+ - ]
935 [ + - + - : 3476 : + HelpExampleRpc("exportwatchonlywallet", "\"/path/to/export.dat\"")
+ - + - ]
936 [ + - ]: 869 : },
937 : 869 : [&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
938 : : {
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 [ + - + - ]: 10 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(exported).original);
950 : : }
951 : 7 : UniValue out{UniValue::VOBJ};
952 [ + - + - : 14 : out.pushKV("exported_file", *exported);
+ - ]
953 : 7 : return out;
954 [ + - ]: 36 : }
955 [ + - + - : 4345 : };
+ + - - ]
956 [ + - + - ]: 3476 : }
957 : :
958 : 888 : RPCMethod derivehdkey()
959 : : {
960 : 888 : return RPCMethod{
961 : 888 : "derivehdkey",
962 : : "Derive extended public or private key from HD key in the wallet at a given path.\n"
963 : : "Derivation uses wallet private key material.\n"
964 [ + - ]: 1776 : + HELP_REQUIRING_PASSPHRASE,
965 : : {
966 [ + - + - ]: 1776 : {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "BIP 32 derivation path with at least one hardened step."},
967 [ + - + - ]: 1776 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
968 [ + - + - : 2664 : {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private key"},
+ - ]
969 [ + - + - : 2664 : {"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 : : }},
971 : : },
972 [ + - ]: 1776 : RPCResult{
973 [ + - + - ]: 1776 : RPCResult::Type::OBJ, "", "", {
974 [ + - + - ]: 1776 : {RPCResult::Type::STR, "origin", "Fingerprint and path for use in descriptors"},
975 [ + - + - ]: 1776 : {RPCResult::Type::STR, "xpub", "The extended public key"},
976 [ + - + - ]: 1776 : {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
977 : : },
978 [ + - + - : 7104 : },
+ + - - ]
979 : 888 : RPCExamples{
980 [ + - + - : 1776 : HelpExampleCli("derivehdkey", "m/87h/0h/0h") + HelpExampleRpc("derivehdkey", "\"m/87h/0h/0h\"")
+ - + - +
- + - +
- ]
981 [ + - + - : 5328 : + HelpExampleCliNamed("derivehdkey", {{"path", "m/87h/0h/0h"}, {"private", "true"}})
+ - + - +
+ - - ]
982 [ + - + - : 5328 : + HelpExampleRpcNamed("derivehdkey", {{"path", "m/87h/0h/0h"}, {"private", "true"}})
+ - + - +
+ - - ]
983 [ + - ]: 888 : },
984 : 888 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
985 : : {
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "derivehdkey is not available for watch-only wallets");
992 : : }
993 : :
994 [ + - + - : 30 : std::vector<uint32_t> path = ParsePathBIP32(request.params[0].get_str());
+ + ]
995 [ + - + + : 27 : UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
+ - + - ]
996 [ + - + + : 57 : const bool priv{options.exists("private") ? options["private"].get_bool() : false};
+ - + - +
- ]
997 [ + - + - : 27 : UniValue hdkey{options["hdkey"]};
+ - ]
998 [ - + + - : 27 : if (!HasHardenedDerivation(path)) {
+ + ]
999 [ + - + - ]: 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Derivation path requires at least one hardened step");
1000 : : }
1001 : :
1002 [ + - ]: 25 : LOCK(wallet->cs_wallet);
1003 : :
1004 : : // The RPC requires a hardened derivation step, so always unlock
1005 : : // the wallet.
1006 [ + + ]: 25 : EnsureWalletIsUnlocked(*wallet);
1007 : :
1008 [ + + ]: 23 : CExtPubKey xpub;
1009 [ + + ]: 23 : if (!hdkey.isNull()) {
1010 [ + - + - ]: 7 : xpub = DecodeExtPubKey(hdkey.get_str());
1011 [ + + ]: 7 : if (!xpub.pubkey.IsValid()) {
1012 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
1013 : : }
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 [ + - + + ]: 11 : for (const auto& candidate : wallet->GetHDPubKeys(HDKeyFilter::UnusedKey)) {
1019 [ + - ]: 5 : xpub_candidates.insert(candidate.first);
1020 : 0 : }
1021 [ + - + + ]: 9 : for (const auto& candidate : wallet->GetHDPubKeys(HDKeyFilter::Active)) {
1022 [ + - ]: 3 : xpub_candidates.insert(candidate.first);
1023 : 0 : }
1024 [ + + ]: 6 : if (!xpub_candidates.contains(xpub)) {
1025 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "HD key is not used by an active or unused(KEY) descriptor");
1026 : : }
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 [ + + ]: 21 : if (hdkey.isNull()) {
1032 [ + - ]: 16 : HDPubKeyMap wallet_xpubs{wallet->GetHDPubKeys(HDKeyFilter::UnusedKey)};
1033 : :
1034 [ + + ]: 16 : if (wallet_xpubs.size() > 1) {
1035 [ + - + - ]: 2 : 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 : : } else {
1039 [ + - ]: 6 : HDPubKeyMap active_xpubs = wallet->GetHDPubKeys(HDKeyFilter::Active);
1040 [ + + ]: 6 : if (active_xpubs.empty()) {
1041 [ + - + - ]: 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No active or unused(KEY) descriptor found");
1042 : : }
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 : : }
1047 : :
1048 : 4 : xpub = active_xpubs.begin()->first;
1049 : 6 : }
1050 : 16 : }
1051 : :
1052 [ + - ]: 18 : std::optional<CExtKey> xprv{wallet->GetExtKey(xpub)};
1053 [ - + ]: 18 : if (!xprv) {
1054 [ # # # # : 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
# # ]
1055 : : }
1056 : :
1057 [ + - ]: 18 : std::optional<std::pair<CExtKey, KeyOriginInfo>> child{DeriveExtKey(*xprv, path)};
1058 [ + + ]: 18 : if (!child) {
1059 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to derive HD key at the requested path");
1060 : : }
1061 : :
1062 : 17 : UniValue res{UniValue::VOBJ};
1063 : :
1064 [ + - ]: 17 : const std::string fingerprint{HexStr(child->second.fingerprint)};
1065 : :
1066 [ + - + - : 34 : res.pushKV("origin", strprintf("[%s%s]", fingerprint, FormatHDKeypath(child->second.path)));
+ - + - +
- ]
1067 [ + - + - : 34 : res.pushKV("xpub", EncodeExtPubKey(child->first.Neuter()));
+ - + - +
- ]
1068 [ + + ]: 17 : if (priv) {
1069 [ + - + - : 4 : res.pushKV("xprv", EncodeExtKey(child->first));
+ - + - ]
1070 : : }
1071 : 17 : return res;
1072 [ + - + - ]: 106 : },
1073 [ + - + - : 8880 : };
+ - + + +
+ - - -
- ]
1074 [ + - + - : 14208 : }
+ - + - +
- + - + -
+ - + - +
- + - - -
- - - - -
- - - ]
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 : 437 : std::span<const CRPCCommand> GetWalletRPCCommands()
1138 : : {
1139 : 437 : static const CRPCCommand commands[]{
1140 [ + - ]: 852 : {"rawtransactions", &fundrawtransaction},
1141 [ + - ]: 852 : {"wallet", &abandontransaction},
1142 [ + - ]: 852 : {"wallet", &abortrescan},
1143 [ + - ]: 852 : {"wallet", &addhdkey},
1144 [ + - ]: 852 : {"wallet", &backupwallet},
1145 [ + - ]: 852 : {"wallet", &bumpfee},
1146 [ + - ]: 852 : {"wallet", &psbtbumpfee},
1147 [ + - ]: 852 : {"wallet", &createwallet},
1148 [ + - ]: 852 : {"wallet", &createwalletdescriptor},
1149 [ + - ]: 852 : {"wallet", &derivehdkey},
1150 [ + - ]: 852 : {"wallet", &restorewallet},
1151 [ + - ]: 852 : {"wallet", &encryptwallet},
1152 [ + - ]: 852 : {"wallet", &exportwatchonlywallet},
1153 [ + - ]: 852 : {"wallet", &getaddressesbylabel},
1154 [ + - ]: 852 : {"wallet", &getaddressinfo},
1155 [ + - ]: 852 : {"wallet", &getbalance},
1156 [ + - ]: 852 : {"wallet", &gethdkeys},
1157 [ + - ]: 852 : {"wallet", &getnewaddress},
1158 [ + - ]: 852 : {"wallet", &getrawchangeaddress},
1159 [ + - ]: 852 : {"wallet", &getreceivedbyaddress},
1160 [ + - ]: 852 : {"wallet", &getreceivedbylabel},
1161 [ + - ]: 852 : {"wallet", &gettransaction},
1162 [ + - ]: 852 : {"wallet", &getbalances},
1163 [ + - ]: 852 : {"wallet", &getwalletinfo},
1164 [ + - ]: 852 : {"wallet", &importdescriptors},
1165 [ + - ]: 852 : {"wallet", &importprunedfunds},
1166 [ + - ]: 852 : {"wallet", &keypoolrefill},
1167 [ + - ]: 852 : {"wallet", &listaddressgroupings},
1168 [ + - ]: 852 : {"wallet", &listdescriptors},
1169 [ + - ]: 852 : {"wallet", &listlabels},
1170 [ + - ]: 852 : {"wallet", &listlockunspent},
1171 [ + - ]: 852 : {"wallet", &listreceivedbyaddress},
1172 [ + - ]: 852 : {"wallet", &listreceivedbylabel},
1173 [ + - ]: 852 : {"wallet", &listsinceblock},
1174 [ + - ]: 852 : {"wallet", &listtransactions},
1175 [ + - ]: 852 : {"wallet", &listunspent},
1176 [ + - ]: 852 : {"wallet", &listwalletdir},
1177 [ + - ]: 852 : {"wallet", &listwallets},
1178 [ + - ]: 852 : {"wallet", &loadwallet},
1179 [ + - ]: 852 : {"wallet", &lockunspent},
1180 [ + - ]: 852 : {"wallet", &migratewallet},
1181 [ + - ]: 852 : {"wallet", &removeprunedfunds},
1182 [ + - ]: 852 : {"wallet", &rescanblockchain},
1183 [ + - ]: 852 : {"wallet", &send},
1184 [ + - ]: 852 : {"wallet", &sendmany},
1185 [ + - ]: 852 : {"wallet", &sendtoaddress},
1186 [ + - ]: 852 : {"wallet", &setlabel},
1187 [ + - ]: 852 : {"wallet", &setwalletflag},
1188 [ + - ]: 852 : {"wallet", &signmessage},
1189 [ + - ]: 852 : {"wallet", &signrawtransactionwithwallet},
1190 [ + - ]: 852 : {"wallet", &simulaterawtransaction},
1191 [ + - ]: 852 : {"wallet", &sendall},
1192 [ + - ]: 852 : {"wallet", &unloadwallet},
1193 [ + - ]: 852 : {"wallet", &walletcreatefundedpsbt},
1194 : : #ifdef ENABLE_EXTERNAL_SIGNER
1195 [ + - ]: 852 : {"wallet", &walletdisplayaddress},
1196 : : #endif // ENABLE_EXTERNAL_SIGNER
1197 [ + - ]: 852 : {"wallet", &walletlock},
1198 [ + - ]: 852 : {"wallet", &walletpassphrase},
1199 [ + - ]: 852 : {"wallet", &walletpassphrasechange},
1200 [ + - ]: 852 : {"wallet", &walletprocesspsbt},
1201 [ + + + - : 25571 : };
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - -
- ]
1202 : 437 : return commands;
1203 : : }
1204 : : } // namespace wallet
|