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