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