Skip to content

Commit 460ae1b

Browse files
committed
wallet, rpc: Add createwalletdescriptor RPC
1 parent 8e1a475 commit 460ae1b

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
278278
{ "gethdkeys", 0, "active_only" },
279279
{ "gethdkeys", 0, "options" },
280280
{ "gethdkeys", 0, "private" },
281+
{ "createwalletdescriptor", 1, "options" },
282+
{ "createwalletdescriptor", 1, "internal" },
281283
// Echo with conversion (For testing only)
282284
{ "echojson", 0, "arg0" },
283285
{ "echojson", 1, "arg1" },

src/wallet/rpc/wallet.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,109 @@ RPCHelpMan gethdkeys()
925925
};
926926
}
927927

928+
static RPCHelpMan createwalletdescriptor()
929+
{
930+
return RPCHelpMan{"createwalletdescriptor",
931+
"Creates the wallet's descriptor for the given address type. "
932+
"The address type must be one that the wallet does not already have a descriptor for."
933+
+ HELP_REQUIRING_PASSPHRASE,
934+
{
935+
{"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
936+
{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
937+
{"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)"},
938+
{"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"},
939+
}},
940+
},
941+
RPCResult{
942+
RPCResult::Type::OBJ, "", "",
943+
{
944+
{RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
945+
{{RPCResult::Type::STR, "", ""}}
946+
}
947+
},
948+
},
949+
RPCExamples{
950+
HelpExampleCli("createwalletdescriptor", "bech32m")
951+
+ HelpExampleRpc("createwalletdescriptor", "bech32m")
952+
},
953+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
954+
{
955+
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
956+
if (!pwallet) return UniValue::VNULL;
957+
958+
// Make sure wallet is a descriptor wallet
959+
if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
960+
throw JSONRPCError(RPC_WALLET_ERROR, "createwalletdescriptor is not available for non-descriptor wallets");
961+
}
962+
963+
std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());
964+
if (!output_type) {
965+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
966+
}
967+
968+
UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
969+
UniValue internal_only{options["internal"]};
970+
UniValue hdkey{options["hdkey"]};
971+
972+
std::vector<bool> internals;
973+
if (internal_only.isNull()) {
974+
internals.push_back(false);
975+
internals.push_back(true);
976+
} else {
977+
internals.push_back(internal_only.get_bool());
978+
}
979+
980+
LOCK(pwallet->cs_wallet);
981+
EnsureWalletIsUnlocked(*pwallet);
982+
983+
CExtPubKey xpub;
984+
if (hdkey.isNull()) {
985+
std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys();
986+
if (active_xpubs.size() != 1) {
987+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
988+
}
989+
xpub = *active_xpubs.begin();
990+
} else {
991+
xpub = DecodeExtPubKey(hdkey.get_str());
992+
if (!xpub.pubkey.IsValid()) {
993+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
994+
}
995+
}
996+
997+
std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
998+
if (!key) {
999+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
1000+
}
1001+
CExtKey active_hdkey(xpub, *key);
1002+
1003+
std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
1004+
WalletBatch batch{pwallet->GetDatabase()};
1005+
for (bool internal : internals) {
1006+
WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal);
1007+
uint256 w_id = DescriptorID(*w_desc.descriptor);
1008+
if (!pwallet->GetScriptPubKeyMan(w_id)) {
1009+
spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal));
1010+
}
1011+
}
1012+
if (spkms.empty()) {
1013+
throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
1014+
}
1015+
1016+
// Fetch each descspkm from the wallet in order to get the descriptor strings
1017+
UniValue descs{UniValue::VARR};
1018+
for (const auto& spkm : spkms) {
1019+
std::string desc_str;
1020+
bool ok = spkm.get().GetDescriptorString(desc_str, false);
1021+
CHECK_NONFATAL(ok);
1022+
descs.push_back(desc_str);
1023+
}
1024+
UniValue out{UniValue::VOBJ};
1025+
out.pushKV("descs", std::move(descs));
1026+
return out;
1027+
}
1028+
};
1029+
}
1030+
9281031
// addresses
9291032
RPCHelpMan getaddressinfo();
9301033
RPCHelpMan getnewaddress();
@@ -1008,6 +1111,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
10081111
{"wallet", &bumpfee},
10091112
{"wallet", &psbtbumpfee},
10101113
{"wallet", &createwallet},
1114+
{"wallet", &createwalletdescriptor},
10111115
{"wallet", &restorewallet},
10121116
{"wallet", &dumpprivkey},
10131117
{"wallet", &dumpwallet},

0 commit comments

Comments
 (0)