addresses.cpp raw
1 // Copyright (c) 2011-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <bitcoin-build-config.h> // IWYU pragma: keep
6
7 #include <core_io.h>
8 #include <key_io.h>
9 #include <rpc/util.h>
10 #include <script/script.h>
11 #include <script/solver.h>
12 #include <util/bip32.h>
13 #include <util/translation.h>
14 #include <wallet/receive.h>
15 #include <wallet/rpc/util.h>
16 #include <wallet/wallet.h>
17
18 #include <univalue.h>
19
20 namespace wallet {
21 RPCMethod getnewaddress()
22 {
23 return RPCMethod{
24 "getnewaddress",
25 "Returns a new Bitcoin address for receiving payments.\n"
26 "If 'label' is specified, it is added to the address book \n"
27 "so payments received with the address will be associated with 'label'.\n",
28 {
29 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."},
30 {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are " + FormatAllOutputTypes() + "."},
31 },
32 RPCResult{
33 RPCResult::Type::STR, "address", "The new bitcoin address"
34 },
35 RPCExamples{
36 HelpExampleCli("getnewaddress", "")
37 + HelpExampleRpc("getnewaddress", "")
38 },
39 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
40 {
41 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
42 if (!pwallet) return UniValue::VNULL;
43
44 LOCK(pwallet->cs_wallet);
45
46 if (!pwallet->CanGetAddresses()) {
47 throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
48 }
49
50 // Parse the label first so we don't generate a key if there's an error
51 const std::string label{LabelFromValue(request.params[0])};
52
53 OutputType output_type = pwallet->m_default_address_type;
54 if (!request.params[1].isNull()) {
55 std::optional<OutputType> parsed = ParseOutputType(request.params[1].get_str());
56 if (!parsed) {
57 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
58 }
59 output_type = parsed.value();
60 }
61
62 auto op_dest = pwallet->GetNewDestination(output_type, label);
63 if (!op_dest) {
64 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
65 }
66
67 return EncodeDestination(*op_dest);
68 },
69 };
70 }
71
72 RPCMethod getrawchangeaddress()
73 {
74 return RPCMethod{
75 "getrawchangeaddress",
76 "Returns a new Bitcoin address, for receiving change.\n"
77 "This is for use with raw transactions, NOT normal use.\n",
78 {
79 {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The address type to use. Options are " + FormatAllOutputTypes() + "."},
80 },
81 RPCResult{
82 RPCResult::Type::STR, "address", "The address"
83 },
84 RPCExamples{
85 HelpExampleCli("getrawchangeaddress", "")
86 + HelpExampleRpc("getrawchangeaddress", "")
87 },
88 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
89 {
90 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
91 if (!pwallet) return UniValue::VNULL;
92
93 LOCK(pwallet->cs_wallet);
94
95 if (!pwallet->CanGetAddresses(true)) {
96 throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
97 }
98
99 OutputType output_type = pwallet->m_default_change_type.value_or(pwallet->m_default_address_type);
100 if (!request.params[0].isNull()) {
101 std::optional<OutputType> parsed = ParseOutputType(request.params[0].get_str());
102 if (!parsed) {
103 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
104 }
105 output_type = parsed.value();
106 }
107
108 auto op_dest = pwallet->GetNewChangeDestination(output_type);
109 if (!op_dest) {
110 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
111 }
112 return EncodeDestination(*op_dest);
113 },
114 };
115 }
116
117
118 RPCMethod setlabel()
119 {
120 return RPCMethod{
121 "setlabel",
122 "Sets the label associated with the given address.\n",
123 {
124 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to be associated with a label."},
125 {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label to assign to the address."},
126 },
127 RPCResult{RPCResult::Type::NONE, "", ""},
128 RPCExamples{
129 HelpExampleCli("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\" \"tabby\"")
130 + HelpExampleRpc("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\", \"tabby\"")
131 },
132 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
133 {
134 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
135 if (!pwallet) return UniValue::VNULL;
136
137 LOCK(pwallet->cs_wallet);
138
139 CTxDestination dest = DecodeDestination(request.params[0].get_str());
140 if (!IsValidDestination(dest)) {
141 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
142 }
143
144 const std::string label{LabelFromValue(request.params[1])};
145
146 if (pwallet->IsMine(dest)) {
147 pwallet->SetAddressBook(dest, label, AddressPurpose::RECEIVE);
148 } else {
149 pwallet->SetAddressBook(dest, label, AddressPurpose::SEND);
150 }
151
152 return UniValue::VNULL;
153 },
154 };
155 }
156
157 RPCMethod listaddressgroupings()
158 {
159 return RPCMethod{
160 "listaddressgroupings",
161 "Lists groups of addresses which have had their common ownership\n"
162 "made public by common use as inputs or as the resulting change\n"
163 "in past transactions\n",
164 {},
165 RPCResult{
166 RPCResult::Type::ARR, "", "",
167 {
168 {RPCResult::Type::ARR, "", "",
169 {
170 {RPCResult::Type::ARR_FIXED, "", "",
171 {
172 {RPCResult::Type::STR, "address", "The bitcoin address"},
173 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
174 {RPCResult::Type::STR, "label", /*optional=*/true, "The label"},
175 }},
176 }},
177 }
178 },
179 RPCExamples{
180 HelpExampleCli("listaddressgroupings", "")
181 + HelpExampleRpc("listaddressgroupings", "")
182 },
183 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
184 {
185 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
186 if (!pwallet) return UniValue::VNULL;
187
188 // Make sure the results are valid at least up to the most recent block
189 // the user could have gotten from another RPC command prior to now
190 pwallet->BlockUntilSyncedToCurrentChain();
191
192 LOCK(pwallet->cs_wallet);
193
194 UniValue jsonGroupings(UniValue::VARR);
195 std::map<CTxDestination, CAmount> balances = GetAddressBalances(*pwallet);
196 for (const std::set<CTxDestination>& grouping : GetAddressGroupings(*pwallet)) {
197 UniValue jsonGrouping(UniValue::VARR);
198 for (const CTxDestination& address : grouping)
199 {
200 UniValue addressInfo(UniValue::VARR);
201 addressInfo.push_back(EncodeDestination(address));
202 addressInfo.push_back(ValueFromAmount(balances[address]));
203 {
204 const auto* address_book_entry = pwallet->FindAddressBookEntry(address);
205 if (address_book_entry) {
206 addressInfo.push_back(address_book_entry->GetLabel());
207 }
208 }
209 jsonGrouping.push_back(std::move(addressInfo));
210 }
211 jsonGroupings.push_back(std::move(jsonGrouping));
212 }
213 return jsonGroupings;
214 },
215 };
216 }
217
218 RPCMethod keypoolrefill()
219 {
220 return RPCMethod{"keypoolrefill",
221 "Refills each descriptor keypool in the wallet up to the specified number of new keys.\n"
222 "By default, descriptor wallets have 4 active ranged descriptors (" + FormatAllOutputTypes() + "), each with " + util::ToString(DEFAULT_KEYPOOL_SIZE) + " entries.\n" +
223 HELP_REQUIRING_PASSPHRASE,
224 {
225 {"newsize", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%u, or as set by -keypool", DEFAULT_KEYPOOL_SIZE)}, "The new keypool size"},
226 },
227 RPCResult{RPCResult::Type::NONE, "", ""},
228 RPCExamples{
229 HelpExampleCli("keypoolrefill", "")
230 + HelpExampleRpc("keypoolrefill", "")
231 },
232 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
233 {
234 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
235 if (!pwallet) return UniValue::VNULL;
236
237 LOCK(pwallet->cs_wallet);
238
239 // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool
240 unsigned int kpSize = 0;
241 if (!request.params[0].isNull()) {
242 if (request.params[0].getInt<int>() < 0)
243 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size.");
244 kpSize = (unsigned int)request.params[0].getInt<int>();
245 }
246
247 EnsureWalletIsUnlocked(*pwallet);
248 pwallet->TopUpKeyPool(kpSize);
249
250 if (pwallet->GetKeyPoolSize() < kpSize) {
251 throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
252 }
253 pwallet->RefreshAllTXOs();
254
255 return UniValue::VNULL;
256 },
257 };
258 }
259
260 class DescribeWalletAddressVisitor
261 {
262 public:
263 const SigningProvider * const provider;
264
265 // NOLINTNEXTLINE(misc-no-recursion)
266 void ProcessSubScript(const CScript& subscript, UniValue& obj) const
267 {
268 // Always present: script type and redeemscript
269 std::vector<std::vector<unsigned char>> solutions_data;
270 TxoutType which_type = Solver(subscript, solutions_data);
271 obj.pushKV("script", GetTxnOutputType(which_type));
272 obj.pushKV("hex", HexStr(subscript));
273
274 CTxDestination embedded;
275 if (ExtractDestination(subscript, embedded)) {
276 // Only when the script corresponds to an address.
277 UniValue subobj(UniValue::VOBJ);
278 UniValue detail = DescribeAddress(embedded);
279 subobj.pushKVs(std::move(detail));
280 UniValue wallet_detail = std::visit(*this, embedded);
281 subobj.pushKVs(std::move(wallet_detail));
282 subobj.pushKV("address", EncodeDestination(embedded));
283 subobj.pushKV("scriptPubKey", HexStr(subscript));
284 // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works.
285 if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]);
286 obj.pushKV("embedded", std::move(subobj));
287 } else if (which_type == TxoutType::MULTISIG) {
288 // Also report some information on multisig scripts (which do not have a corresponding address).
289 obj.pushKV("sigsrequired", solutions_data[0][0]);
290 UniValue pubkeys(UniValue::VARR);
291 for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
292 CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
293 pubkeys.push_back(HexStr(key));
294 }
295 obj.pushKV("pubkeys", std::move(pubkeys));
296 }
297 }
298
299 explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {}
300
301 UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
302 UniValue operator()(const PubKeyDestination& dest) const { return UniValue(UniValue::VOBJ); }
303
304 UniValue operator()(const PKHash& pkhash) const
305 {
306 CKeyID keyID{ToKeyID(pkhash)};
307 UniValue obj(UniValue::VOBJ);
308 CPubKey vchPubKey;
309 if (provider && provider->GetPubKey(keyID, vchPubKey)) {
310 obj.pushKV("pubkey", HexStr(vchPubKey));
311 obj.pushKV("iscompressed", vchPubKey.IsCompressed());
312 }
313 return obj;
314 }
315
316 // NOLINTNEXTLINE(misc-no-recursion)
317 UniValue operator()(const ScriptHash& scripthash) const
318 {
319 UniValue obj(UniValue::VOBJ);
320 CScript subscript;
321 if (provider && provider->GetCScript(ToScriptID(scripthash), subscript)) {
322 ProcessSubScript(subscript, obj);
323 }
324 return obj;
325 }
326
327 UniValue operator()(const WitnessV0KeyHash& id) const
328 {
329 UniValue obj(UniValue::VOBJ);
330 CPubKey pubkey;
331 if (provider && provider->GetPubKey(ToKeyID(id), pubkey)) {
332 obj.pushKV("pubkey", HexStr(pubkey));
333 }
334 return obj;
335 }
336
337 // NOLINTNEXTLINE(misc-no-recursion)
338 UniValue operator()(const WitnessV0ScriptHash& id) const
339 {
340 UniValue obj(UniValue::VOBJ);
341 CScript subscript;
342 CRIPEMD160 hasher;
343 uint160 hash;
344 hasher.Write(id.begin(), 32).Finalize(hash.begin());
345 if (provider && provider->GetCScript(CScriptID(hash), subscript)) {
346 ProcessSubScript(subscript, obj);
347 }
348 return obj;
349 }
350
351 UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); }
352 UniValue operator()(const PayToAnchor& id) const { return UniValue(UniValue::VOBJ); }
353 UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
354 };
355
356 static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestination& dest)
357 {
358 UniValue ret(UniValue::VOBJ);
359 UniValue detail = DescribeAddress(dest);
360 CScript script = GetScriptForDestination(dest);
361 std::unique_ptr<SigningProvider> provider = nullptr;
362 provider = wallet.GetSolvingProvider(script);
363 ret.pushKVs(std::move(detail));
364 ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
365 return ret;
366 }
367
368 // NOLINTNEXTLINE(misc-no-recursion)
369 static std::vector<RPCResult> GetAddressInfoEmbeddedFields(bool include_nested)
370 {
371 auto fields = std::vector<RPCResult>{
372 {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the embedded script."},
373 {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded output script generated by the address."},
374 {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script."},
375 {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address."},
376 {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program."},
377 {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program."},
378 {RPCResult::Type::STR, "script", /*optional=*/true,
379 "The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
380 "types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
381 "witness_v0_scripthash, witness_unknown."},
382 {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The redeemscript for the p2sh address."},
383 {RPCResult::Type::ARR, "pubkeys", /*optional=*/true,
384 "Array of pubkeys associated with the known redeemscript (only if script is multisig).",
385 {
386 {RPCResult::Type::STR, "pubkey", ""},
387 }},
388 {RPCResult::Type::NUM, "sigsrequired", /*optional=*/true,
389 "The number of signatures required to spend multisig output (only if script is multisig)."},
390 {RPCResult::Type::STR_HEX, "pubkey", /*optional=*/true,
391 "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH)."},
392 };
393
394 if (include_nested) {
395 fields.emplace_back(
396 RPCResult::Type::OBJ,
397 "embedded",
398 /*optional=*/true,
399 "Information about the address embedded in P2SH or P2WSH, if relevant and known.",
400 GetAddressInfoEmbeddedFields(/*include_nested=*/false)
401 );
402 }
403
404 fields.emplace_back(
405 RPCResult::Type::BOOL,
406 "iscompressed",
407 /*optional=*/true,
408 "If the pubkey is compressed."
409 );
410
411 return fields;
412 }
413
414 RPCMethod getaddressinfo()
415 {
416 return RPCMethod{
417 "getaddressinfo",
418 "Return information about the given bitcoin address.\n"
419 "Some of the information will only be present if the address is in the active wallet.\n",
420 {
421 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for which to get information."},
422 },
423 RPCResult{
424 RPCResult::Type::OBJ, "", "",
425 {
426 {RPCResult::Type::STR, "address", "The bitcoin address validated."},
427 {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address."},
428 {RPCResult::Type::BOOL, "ismine", "If the address is yours."},
429 {RPCResult::Type::BOOL, "iswatchonly", "(DEPRECATED) Always false."},
430 {RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."},
431 {RPCResult::Type::STR, "desc", /*optional=*/true, "A descriptor for spending coins sent to this address (only when solvable)."},
432 {RPCResult::Type::STR, "parent_desc", /*optional=*/true, "The descriptor used to derive this address if this is a descriptor wallet"},
433 {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script."},
434 {RPCResult::Type::BOOL, "ischange", "If the address was used for change output."},
435 {RPCResult::Type::BOOL, "iswitness", "If the address is a witness address."},
436 {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program."},
437 {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program."},
438 {RPCResult::Type::STR, "script", /*optional=*/true, "The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
439 "types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
440 "witness_v0_scripthash, witness_unknown."},
441 {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The redeemscript for the p2sh address."},
442 {RPCResult::Type::ARR, "pubkeys", /*optional=*/true, "Array of pubkeys associated with the known redeemscript (only if script is multisig).",
443 {
444 {RPCResult::Type::STR, "pubkey", ""},
445 }},
446 {RPCResult::Type::NUM, "sigsrequired", /*optional=*/true, "The number of signatures required to spend multisig output (only if script is multisig)."},
447 {RPCResult::Type::STR_HEX, "pubkey", /*optional=*/true, "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH)."},
448 {RPCResult::Type::OBJ, "embedded", /*optional=*/true,
449 "Information about the address embedded in P2SH or P2WSH, if relevant and known.",
450 ElideGroup(
451 GetAddressInfoEmbeddedFields(/*include_nested=*/true),
452 "Includes all getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath, hdseedid)\n"
453 "and relation to the wallet (ismine)."
454 )},
455 {RPCResult::Type::BOOL, "iscompressed", /*optional=*/true, "If the pubkey is compressed."},
456 {RPCResult::Type::NUM_TIME, "timestamp", /*optional=*/true, "The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + "."},
457 {RPCResult::Type::STR, "hdkeypath", /*optional=*/true, "The HD keypath, if the key is HD and available."},
458 {RPCResult::Type::STR_HEX, "hdseedid", /*optional=*/true, "The Hash160 of the HD seed."},
459 {RPCResult::Type::STR_HEX, "hdmasterfingerprint", /*optional=*/true, "The fingerprint of the master key."},
460 {RPCResult::Type::ARR, "labels", "Array of labels associated with the address. Currently limited to one label but returned\n"
461 "as an array to keep the API stable if multiple labels are enabled in the future.",
462 {
463 {RPCResult::Type::STR, "label name", "Label name (defaults to \"\")."},
464 }},
465 }
466 },
467 RPCExamples{
468 HelpExampleCli("getaddressinfo", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
469 HelpExampleRpc("getaddressinfo", "\"" + EXAMPLE_ADDRESS[0] + "\"")
470 },
471 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
472 {
473 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
474 if (!pwallet) return UniValue::VNULL;
475
476 LOCK(pwallet->cs_wallet);
477
478 std::string error_msg;
479 CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
480
481 // Make sure the destination is valid
482 if (!IsValidDestination(dest)) {
483 // Set generic error message in case 'DecodeDestination' didn't set it
484 if (error_msg.empty()) error_msg = "Invalid address";
485
486 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error_msg);
487 }
488
489 UniValue ret(UniValue::VOBJ);
490
491 std::string currentAddress = EncodeDestination(dest);
492 ret.pushKV("address", currentAddress);
493
494 CScript scriptPubKey = GetScriptForDestination(dest);
495 ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
496
497 std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
498
499 bool mine = pwallet->IsMine(dest);
500 ret.pushKV("ismine", mine);
501
502 if (provider) {
503 auto inferred = InferDescriptor(scriptPubKey, *provider);
504 bool solvable = inferred->IsSolvable();
505 ret.pushKV("solvable", solvable);
506 if (solvable) {
507 ret.pushKV("desc", inferred->ToString());
508 }
509 } else {
510 ret.pushKV("solvable", false);
511 }
512
513 const auto& spk_mans = pwallet->GetScriptPubKeyMans(scriptPubKey);
514 // In most cases there is only one matching ScriptPubKey manager and we can't resolve ambiguity in a better way
515 ScriptPubKeyMan* spk_man{nullptr};
516 if (spk_mans.size()) spk_man = *spk_mans.begin();
517
518 DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
519 if (desc_spk_man) {
520 std::string desc_str;
521 if (desc_spk_man->GetDescriptorString(desc_str, /*priv=*/false)) {
522 ret.pushKV("parent_desc", desc_str);
523 }
524 }
525
526 ret.pushKV("iswatchonly", false);
527
528 UniValue detail = DescribeWalletAddress(*pwallet, dest);
529 ret.pushKVs(std::move(detail));
530
531 ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
532
533 if (spk_man) {
534 if (const std::unique_ptr<CKeyMetadata> meta = spk_man->GetMetadata(dest)) {
535 ret.pushKV("timestamp", meta->nCreateTime);
536 if (meta->has_key_origin) {
537 // In legacy wallets hdkeypath has always used an apostrophe for
538 // hardened derivation. Perhaps some external tool depends on that.
539 ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path, /*apostrophe=*/!desc_spk_man));
540 ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
541 ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint));
542 }
543 }
544 }
545
546 // Return a `labels` array containing the label associated with the address,
547 // equivalent to the `label` field above. Currently only one label can be
548 // associated with an address, but we return an array so the API remains
549 // stable if we allow multiple labels to be associated with an address in
550 // the future.
551 UniValue labels(UniValue::VARR);
552 const auto* address_book_entry = pwallet->FindAddressBookEntry(dest);
553 if (address_book_entry) {
554 labels.push_back(address_book_entry->GetLabel());
555 }
556 ret.pushKV("labels", std::move(labels));
557
558 return ret;
559 },
560 };
561 }
562
563 RPCMethod getaddressesbylabel()
564 {
565 return RPCMethod{
566 "getaddressesbylabel",
567 "Returns the list of addresses assigned the specified label.\n",
568 {
569 {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."},
570 },
571 RPCResult{
572 RPCResult::Type::OBJ_DYN, "", "json object with addresses as keys",
573 {
574 {RPCResult::Type::OBJ, "address", "json object with information about address",
575 {
576 {RPCResult::Type::STR, "purpose", "Purpose of address (\"send\" for sending address, \"receive\" for receiving address)"},
577 }},
578 }
579 },
580 RPCExamples{
581 HelpExampleCli("getaddressesbylabel", "\"tabby\"")
582 + HelpExampleRpc("getaddressesbylabel", "\"tabby\"")
583 },
584 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
585 {
586 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
587 if (!pwallet) return UniValue::VNULL;
588
589 LOCK(pwallet->cs_wallet);
590
591 const std::string label{LabelFromValue(request.params[0])};
592
593 // Find all addresses that have the given label
594 UniValue ret(UniValue::VOBJ);
595 std::set<std::string> addresses;
596 pwallet->ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, bool _is_change, const std::optional<AddressPurpose>& _purpose) {
597 if (_is_change) return;
598 if (_label == label) {
599 std::string address = EncodeDestination(_dest);
600 // CWallet::m_address_book is not expected to contain duplicate
601 // address strings, but build a separate set as a precaution just in
602 // case it does.
603 bool unique = addresses.emplace(address).second;
604 CHECK_NONFATAL(unique);
605 // UniValue::pushKV checks if the key exists in O(N)
606 // and since duplicate addresses are unexpected (checked with
607 // std::set in O(log(N))), UniValue::pushKVEnd is used instead,
608 // which currently is O(1).
609 UniValue value(UniValue::VOBJ);
610 value.pushKV("purpose", _purpose ? PurposeToString(*_purpose) : "unknown");
611 ret.pushKVEnd(address, std::move(value));
612 }
613 });
614
615 if (ret.empty()) {
616 throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
617 }
618
619 return ret;
620 },
621 };
622 }
623
624 RPCMethod listlabels()
625 {
626 return RPCMethod{
627 "listlabels",
628 "Returns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n",
629 {
630 {"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."},
631 },
632 RPCResult{
633 RPCResult::Type::ARR, "", "",
634 {
635 {RPCResult::Type::STR, "label", "Label name"},
636 }
637 },
638 RPCExamples{
639 "\nList all labels\n"
640 + HelpExampleCli("listlabels", "") +
641 "\nList labels that have receiving addresses\n"
642 + HelpExampleCli("listlabels", "receive") +
643 "\nList labels that have sending addresses\n"
644 + HelpExampleCli("listlabels", "send") +
645 "\nAs a JSON-RPC call\n"
646 + HelpExampleRpc("listlabels", "receive")
647 },
648 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
649 {
650 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
651 if (!pwallet) return UniValue::VNULL;
652
653 LOCK(pwallet->cs_wallet);
654
655 std::optional<AddressPurpose> purpose;
656 if (!request.params[0].isNull()) {
657 std::string purpose_str = request.params[0].get_str();
658 if (!purpose_str.empty()) {
659 purpose = PurposeFromString(purpose_str);
660 if (!purpose) {
661 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid 'purpose' argument, must be a known purpose string, typically 'send', or 'receive'.");
662 }
663 }
664 }
665
666 // Add to a set to sort by label name, then insert into Univalue array
667 std::set<std::string> label_set = pwallet->ListAddrBookLabels(purpose);
668
669 UniValue ret(UniValue::VARR);
670 for (const std::string& name : label_set) {
671 ret.push_back(name);
672 }
673
674 return ret;
675 },
676 };
677 }
678
679
680 #ifdef ENABLE_EXTERNAL_SIGNER
681 RPCMethod walletdisplayaddress()
682 {
683 return RPCMethod{
684 "walletdisplayaddress",
685 "Display address on an external signer for verification.",
686 {
687 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "bitcoin address to display"},
688 },
689 RPCResult{
690 RPCResult::Type::OBJ,"","",
691 {
692 {RPCResult::Type::STR, "address", "The address as confirmed by the signer"},
693 }
694 },
695 RPCExamples{""},
696 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
697 {
698 std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
699 if (!wallet) return UniValue::VNULL;
700 CWallet* const pwallet = wallet.get();
701
702 LOCK(pwallet->cs_wallet);
703
704 CTxDestination dest = DecodeDestination(request.params[0].get_str());
705
706 // Make sure the destination is valid
707 if (!IsValidDestination(dest)) {
708 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
709 }
710
711 util::Result<void> res = pwallet->DisplayAddress(dest);
712 if (!res) throw JSONRPCError(RPC_MISC_ERROR, util::ErrorString(res).original);
713
714 UniValue result(UniValue::VOBJ);
715 result.pushKV("address", request.params[0].get_str());
716 return result;
717 }
718 };
719 }
720 #endif // ENABLE_EXTERNAL_SIGNER
721 } // namespace wallet
722