Branch data Line data Source code
1 : : // Copyright (c) 2010 Satoshi Nakamoto
2 : : // Copyright (c) 2009-2022 The Bitcoin Core developers
3 : : // Distributed under the MIT software license, see the accompanying
4 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 : :
6 : : #include <core_io.h>
7 : : #include <key_io.h>
8 : : #include <rpc/server.h>
9 : : #include <rpc/util.h>
10 : : #include <util/translation.h>
11 : : #include <wallet/context.h>
12 : : #include <wallet/receive.h>
13 : : #include <wallet/rpc/wallet.h>
14 : : #include <wallet/rpc/util.h>
15 : : #include <wallet/wallet.h>
16 : : #include <wallet/walletutil.h>
17 : :
18 : : #include <optional>
19 : :
20 : : #include <univalue.h>
21 : :
22 : :
23 : : namespace wallet {
24 : :
25 [ + - ]: 2 : static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
26 [ + - ]: 2 : {WALLET_FLAG_AVOID_REUSE,
27 : 2 : "You need to rescan the blockchain in order to correctly mark used "
28 [ + - ][ + - ]: 6 : "destinations in the past. Until this is done, some destinations may "
[ + - ][ - + ]
[ # # ]
29 [ + - ][ + - ]: 2 : "be considered unused, even if the opposite is the case."},
[ + - ]
30 [ + - ][ + - ]: 2 : };
[ + - ]
31 : :
32 : : /** Checks if a CKey is in the given CWallet compressed or otherwise*/
33 : 0 : bool HaveKey(const SigningProvider& wallet, const CKey& key)
34 : : {
35 : 0 : CKey key2;
36 [ # # ][ # # ]: 0 : key2.Set(key.begin(), key.end(), !key.IsCompressed());
[ # # ][ # # ]
37 [ # # ][ # # ]: 0 : return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
38 : 0 : }
39 : :
40 : 0 : static RPCHelpMan getwalletinfo()
41 : : {
42 [ # # ][ # # ]: 0 : return RPCHelpMan{"getwalletinfo",
[ # # ][ # # ]
43 [ # # ]: 0 : "Returns an object containing various wallet state info.\n",
44 : 0 : {},
45 [ # # ][ # # ]: 0 : RPCResult{
46 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
47 [ # # ]: 0 : {
48 : 0 : {
49 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "walletname", "the wallet name"},
[ # # ]
50 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "walletversion", "the wallet version"},
[ # # ]
51 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
[ # # ]
52 [ # # ][ # # ]: 0 : {RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
[ # # ]
53 [ # # ][ # # ]: 0 : {RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
[ # # ]
54 [ # # ][ # # ]: 0 : {RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
[ # # ]
55 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
[ # # ]
56 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM_TIME, "keypoololdest", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only."},
[ # # ][ # # ]
57 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
[ # # ]
58 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "keypoolsize_hd_internal", /*optional=*/true, "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
[ # # ]
59 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM_TIME, "unlocked_until", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
[ # # ][ # # ]
60 [ # # ][ # # ]: 0 : {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
[ # # ][ # # ]
61 [ # # ][ # # ]: 0 : {RPCResult::Type::STR_HEX, "hdseedid", /*optional=*/true, "the Hash160 of the HD seed (only present when HD is enabled)"},
[ # # ]
62 [ # # ][ # # ]: 0 : {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
[ # # ]
63 [ # # ][ # # ]: 0 : {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
[ # # ]
64 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
[ # # ]
65 [ # # ]: 0 : {
66 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
[ # # ]
67 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
[ # # ]
68 : : }, /*skip_type_check=*/true},
69 [ # # ][ # # ]: 0 : {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
[ # # ]
70 [ # # ][ # # ]: 0 : {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
[ # # ]
71 [ # # ][ # # ]: 0 : {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
[ # # ]
72 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."},
[ # # ]
73 [ # # ]: 0 : RESULT_LAST_PROCESSED_BLOCK,
74 : : }},
75 : : },
76 [ # # ]: 0 : RPCExamples{
77 [ # # ][ # # ]: 0 : HelpExampleCli("getwalletinfo", "")
[ # # ]
78 [ # # ][ # # ]: 0 : + HelpExampleRpc("getwalletinfo", "")
[ # # ][ # # ]
79 : : },
80 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
81 : : {
82 : 0 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
83 [ # # ][ # # ]: 0 : if (!pwallet) return UniValue::VNULL;
84 : :
85 : : // Make sure the results are valid at least up to the most recent block
86 : : // the user could have gotten from another RPC command prior to now
87 [ # # ]: 0 : pwallet->BlockUntilSyncedToCurrentChain();
88 : :
89 [ # # ][ # # ]: 0 : LOCK(pwallet->cs_wallet);
90 : :
91 [ # # ]: 0 : UniValue obj(UniValue::VOBJ);
92 : :
93 [ # # ]: 0 : size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
94 [ # # ]: 0 : const auto bal = GetBalance(*pwallet);
95 [ # # ][ # # ]: 0 : obj.pushKV("walletname", pwallet->GetName());
[ # # ]
96 [ # # ][ # # ]: 0 : obj.pushKV("walletversion", pwallet->GetVersion());
[ # # ][ # # ]
97 [ # # ][ # # ]: 0 : obj.pushKV("format", pwallet->GetDatabase().Format());
[ # # ][ # # ]
[ # # ]
98 [ # # ][ # # ]: 2 : obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
[ # # ]
99 [ # # ][ # # ]: 0 : obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
[ # # ]
100 [ # # ][ # # ]: 0 : obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
[ # # ]
101 [ # # ][ # # ]: 0 : obj.pushKV("txcount", (int)pwallet->mapWallet.size());
[ # # ]
102 [ # # ]: 0 : const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
103 [ # # ]: 0 : if (kp_oldest.has_value()) {
104 [ # # ][ # # ]: 0 : obj.pushKV("keypoololdest", kp_oldest.value());
[ # # ][ # # ]
105 : 0 : }
106 [ # # ][ # # ]: 2 : obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
[ # # ]
107 : :
108 [ # # ]: 0 : LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
109 [ # # ]: 0 : if (spk_man) {
110 [ # # ]: 0 : CKeyID seed_id = spk_man->GetHDChain().seed_id;
111 [ # # ][ # # ]: 0 : if (!seed_id.IsNull()) {
112 [ # # ][ # # ]: 0 : obj.pushKV("hdseedid", seed_id.GetHex());
[ # # ][ # # ]
113 : 0 : }
114 : 0 : }
115 : :
116 [ # # ][ # # ]: 0 : if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
117 [ # # ][ # # ]: 0 : obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
[ # # ][ # # ]
118 : 0 : }
119 [ # # ][ # # ]: 0 : if (pwallet->IsCrypted()) {
120 [ # # ][ # # ]: 0 : obj.pushKV("unlocked_until", pwallet->nRelockTime);
[ # # ]
121 : 0 : }
122 [ # # ][ # # ]: 0 : obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
[ # # ][ # # ]
123 [ # # ][ # # ]: 0 : obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
[ # # ][ # # ]
124 [ # # ][ # # ]: 0 : obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
[ # # ][ # # ]
125 [ # # ][ # # ]: 0 : if (pwallet->IsScanning()) {
126 [ # # ]: 0 : UniValue scanning(UniValue::VOBJ);
127 [ # # ][ # # ]: 0 : scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
[ # # ][ # # ]
[ # # ]
128 [ # # ][ # # ]: 0 : scanning.pushKV("progress", pwallet->ScanningProgress());
[ # # ][ # # ]
129 [ # # ][ # # ]: 0 : obj.pushKV("scanning", scanning);
[ # # ]
130 : 0 : } else {
131 [ # # ][ # # ]: 0 : obj.pushKV("scanning", false);
[ # # ]
132 : : }
133 [ # # ][ # # ]: 0 : obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
[ # # ][ # # ]
134 [ # # ][ # # ]: 0 : obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
[ # # ][ # # ]
135 [ # # ][ # # ]: 0 : obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
[ # # ][ # # ]
136 [ # # ][ # # ]: 0 : if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
137 [ # # ][ # # ]: 0 : obj.pushKV("birthtime", birthtime);
[ # # ]
138 : 0 : }
139 : :
140 [ # # ]: 0 : AppendLastProcessedBlock(obj, *pwallet);
141 : 0 : return obj;
142 [ # # ]: 0 : },
143 : : };
144 : 0 : }
145 : :
146 : 0 : static RPCHelpMan listwalletdir()
147 : : {
148 [ # # ][ # # ]: 0 : return RPCHelpMan{"listwalletdir",
[ # # ][ # # ]
[ # # ]
149 [ # # ]: 0 : "Returns a list of wallets in the wallet directory.\n",
150 : 0 : {},
151 [ # # ][ # # ]: 0 : RPCResult{
152 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
153 [ # # ]: 0 : {
154 [ # # ][ # # ]: 0 : {RPCResult::Type::ARR, "wallets", "",
[ # # ]
155 [ # # ]: 0 : {
156 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "", "",
[ # # ]
157 [ # # ]: 0 : {
158 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "name", "The wallet name"},
[ # # ]
159 : : }},
160 : : }},
161 : : }
162 : : },
163 [ + - ][ # # ]: 2 : RPCExamples{
164 [ + - ][ # # ]: 2 : HelpExampleCli("listwalletdir", "")
[ # # ][ # # ]
165 [ + - ][ # # ]: 2 : + HelpExampleRpc("listwalletdir", "")
[ # # ][ # # ]
[ # # ]
166 [ + - ]: 2 : },
167 [ + - ]: 2 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
168 [ + - ]: 2 : {
169 [ + - ][ # # ]: 2 : UniValue wallets(UniValue::VARR);
170 [ + - ][ # # ]: 2 : for (const auto& path : ListDatabases(GetWalletDir())) {
[ # # ][ # # ]
171 [ # # ]: 0 : UniValue wallet(UniValue::VOBJ);
172 [ # # ][ # # ]: 0 : wallet.pushKV("name", path.utf8string());
[ # # ][ # # ]
173 [ # # ][ # # ]: 0 : wallets.push_back(wallet);
174 : 0 : }
175 : :
176 [ # # ]: 0 : UniValue result(UniValue::VOBJ);
177 [ # # ][ # # ]: 0 : result.pushKV("wallets", wallets);
[ # # ]
178 : 0 : return result;
179 [ # # ]: 0 : },
180 : : };
181 : 0 : }
182 : :
183 : 0 : static RPCHelpMan listwallets()
184 : : {
185 [ # # ][ # # ]: 0 : return RPCHelpMan{"listwallets",
[ # # ]
186 [ # # ]: 0 : "Returns a list of currently loaded wallets.\n"
187 : : "For full information on the wallet, use \"getwalletinfo\"\n",
188 : 0 : {},
189 [ # # ][ # # ]: 0 : RPCResult{
190 [ # # ][ # # ]: 0 : RPCResult::Type::ARR, "", "",
191 [ # # ]: 0 : {
192 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "walletname", "the wallet name"},
[ # # ]
193 : : }
194 : : },
195 [ # # ]: 0 : RPCExamples{
196 [ # # ][ # # ]: 0 : HelpExampleCli("listwallets", "")
[ # # ]
197 [ # # ][ # # ]: 0 : + HelpExampleRpc("listwallets", "")
[ # # ][ # # ]
198 : : },
199 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
200 : : {
201 [ # # ]: 0 : UniValue obj(UniValue::VARR);
202 : :
203 [ # # ]: 0 : WalletContext& context = EnsureWalletContext(request.context);
204 [ # # ][ # # ]: 0 : for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
205 [ # # ]: 0 : LOCK(wallet->cs_wallet);
206 [ # # ][ # # ]: 0 : obj.push_back(wallet->GetName());
207 : 0 : }
208 : :
209 : 0 : return obj;
210 [ # # ]: 0 : },
211 : : };
212 : 0 : }
213 : :
214 : 0 : static RPCHelpMan loadwallet()
215 : : {
216 [ # # ][ # # ]: 0 : return RPCHelpMan{"loadwallet",
[ # # ][ # # ]
[ # # ]
217 [ # # ]: 0 : "\nLoads a wallet from a wallet file or directory."
218 : : "\nNote that all wallet command-line options used when starting bitcoind will be"
219 : : "\napplied to the new wallet.\n",
220 [ # # ]: 0 : {
221 [ # # ][ # # ]: 0 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
[ # # ]
222 [ # # ][ # # ]: 0 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
[ # # ]
223 : : },
224 [ # # ][ # # ]: 0 : RPCResult{
225 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
226 [ # # ]: 0 : {
227 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
[ # # ]
228 [ # # ][ # # ]: 0 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.",
[ # # ]
229 [ # # ]: 0 : {
230 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "", ""},
[ # # ]
231 : : }},
232 : : }
233 : : },
234 [ # # ]: 0 : RPCExamples{
235 [ # # ][ # # ]: 0 : HelpExampleCli("loadwallet", "\"test.dat\"")
[ # # ]
236 [ # # ][ # # ]: 0 : + HelpExampleRpc("loadwallet", "\"test.dat\"")
[ # # ][ # # ]
237 : : },
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 : : 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 : : {
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 [ + - ][ # # ]: 2 : },
268 : : };
269 : 0 : }
270 : :
271 : 0 : static RPCHelpMan setwalletflag()
272 : : {
273 : 0 : std::string flags;
274 [ # # ]: 0 : for (auto& it : WALLET_FLAG_MAP)
275 [ # # ]: 0 : if (it.second & MUTABLE_WALLET_FLAGS)
276 [ # # ][ # # ]: 0 : flags += (flags == "" ? "" : ", ") + it.first;
[ # # ]
277 : :
278 [ # # ][ # # ]: 0 : return RPCHelpMan{"setwalletflag",
[ # # ][ # # ]
279 [ # # ]: 0 : "\nChange the state of the given wallet flag for a wallet.\n",
280 [ # # ]: 0 : {
281 [ # # ][ # # ]: 0 : {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
[ # # ]
282 [ # # ][ # # ]: 0 : {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
[ # # ][ # # ]
283 : : },
284 [ # # ][ # # ]: 0 : RPCResult{
285 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
286 [ # # ]: 0 : {
287 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
[ # # ]
288 [ # # ][ # # ]: 0 : {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
[ # # ]
289 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
[ # # ]
290 : : }
291 : : },
292 [ # # ]: 0 : RPCExamples{
293 [ # # ][ # # ]: 0 : HelpExampleCli("setwalletflag", "avoid_reuse")
[ # # ]
294 [ # # ][ # # ]: 0 : + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
[ # # ][ # # ]
295 : : },
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 : 0 : } 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 : 0 : }
332 : :
333 : 0 : return res;
334 [ # # ]: 0 : },
335 : : };
336 : 0 : }
337 : :
338 : 0 : static RPCHelpMan createwallet()
339 : : {
340 [ # # ][ # # ]: 0 : return RPCHelpMan{
[ # # ][ # # ]
341 [ # # ]: 0 : "createwallet",
342 [ # # ]: 0 : "\nCreates and loads a new wallet.\n",
343 [ # # ]: 0 : {
344 [ # # ][ # # ]: 0 : {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
[ # # ]
345 [ # # ][ # # ]: 0 : {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
[ # # ][ # # ]
346 [ # # ][ # # ]: 0 : {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
[ # # ][ # # ]
347 [ # # ][ # # ]: 0 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
[ # # ]
348 [ # # ][ # # ]: 0 : {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
[ # # ][ # # ]
349 [ # # ][ # # ]: 0 : {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."
[ # # ][ # # ]
350 : : " Setting to \"false\" will create a legacy wallet; This is only possible with the -deprecatedrpc=create_bdb setting because, the legacy wallet type is being deprecated and"
351 : : " support for creating and opening legacy wallets will be removed in the future."},
352 [ # # ][ # # ]: 0 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
[ # # ]
353 [ # # ][ # # ]: 0 : {"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
[ # # ][ # # ]
354 : : },
355 [ # # ][ # # ]: 0 : RPCResult{
356 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
357 [ # # ]: 0 : {
358 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."},
[ # # ]
359 [ # # ][ # # ]: 0 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.",
[ # # ]
360 [ # # ]: 0 : {
361 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "", ""},
[ # # ]
362 : : }},
363 : : }
364 : : },
365 [ # # ]: 0 : RPCExamples{
366 [ # # ][ # # ]: 0 : HelpExampleCli("createwallet", "\"testwallet\"")
[ # # ]
367 [ # # ][ # # ]: 0 : + HelpExampleRpc("createwallet", "\"testwallet\"")
[ # # ][ # # ]
368 [ # # ][ # # ]: 0 : + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
369 [ # # ][ # # ]: 0 : + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
370 : : },
371 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
372 : : {
373 : 0 : WalletContext& context = EnsureWalletContext(request.context);
374 : 0 : uint64_t flags = 0;
375 [ # # ][ # # ]: 0 : if (!request.params[1].isNull() && request.params[1].get_bool()) {
376 : 0 : flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
377 : 0 : }
378 : :
379 [ # # ][ # # ]: 0 : if (!request.params[2].isNull() && request.params[2].get_bool()) {
380 : 0 : flags |= WALLET_FLAG_BLANK_WALLET;
381 : 0 : }
382 : 0 : SecureString passphrase;
383 [ # # ]: 0 : passphrase.reserve(100);
384 : 0 : std::vector<bilingual_str> warnings;
385 [ # # ][ # # ]: 0 : if (!request.params[3].isNull()) {
[ # # ]
386 [ # # ][ # # ]: 0 : passphrase = std::string_view{request.params[3].get_str()};
[ # # ]
387 [ # # ]: 0 : if (passphrase.empty()) {
388 : : // Empty string means unencrypted
389 [ # # ][ # # ]: 0 : warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
[ # # ]
390 : 0 : }
391 : 0 : }
392 : :
393 [ # # ][ # # ]: 0 : if (!request.params[4].isNull() && request.params[4].get_bool()) {
[ # # ][ # # ]
[ # # ][ # # ]
394 : 0 : flags |= WALLET_FLAG_AVOID_REUSE;
395 : 0 : }
396 [ # # ][ # # ]: 0 : if (self.Arg<bool>(5)) {
397 : : #ifndef USE_SQLITE
398 : : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
399 : : #endif
400 : 0 : flags |= WALLET_FLAG_DESCRIPTORS;
401 : 0 : } else {
402 [ # # ][ # # ]: 0 : if (!context.chain->rpcEnableDeprecated("create_bdb")) {
[ # # ]
403 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "BDB wallet creation is deprecated and will be removed in a future release."
[ # # ]
404 : : " In this release it can be re-enabled temporarily with the -deprecatedrpc=create_bdb setting.");
405 : : }
406 : : }
407 [ # # ][ # # ]: 0 : if (!request.params[7].isNull() && request.params[7].get_bool()) {
[ # # ][ # # ]
[ # # ][ # # ]
408 : : #ifdef ENABLE_EXTERNAL_SIGNER
409 : : flags |= WALLET_FLAG_EXTERNAL_SIGNER;
410 : : #else
411 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
[ # # ]
412 : : #endif
413 : : }
414 : :
415 : : #ifndef USE_BDB
416 [ # # ]: 0 : if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
417 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
[ # # ]
418 : : }
419 : : #endif
420 : :
421 : 0 : DatabaseOptions options;
422 : : DatabaseStatus status;
423 [ # # ]: 0 : ReadDatabaseArgs(*context.args, options);
424 : 0 : options.require_create = true;
425 : 0 : options.create_flags = flags;
426 [ # # ]: 0 : options.create_passphrase = passphrase;
427 : 0 : bilingual_str error;
428 [ # # ][ # # ]: 0 : std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
[ # # ][ # # ]
[ # # ]
429 [ # # ][ # # ]: 0 : const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
[ # # ]
430 [ # # ]: 0 : if (!wallet) {
431 : 0 : RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
432 [ # # ]: 0 : throw JSONRPCError(code, error.original);
433 : : }
434 : :
435 [ # # ]: 0 : UniValue obj(UniValue::VOBJ);
436 [ # # ][ # # ]: 0 : obj.pushKV("name", wallet->GetName());
[ # # ][ # # ]
437 [ # # ]: 0 : PushWarnings(warnings, obj);
438 : :
439 : 0 : return obj;
440 [ # # ]: 0 : },
441 : : };
442 : 0 : }
443 : :
444 : 0 : static RPCHelpMan unloadwallet()
445 : : {
446 [ # # ][ # # ]: 0 : return RPCHelpMan{"unloadwallet",
[ # # ][ # # ]
[ # # ]
447 [ # # ]: 0 : "Unloads the wallet referenced by the request endpoint, otherwise unloads the wallet specified in the argument.\n"
448 : : "Specifying the wallet name on a wallet endpoint is invalid.",
449 [ # # ]: 0 : {
450 [ # # ][ # # ]: 0 : {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."},
[ # # ][ # # ]
451 [ # # ][ # # ]: 0 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
[ # # ]
452 : : },
453 [ # # ][ # # ]: 0 : RPCResult{RPCResult::Type::OBJ, "", "", {
[ # # ][ # # ]
[ # # ]
454 [ # # ][ # # ]: 0 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.",
[ # # ]
455 [ # # ]: 0 : {
456 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "", ""},
[ # # ]
457 : : }},
458 : : }},
459 [ # # ]: 0 : RPCExamples{
460 [ # # ][ # # ]: 0 : HelpExampleCli("unloadwallet", "wallet_name")
[ # # ]
461 [ # # ][ # # ]: 0 : + HelpExampleRpc("unloadwallet", "wallet_name")
[ # # ][ # # ]
462 : : },
463 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
464 : : {
465 : 0 : std::string wallet_name;
466 [ # # ][ # # ]: 0 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
467 [ # # ][ # # ]: 0 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
[ # # ][ # # ]
[ # # ]
468 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
[ # # ]
469 : : }
470 : 0 : } else {
471 [ # # ][ # # ]: 0 : wallet_name = request.params[0].get_str();
[ # # ]
472 : : }
473 : :
474 [ # # ]: 0 : WalletContext& context = EnsureWalletContext(request.context);
475 [ # # ]: 0 : std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
476 [ # # ]: 0 : if (!wallet) {
477 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
[ # # ]
478 : : }
479 : :
480 : 0 : std::vector<bilingual_str> warnings;
481 : : {
482 [ # # ]: 0 : WalletRescanReserver reserver(*wallet);
483 [ # # ][ # # ]: 0 : if (!reserver.reserve()) {
484 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
[ # # ]
485 : : }
486 : :
487 : : // Release the "main" shared pointer and prevent further notifications.
488 : : // Note that any attempt to load the same wallet would fail until the wallet
489 : : // is destroyed (see CheckUniqueFileid).
490 [ # # ]: 0 : std::optional<bool> load_on_start{self.MaybeArg<bool>(1)};
491 [ # # ][ # # ]: 0 : if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
492 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
[ # # ]
493 : : }
494 : 0 : }
495 : :
496 [ # # ]: 0 : UnloadWallet(std::move(wallet));
497 : :
498 [ # # ]: 0 : UniValue result(UniValue::VOBJ);
499 [ # # ]: 0 : PushWarnings(warnings, result);
500 : :
501 : 0 : return result;
502 [ # # ]: 0 : },
503 : : };
504 : 0 : }
505 : :
506 : 0 : static RPCHelpMan sethdseed()
507 : : {
508 [ # # ][ # # ]: 0 : return RPCHelpMan{"sethdseed",
[ # # ]
509 : : "\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n"
510 : : "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n"
511 [ # # ][ # # ]: 0 : "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + HELP_REQUIRING_PASSPHRASE +
512 : : "Note: This command is only compatible with legacy wallets.\n",
513 [ # # ]: 0 : {
514 [ # # ][ # # ]: 0 : {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
[ # # ][ # # ]
515 : : "If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
516 : : "If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n"
517 : : "keypool will be used until it has been depleted."},
518 [ # # ][ # # ]: 0 : {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n"
[ # # ][ # # ]
519 : : "The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"},
520 : : },
521 [ # # ][ # # ]: 0 : RPCResult{RPCResult::Type::NONE, "", ""},
[ # # ][ # # ]
522 [ # # ]: 0 : RPCExamples{
523 [ # # ][ # # ]: 0 : HelpExampleCli("sethdseed", "")
[ # # ]
524 [ # # ][ # # ]: 0 : + HelpExampleCli("sethdseed", "false")
[ # # ][ # # ]
525 [ # # ][ # # ]: 0 : + HelpExampleCli("sethdseed", "true \"wifkey\"")
[ # # ][ # # ]
526 [ # # ][ # # ]: 0 : + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
[ # # ][ # # ]
527 : : },
528 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
529 : : {
530 : 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
531 [ # # ][ # # ]: 0 : if (!pwallet) return UniValue::VNULL;
532 : :
533 [ # # ]: 0 : LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
534 : :
535 [ # # ][ # # ]: 0 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
536 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
[ # # ]
537 : : }
538 : :
539 [ # # ][ # # ]: 0 : LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
540 : :
541 : : // Do not do anything to non-HD wallets
542 [ # # ][ # # ]: 0 : if (!pwallet->CanSupportFeature(FEATURE_HD)) {
543 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set an HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
[ # # ]
544 : : }
545 : :
546 [ # # ]: 0 : EnsureWalletIsUnlocked(*pwallet);
547 : :
548 : 0 : bool flush_key_pool = true;
549 [ # # ][ # # ]: 0 : if (!request.params[0].isNull()) {
550 [ # # ][ # # ]: 0 : flush_key_pool = request.params[0].get_bool();
551 : 0 : }
552 : :
553 [ # # ]: 0 : CPubKey master_pub_key;
554 [ # # ][ # # ]: 0 : if (request.params[1].isNull()) {
555 [ # # ]: 0 : master_pub_key = spk_man.GenerateNewSeed();
556 : 0 : } else {
557 [ # # ][ # # ]: 0 : CKey key = DecodeSecret(request.params[1].get_str());
[ # # ]
558 [ # # ][ # # ]: 0 : if (!key.IsValid()) {
559 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
[ # # ]
560 : : }
561 : :
562 [ # # ][ # # ]: 0 : if (HaveKey(spk_man, key)) {
563 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
[ # # ]
564 : : }
565 : :
566 [ # # ]: 0 : master_pub_key = spk_man.DeriveNewSeed(key);
567 : 0 : }
568 : :
569 [ # # ]: 0 : spk_man.SetHDSeed(master_pub_key);
570 [ # # ][ # # ]: 0 : if (flush_key_pool) spk_man.NewKeyPool();
571 : :
572 [ # # ]: 0 : return UniValue::VNULL;
573 : 0 : },
574 : : };
575 : 0 : }
576 : :
577 : 0 : static RPCHelpMan upgradewallet()
578 : : {
579 [ # # ][ # # ]: 0 : return RPCHelpMan{"upgradewallet",
[ # # ][ # # ]
580 [ # # ]: 0 : "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
581 : : "New keys may be generated and a new wallet backup will need to be made.",
582 [ # # ]: 0 : {
583 [ # # ][ # # ]: 0 : {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
[ # # ][ # # ]
584 : : },
585 [ # # ][ # # ]: 0 : RPCResult{
586 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
587 [ # # ]: 0 : {
588 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
[ # # ]
589 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
[ # # ]
590 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
[ # # ]
591 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "result", /*optional=*/true, "Description of result, if no error"},
[ # # ]
592 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}
[ # # ]
593 : : },
594 : : },
595 [ # # ]: 0 : RPCExamples{
596 [ # # ][ # # ]: 0 : HelpExampleCli("upgradewallet", "169900")
[ # # ]
597 [ # # ][ # # ]: 0 : + HelpExampleRpc("upgradewallet", "169900")
[ # # ][ # # ]
598 : : },
599 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
600 : : {
601 : 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
602 [ # # ][ # # ]: 0 : if (!pwallet) return UniValue::VNULL;
603 : :
604 [ # # ]: 0 : EnsureWalletIsUnlocked(*pwallet);
605 : :
606 : 0 : int version = 0;
607 [ # # ][ # # ]: 0 : if (!request.params[0].isNull()) {
608 [ # # ][ # # ]: 0 : version = request.params[0].getInt<int>();
609 : 0 : }
610 : 0 : bilingual_str error;
611 [ # # ]: 0 : const int previous_version{pwallet->GetVersion()};
612 [ # # ]: 0 : const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
613 [ # # ]: 0 : const int current_version{pwallet->GetVersion()};
614 : 0 : std::string result;
615 : :
616 [ # # ]: 0 : if (wallet_upgraded) {
617 [ # # ]: 0 : if (previous_version == current_version) {
618 [ # # ]: 0 : result = "Already at latest version. Wallet version unchanged.";
619 : 0 : } else {
620 [ # # ]: 0 : result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
621 : : }
622 : 0 : }
623 : :
624 [ # # ]: 0 : UniValue obj(UniValue::VOBJ);
625 [ # # ][ # # ]: 0 : obj.pushKV("wallet_name", pwallet->GetName());
[ # # ]
626 [ # # ][ # # ]: 0 : obj.pushKV("previous_version", previous_version);
[ # # ]
627 [ # # ][ # # ]: 0 : obj.pushKV("current_version", current_version);
[ # # ]
628 [ # # ]: 0 : if (!result.empty()) {
629 [ # # ][ # # ]: 0 : obj.pushKV("result", result);
[ # # ]
630 : 0 : } else {
631 [ # # ][ # # ]: 0 : CHECK_NONFATAL(!error.empty());
632 [ # # ][ # # ]: 0 : obj.pushKV("error", error.original);
[ # # ]
633 : : }
634 : 0 : return obj;
635 [ # # ]: 0 : },
636 : : };
637 : 0 : }
638 : :
639 : 0 : RPCHelpMan simulaterawtransaction()
640 : : {
641 [ # # ][ # # ]: 0 : return RPCHelpMan{"simulaterawtransaction",
[ # # ][ # # ]
[ # # ][ # # ]
642 [ # # ]: 0 : "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
643 [ # # ]: 0 : {
644 [ # # ][ # # ]: 0 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n",
[ # # ]
645 [ # # ]: 0 : {
646 [ # # ][ # # ]: 0 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
[ # # ]
647 : : },
648 : : },
649 [ # # ][ # # ]: 0 : {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
[ # # ]
650 [ # # ]: 0 : {
651 [ # # ][ # # ]: 0 : {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"},
[ # # ][ # # ]
652 : : },
653 : : },
654 : : },
655 [ # # ][ # # ]: 0 : RPCResult{
656 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
657 [ # # ]: 0 : {
658 [ # # ][ # # ]: 0 : {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
[ # # ]
659 : : }
660 : : },
661 [ # # ]: 0 : RPCExamples{
662 [ # # ][ # # ]: 0 : HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
[ # # ]
663 [ # # ][ # # ]: 0 : + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
[ # # ][ # # ]
664 : : },
665 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
666 : : {
667 : 0 : const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
668 [ # # ][ # # ]: 0 : if (!rpc_wallet) return UniValue::VNULL;
669 : 0 : const CWallet& wallet = *rpc_wallet;
670 : :
671 [ # # ]: 0 : LOCK(wallet.cs_wallet);
672 : :
673 [ # # ]: 0 : UniValue include_watchonly(UniValue::VNULL);
674 [ # # ][ # # ]: 0 : if (request.params[1].isObject()) {
[ # # ]
675 [ # # ][ # # ]: 0 : UniValue options = request.params[1];
676 [ # # ]: 0 : RPCTypeCheckObj(options,
677 [ # # ]: 0 : {
678 [ # # ][ # # ]: 0 : {"include_watchonly", UniValueType(UniValue::VBOOL)},
679 : : },
680 : : true, true);
681 : :
682 [ # # ][ # # ]: 0 : include_watchonly = options["include_watchonly"];
[ # # ]
683 : 0 : }
684 : :
685 : 0 : isminefilter filter = ISMINE_SPENDABLE;
686 [ # # ][ # # ]: 0 : if (ParseIncludeWatchonly(include_watchonly, wallet)) {
687 : 0 : filter |= ISMINE_WATCH_ONLY;
688 : 0 : }
689 : :
690 [ # # ][ # # ]: 0 : const auto& txs = request.params[0].get_array();
691 : 0 : CAmount changes{0};
692 : 0 : std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
693 : 0 : std::set<COutPoint> spent;
694 : :
695 [ # # ][ # # ]: 0 : for (size_t i = 0; i < txs.size(); ++i) {
696 [ # # ]: 0 : CMutableTransaction mtx;
697 [ # # ][ # # ]: 0 : if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) {
[ # # ][ # # ]
698 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
[ # # ]
699 : : }
700 : :
701 : : // Fetch previous transactions (inputs)
702 : 0 : std::map<COutPoint, Coin> coins;
703 [ # # ]: 0 : for (const CTxIn& txin : mtx.vin) {
704 [ # # ]: 0 : coins[txin.prevout]; // Create empty map entry keyed by prevout.
705 : : }
706 [ # # ][ # # ]: 0 : wallet.chain().findCoins(coins);
707 : :
708 : : // Fetch debit; we are *spending* these; if the transaction is signed and
709 : : // broadcast, we will lose everything in these
710 [ # # ]: 0 : for (const auto& txin : mtx.vin) {
711 : 0 : const auto& outpoint = txin.prevout;
712 [ # # ][ # # ]: 0 : if (spent.count(outpoint)) {
713 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
[ # # ]
714 : : }
715 [ # # ][ # # ]: 0 : if (new_utxos.count(outpoint)) {
716 [ # # ]: 0 : changes -= new_utxos.at(outpoint);
717 [ # # ]: 0 : new_utxos.erase(outpoint);
718 : 0 : } else {
719 [ # # ][ # # ]: 0 : if (coins.at(outpoint).IsSpent()) {
[ # # ]
720 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
[ # # ]
721 : : }
722 [ # # ]: 0 : changes -= wallet.GetDebit(txin, filter);
723 : : }
724 [ # # ]: 0 : spent.insert(outpoint);
725 : : }
726 : :
727 : : // Iterate over outputs; we are *receiving* these, if the wallet considers
728 : : // them "mine"; if the transaction is signed and broadcast, we will receive
729 : : // everything in these
730 : : // Also populate new_utxos in case these are spent in later transactions
731 : :
732 [ # # ]: 0 : const auto& hash = mtx.GetHash();
733 [ # # ]: 0 : for (size_t i = 0; i < mtx.vout.size(); ++i) {
734 : 0 : const auto& txout = mtx.vout[i];
735 [ # # ]: 0 : bool is_mine = 0 < (wallet.IsMine(txout) & filter);
736 [ # # ][ # # ]: 0 : changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
[ # # ]
737 : 0 : }
738 : 0 : }
739 : :
740 [ # # ]: 0 : UniValue result(UniValue::VOBJ);
741 [ # # ][ # # ]: 0 : result.pushKV("balance_change", ValueFromAmount(changes));
[ # # ]
742 : :
743 : 0 : return result;
744 [ # # ]: 0 : }
745 : : };
746 : 0 : }
747 : :
748 : 0 : static RPCHelpMan migratewallet()
749 : : {
750 [ # # ][ # # ]: 0 : return RPCHelpMan{"migratewallet",
[ # # ][ # # ]
751 [ # # ]: 0 : "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n"
752 : : "\nMigrate the wallet to a descriptor wallet.\n"
753 : : "A new wallet backup will need to be made.\n"
754 : : "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
755 : : "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
756 : : "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
757 : : "\nEncrypted wallets must have the passphrase provided as an argument to this call.",
758 [ # # ]: 0 : {
759 [ # # ][ # # ]: 0 : {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
[ # # ][ # # ]
760 [ # # ][ # # ]: 0 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
[ # # ]
761 : : },
762 [ # # ][ # # ]: 0 : RPCResult{
763 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
764 [ # # ]: 0 : {
765 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
[ # # ]
766 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
[ # # ]
767 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
[ # # ]
768 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
[ # # ]
769 : : }
770 : : },
771 [ # # ]: 0 : RPCExamples{
772 [ # # ][ # # ]: 0 : HelpExampleCli("migratewallet", "")
[ # # ]
773 [ # # ][ # # ]: 0 : + HelpExampleRpc("migratewallet", "")
[ # # ][ # # ]
774 : : },
775 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
776 : : {
777 : 0 : std::string wallet_name;
778 [ # # ][ # # ]: 0 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
779 [ # # ][ # # ]: 0 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
[ # # ][ # # ]
[ # # ]
780 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
[ # # ]
781 : : }
782 : 0 : } else {
783 [ # # ][ # # ]: 0 : if (request.params[0].isNull()) {
784 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Either RPC endpoint wallet or wallet_name parameter must be provided");
[ # # ]
785 : : }
786 [ # # ][ # # ]: 0 : wallet_name = request.params[0].get_str();
[ # # ]
787 : : }
788 : :
789 : 0 : SecureString wallet_pass;
790 [ # # ]: 0 : wallet_pass.reserve(100);
791 [ # # ][ # # ]: 0 : if (!request.params[1].isNull()) {
792 [ # # ][ # # ]: 0 : wallet_pass = std::string_view{request.params[1].get_str()};
[ # # ]
793 : 0 : }
794 : :
795 [ # # ]: 0 : WalletContext& context = EnsureWalletContext(request.context);
796 [ # # ]: 0 : util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
797 [ # # ]: 0 : if (!res) {
798 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
[ # # ]
799 : : }
800 : :
801 [ # # ]: 0 : UniValue r{UniValue::VOBJ};
802 [ # # ][ # # ]: 0 : r.pushKV("wallet_name", res->wallet_name);
[ # # ][ # # ]
803 [ # # ][ # # ]: 0 : if (res->watchonly_wallet) {
804 [ # # ][ # # ]: 0 : r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
[ # # ][ # # ]
805 : 0 : }
806 [ # # ][ # # ]: 0 : if (res->solvables_wallet) {
807 [ # # ][ # # ]: 0 : r.pushKV("solvables_name", res->solvables_wallet->GetName());
[ # # ][ # # ]
808 : 0 : }
809 [ # # ][ # # ]: 0 : r.pushKV("backup_path", res->backup_path.utf8string());
[ # # ][ # # ]
[ # # ]
810 : :
811 : 0 : return r;
812 [ # # ]: 0 : },
813 : : };
814 : 0 : }
815 : :
816 : : // addresses
817 : : RPCHelpMan getaddressinfo();
818 : : RPCHelpMan getnewaddress();
819 : : RPCHelpMan getrawchangeaddress();
820 : : RPCHelpMan setlabel();
821 : : RPCHelpMan listaddressgroupings();
822 : : RPCHelpMan addmultisigaddress();
823 : : RPCHelpMan keypoolrefill();
824 : : RPCHelpMan newkeypool();
825 : : RPCHelpMan getaddressesbylabel();
826 : : RPCHelpMan listlabels();
827 : : #ifdef ENABLE_EXTERNAL_SIGNER
828 : : RPCHelpMan walletdisplayaddress();
829 : : #endif // ENABLE_EXTERNAL_SIGNER
830 : :
831 : : // backup
832 : : RPCHelpMan dumpprivkey();
833 : : RPCHelpMan importprivkey();
834 : : RPCHelpMan importaddress();
835 : : RPCHelpMan importpubkey();
836 : : RPCHelpMan dumpwallet();
837 : : RPCHelpMan importwallet();
838 : : RPCHelpMan importprunedfunds();
839 : : RPCHelpMan removeprunedfunds();
840 : : RPCHelpMan importmulti();
841 : : RPCHelpMan importdescriptors();
842 : : RPCHelpMan listdescriptors();
843 : : RPCHelpMan backupwallet();
844 : : RPCHelpMan restorewallet();
845 : :
846 : : // coins
847 : : RPCHelpMan getreceivedbyaddress();
848 : : RPCHelpMan getreceivedbylabel();
849 : : RPCHelpMan getbalance();
850 : : RPCHelpMan getunconfirmedbalance();
851 : : RPCHelpMan lockunspent();
852 : : RPCHelpMan listlockunspent();
853 : : RPCHelpMan getbalances();
854 : : RPCHelpMan listunspent();
855 : :
856 : : // encryption
857 : : RPCHelpMan walletpassphrase();
858 : : RPCHelpMan walletpassphrasechange();
859 : : RPCHelpMan walletlock();
860 : : RPCHelpMan encryptwallet();
861 : :
862 : : // spend
863 : : RPCHelpMan sendtoaddress();
864 : : RPCHelpMan sendmany();
865 : : RPCHelpMan settxfee();
866 : : RPCHelpMan fundrawtransaction();
867 : : RPCHelpMan bumpfee();
868 : : RPCHelpMan psbtbumpfee();
869 : : RPCHelpMan send();
870 : : RPCHelpMan sendall();
871 : : RPCHelpMan walletprocesspsbt();
872 : : RPCHelpMan walletcreatefundedpsbt();
873 : : RPCHelpMan signrawtransactionwithwallet();
874 : :
875 : : // signmessage
876 : : RPCHelpMan signmessage();
877 : :
878 : : // transactions
879 : : RPCHelpMan listreceivedbyaddress();
880 : : RPCHelpMan listreceivedbylabel();
881 : : RPCHelpMan listtransactions();
882 : : RPCHelpMan listsinceblock();
883 : : RPCHelpMan gettransaction();
884 : : RPCHelpMan abandontransaction();
885 : : RPCHelpMan rescanblockchain();
886 : : RPCHelpMan abortrescan();
887 : :
888 : 0 : Span<const CRPCCommand> GetWalletRPCCommands()
889 : : {
890 [ # # ][ # # ]: 0 : static const CRPCCommand commands[]{
[ # # ]
891 [ # # ][ # # ]: 0 : {"rawtransactions", &fundrawtransaction},
892 [ # # ][ # # ]: 0 : {"wallet", &abandontransaction},
893 [ # # ][ # # ]: 0 : {"wallet", &abortrescan},
894 [ # # ][ # # ]: 0 : {"wallet", &addmultisigaddress},
895 [ # # ][ # # ]: 0 : {"wallet", &backupwallet},
896 [ # # ][ # # ]: 0 : {"wallet", &bumpfee},
897 [ # # ][ # # ]: 0 : {"wallet", &psbtbumpfee},
898 [ # # ][ # # ]: 0 : {"wallet", &createwallet},
899 [ # # ][ # # ]: 0 : {"wallet", &restorewallet},
900 [ # # ][ # # ]: 0 : {"wallet", &dumpprivkey},
901 [ # # ][ # # ]: 0 : {"wallet", &dumpwallet},
902 [ # # ][ # # ]: 0 : {"wallet", &encryptwallet},
903 [ # # ][ # # ]: 0 : {"wallet", &getaddressesbylabel},
904 [ # # ][ # # ]: 0 : {"wallet", &getaddressinfo},
905 [ # # ][ # # ]: 0 : {"wallet", &getbalance},
906 [ # # ][ # # ]: 0 : {"wallet", &getnewaddress},
907 [ # # ][ # # ]: 0 : {"wallet", &getrawchangeaddress},
908 [ # # ][ # # ]: 0 : {"wallet", &getreceivedbyaddress},
909 [ # # ][ # # ]: 0 : {"wallet", &getreceivedbylabel},
910 [ # # ][ # # ]: 0 : {"wallet", &gettransaction},
911 [ # # ][ # # ]: 0 : {"wallet", &getunconfirmedbalance},
912 [ # # ][ # # ]: 0 : {"wallet", &getbalances},
913 [ # # ][ # # ]: 0 : {"wallet", &getwalletinfo},
914 [ # # ][ # # ]: 0 : {"wallet", &importaddress},
915 [ # # ][ # # ]: 0 : {"wallet", &importdescriptors},
916 [ # # ][ # # ]: 0 : {"wallet", &importmulti},
917 [ # # ][ # # ]: 0 : {"wallet", &importprivkey},
918 [ # # ][ # # ]: 0 : {"wallet", &importprunedfunds},
919 [ # # ][ # # ]: 0 : {"wallet", &importpubkey},
920 [ # # ][ # # ]: 0 : {"wallet", &importwallet},
921 [ # # ][ # # ]: 0 : {"wallet", &keypoolrefill},
922 [ # # ][ # # ]: 0 : {"wallet", &listaddressgroupings},
923 [ # # ][ # # ]: 0 : {"wallet", &listdescriptors},
924 [ # # ][ # # ]: 0 : {"wallet", &listlabels},
925 [ # # ][ # # ]: 0 : {"wallet", &listlockunspent},
926 [ # # ][ # # ]: 0 : {"wallet", &listreceivedbyaddress},
927 [ # # ][ # # ]: 0 : {"wallet", &listreceivedbylabel},
928 [ # # ][ # # ]: 0 : {"wallet", &listsinceblock},
929 [ # # ][ # # ]: 0 : {"wallet", &listtransactions},
930 [ # # ][ # # ]: 0 : {"wallet", &listunspent},
931 [ # # ][ # # ]: 0 : {"wallet", &listwalletdir},
932 [ # # ][ # # ]: 0 : {"wallet", &listwallets},
933 [ # # ][ # # ]: 0 : {"wallet", &loadwallet},
934 [ # # ][ # # ]: 0 : {"wallet", &lockunspent},
935 [ # # ][ # # ]: 0 : {"wallet", &migratewallet},
936 [ # # ][ # # ]: 0 : {"wallet", &newkeypool},
937 [ # # ][ # # ]: 0 : {"wallet", &removeprunedfunds},
938 [ # # ][ # # ]: 0 : {"wallet", &rescanblockchain},
939 [ # # ][ # # ]: 0 : {"wallet", &send},
940 [ # # ][ # # ]: 0 : {"wallet", &sendmany},
941 [ # # ][ # # ]: 0 : {"wallet", &sendtoaddress},
942 [ # # ][ # # ]: 0 : {"wallet", &sethdseed},
943 [ # # ][ # # ]: 0 : {"wallet", &setlabel},
944 [ # # ][ # # ]: 0 : {"wallet", &settxfee},
945 [ # # ][ # # ]: 0 : {"wallet", &setwalletflag},
946 [ # # ][ # # ]: 0 : {"wallet", &signmessage},
947 [ # # ][ # # ]: 0 : {"wallet", &signrawtransactionwithwallet},
948 [ # # ][ # # ]: 0 : {"wallet", &simulaterawtransaction},
949 [ # # ][ # # ]: 0 : {"wallet", &sendall},
950 [ # # ][ # # ]: 0 : {"wallet", &unloadwallet},
951 [ # # ][ # # ]: 0 : {"wallet", &upgradewallet},
952 [ # # ][ # # ]: 0 : {"wallet", &walletcreatefundedpsbt},
953 : : #ifdef ENABLE_EXTERNAL_SIGNER
954 : : {"wallet", &walletdisplayaddress},
955 : : #endif // ENABLE_EXTERNAL_SIGNER
956 [ # # ][ # # ]: 0 : {"wallet", &walletlock},
957 [ # # ][ # # ]: 0 : {"wallet", &walletpassphrase},
958 [ # # ][ # # ]: 0 : {"wallet", &walletpassphrasechange},
959 [ # # ][ # # ]: 0 : {"wallet", &walletprocesspsbt},
960 : : };
961 : 0 : return commands;
962 : 0 : }
963 : : } // namespace wallet
|