diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 0f7757667..d97bcf54b 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,1210 +1,1210 @@
 // Copyright (c) 2009-2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <rpc/server.h>
 
 #include <addrman.h>
 #include <avalanche/avalanche.h>
 #include <banman.h>
 #include <chainparams.h>
 #include <clientversion.h>
 #include <config.h>
 #include <net_permissions.h>
 #include <net_processing.h>
 #include <net_types.h> // For banmap_t
 #include <netbase.h>
 #include <node/context.h>
 #include <policy/settings.h>
 #include <rpc/blockchain.h>
 #include <rpc/protocol.h>
 #include <rpc/server_util.h>
 #include <rpc/util.h>
 #include <sync.h>
 #include <timedata.h>
 #include <util/strencodings.h>
 #include <util/string.h>
 #include <util/translation.h>
 #include <validation.h>
 #include <version.h>
 #include <warnings.h>
 
 #include <optional>
 
 #include <univalue.h>
 
 using node::NodeContext;
 
 static RPCHelpMan getconnectioncount() {
     return RPCHelpMan{
         "getconnectioncount",
         "Returns the number of connections to other nodes.\n",
         {},
         RPCResult{RPCResult::Type::NUM, "", "The connection count"},
         RPCExamples{HelpExampleCli("getconnectioncount", "") +
                     HelpExampleRpc("getconnectioncount", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             const CConnman &connman = EnsureConnman(node);
 
-            return int(connman.GetNodeCount(CConnman::CONNECTIONS_ALL));
+            return connman.GetNodeCount(CConnman::CONNECTIONS_ALL);
         },
     };
 }
 
 static RPCHelpMan ping() {
     return RPCHelpMan{
         "ping",
         "Requests that a ping be sent to all other nodes, to measure ping "
         "time.\n"
         "Results provided in getpeerinfo, pingtime and pingwait fields are "
         "decimal seconds.\n"
         "Ping command is handled in queue with all other commands, so it "
         "measures processing backlog, not just network ping.\n",
         {},
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("ping", "") + HelpExampleRpc("ping", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             PeerManager &peerman = EnsurePeerman(node);
 
             // Request that each node send a ping during next message processing
             // pass
             peerman.SendPings();
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan getpeerinfo() {
     return RPCHelpMan{
         "getpeerinfo",
         "Returns data about each connected network node as a json array of "
         "objects.\n",
         {},
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {{
                 RPCResult::Type::OBJ,
                 "",
                 "",
                 {{
                     {RPCResult::Type::NUM, "id", "Peer index"},
                     {RPCResult::Type::STR, "addr",
                      "(host:port) The IP address and port of the peer"},
                     {RPCResult::Type::STR, "addrbind",
                      "(ip:port) Bind address of the connection to the peer"},
                     {RPCResult::Type::STR, "addrlocal",
                      "(ip:port) Local address as reported by the peer"},
                     {RPCResult::Type::NUM, "addr_processed",
                      "The total number of addresses processed, excluding those "
                      "dropped due to rate limiting"},
                     {RPCResult::Type::NUM, "addr_rate_limited",
                      "The total number of addresses dropped due to rate "
                      "limiting"},
                     {RPCResult::Type::STR, "network",
                      "Network (" +
                          Join(GetNetworkNames(/* append_unroutable */ true),
                               ", ") +
                          ")"},
                     {RPCResult::Type::NUM, "mapped_as",
                      "The AS in the BGP route to the peer used for "
                      "diversifying\n"
                      "peer selection (only available if the asmap config flag "
                      "is set)\n"},
                     {RPCResult::Type::STR_HEX, "services",
                      "The services offered"},
                     {RPCResult::Type::ARR,
                      "servicesnames",
                      "the services offered, in human-readable form",
                      {{RPCResult::Type::STR, "SERVICE_NAME",
                        "the service name if it is recognised"}}},
                     {RPCResult::Type::BOOL, "relaytxes",
                      "Whether peer has asked us to relay transactions to it"},
                     {RPCResult::Type::NUM_TIME, "lastsend",
                      "The " + UNIX_EPOCH_TIME + " of the last send"},
                     {RPCResult::Type::NUM_TIME, "lastrecv",
                      "The " + UNIX_EPOCH_TIME + " of the last receive"},
                     {RPCResult::Type::NUM_TIME, "last_transaction",
                      "The " + UNIX_EPOCH_TIME +
                          " of the last valid transaction received from this "
                          "peer"},
                     {RPCResult::Type::NUM_TIME, "last_block",
                      "The " + UNIX_EPOCH_TIME +
                          " of the last block received from this peer"},
                     {RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
                     {RPCResult::Type::NUM, "bytesrecv",
                      "The total bytes received"},
                     {RPCResult::Type::NUM_TIME, "conntime",
                      "The " + UNIX_EPOCH_TIME + " of the connection"},
                     {RPCResult::Type::NUM, "timeoffset",
                      "The time offset in seconds"},
                     {RPCResult::Type::NUM, "pingtime",
                      "ping time (if available)"},
                     {RPCResult::Type::NUM, "minping",
                      "minimum observed ping time (if any at all)"},
                     {RPCResult::Type::NUM, "pingwait",
                      "ping wait (if non-zero)"},
                     {RPCResult::Type::NUM, "version",
                      "The peer version, such as 70001"},
                     {RPCResult::Type::STR, "subver", "The string version"},
                     {RPCResult::Type::BOOL, "inbound",
                      "Inbound (true) or Outbound (false)"},
                     {RPCResult::Type::BOOL, "bip152_hb_to",
                      "Whether we selected peer as (compact blocks) "
                      "high-bandwidth peer"},
                     {RPCResult::Type::BOOL, "bip152_hb_from",
                      "Whether peer selected us as (compact blocks) "
                      "high-bandwidth peer"},
                     {RPCResult::Type::STR, "connection_type",
                      "Type of connection: \n" +
                          Join(CONNECTION_TYPE_DOC, ",\n") + "."},
                     {RPCResult::Type::NUM, "startingheight",
                      "The starting height (block) of the peer"},
                     {RPCResult::Type::NUM, "presynced_headers",
                      /*optional=*/true,
                      "The current height of header pre-synchronization with "
                      "this peer, or -1 if no low-work sync is in progress"},
                     {RPCResult::Type::NUM, "synced_headers",
                      "The last header we have in common with this peer"},
                     {RPCResult::Type::NUM, "synced_blocks",
                      "The last block we have in common with this peer"},
                     {RPCResult::Type::ARR,
                      "inflight",
                      "",
                      {
                          {RPCResult::Type::NUM, "n",
                           "The heights of blocks we're currently asking from "
                           "this peer"},
                      }},
                     {RPCResult::Type::BOOL, "addr_relay_enabled",
                      "Whether we participate in address relay with this peer"},
                     {RPCResult::Type::NUM, "minfeefilter",
                      "The minimum fee rate for transactions this peer accepts"},
                     {RPCResult::Type::OBJ_DYN,
                      "bytessent_per_msg",
                      "",
                      {{RPCResult::Type::NUM, "msg",
                        "The total bytes sent aggregated by message type\n"
                        "When a message type is not listed in this json object, "
                        "the bytes sent are 0.\n"
                        "Only known message types can appear as keys in the "
                        "object."}}},
                     {RPCResult::Type::OBJ,
                      "bytesrecv_per_msg",
                      "",
                      {{RPCResult::Type::NUM, "msg",
                        "The total bytes received aggregated by message type\n"
                        "When a message type is not listed in this json object, "
                        "the bytes received are 0.\n"
                        "Only known message types can appear as keys in the "
                        "object and all bytes received\n"
                        "of unknown message types are listed under '" +
                            NET_MESSAGE_COMMAND_OTHER + "'."}}},
                     {RPCResult::Type::NUM, "availability_score",
                      "Avalanche availability score of this node (if any)"},
                 }},
             }},
         },
         RPCExamples{HelpExampleCli("getpeerinfo", "") +
                     HelpExampleRpc("getpeerinfo", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             const CConnman &connman = EnsureConnman(node);
             const PeerManager &peerman = EnsurePeerman(node);
 
             std::vector<CNodeStats> vstats;
             connman.GetNodeStats(vstats);
 
             UniValue ret(UniValue::VARR);
 
             for (const CNodeStats &stats : vstats) {
                 UniValue obj(UniValue::VOBJ);
                 CNodeStateStats statestats;
                 bool fStateStats =
                     peerman.GetNodeStateStats(stats.nodeid, statestats);
                 obj.pushKV("id", stats.nodeid);
                 obj.pushKV("addr", stats.m_addr_name);
                 if (stats.addrBind.IsValid()) {
                     obj.pushKV("addrbind", stats.addrBind.ToString());
                 }
                 if (!(stats.addrLocal.empty())) {
                     obj.pushKV("addrlocal", stats.addrLocal);
                 }
                 obj.pushKV("network", GetNetworkName(stats.m_network));
                 if (stats.m_mapped_as != 0) {
                     obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
                 }
                 ServiceFlags services{fStateStats ? statestats.their_services
                                                   : ServiceFlags::NODE_NONE};
                 obj.pushKV("services", strprintf("%016x", services));
                 obj.pushKV("servicesnames", GetServicesNames(services));
                 obj.pushKV("lastsend", count_seconds(stats.m_last_send));
                 obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
                 obj.pushKV("last_transaction",
                            count_seconds(stats.m_last_tx_time));
                 if (g_avalanche) {
                     obj.pushKV("last_proof",
                                count_seconds(stats.m_last_proof_time));
                 }
                 obj.pushKV("last_block",
                            count_seconds(stats.m_last_block_time));
                 obj.pushKV("bytessent", stats.nSendBytes);
                 obj.pushKV("bytesrecv", stats.nRecvBytes);
                 obj.pushKV("conntime", count_seconds(stats.m_connected));
                 obj.pushKV("timeoffset", stats.nTimeOffset);
                 if (stats.m_last_ping_time > 0us) {
                     obj.pushKV("pingtime",
                                CountSecondsDouble(stats.m_last_ping_time));
                 }
                 if (stats.m_min_ping_time < std::chrono::microseconds::max()) {
                     obj.pushKV("minping",
                                CountSecondsDouble(stats.m_min_ping_time));
                 }
                 if (fStateStats && statestats.m_ping_wait > 0s) {
                     obj.pushKV("pingwait",
                                CountSecondsDouble(statestats.m_ping_wait));
                 }
                 obj.pushKV("version", stats.nVersion);
                 // Use the sanitized form of subver here, to avoid tricksy
                 // remote peers from corrupting or modifying the JSON output by
                 // putting special characters in their ver message.
                 obj.pushKV("subver", stats.cleanSubVer);
                 obj.pushKV("inbound", stats.fInbound);
                 obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to);
                 obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from);
                 if (fStateStats) {
                     obj.pushKV("startingheight", statestats.m_starting_height);
                     obj.pushKV("presynced_headers", statestats.presync_height);
                     obj.pushKV("synced_headers", statestats.nSyncHeight);
                     obj.pushKV("synced_blocks", statestats.nCommonHeight);
                     UniValue heights(UniValue::VARR);
                     for (const int height : statestats.vHeightInFlight) {
                         heights.push_back(height);
                     }
                     obj.pushKV("inflight", heights);
                     obj.pushKV("relaytxes", statestats.m_relay_txs);
                     obj.pushKV("minfeefilter",
                                statestats.m_fee_filter_received);
                     obj.pushKV("addr_relay_enabled",
                                statestats.m_addr_relay_enabled);
                     obj.pushKV("addr_processed", statestats.m_addr_processed);
                     obj.pushKV("addr_rate_limited",
                                statestats.m_addr_rate_limited);
                 }
                 UniValue permissions(UniValue::VARR);
                 for (const auto &permission :
                      NetPermissions::ToStrings(stats.m_permissionFlags)) {
                     permissions.push_back(permission);
                 }
                 obj.pushKV("permissions", permissions);
 
                 UniValue sendPerMsgCmd(UniValue::VOBJ);
                 for (const auto &i : stats.mapSendBytesPerMsgCmd) {
                     if (i.second > 0) {
                         sendPerMsgCmd.pushKV(i.first, i.second);
                     }
                 }
                 obj.pushKV("bytessent_per_msg", sendPerMsgCmd);
 
                 UniValue recvPerMsgCmd(UniValue::VOBJ);
                 for (const auto &i : stats.mapRecvBytesPerMsgCmd) {
                     if (i.second > 0) {
                         recvPerMsgCmd.pushKV(i.first, i.second);
                     }
                 }
                 obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
                 obj.pushKV("connection_type",
                            ConnectionTypeAsString(stats.m_conn_type));
 
                 if (stats.m_availabilityScore) {
                     obj.pushKV("availability_score",
                                *stats.m_availabilityScore);
                 }
 
                 ret.push_back(obj);
             }
 
             return ret;
         },
     };
 }
 
 static RPCHelpMan addnode() {
     return RPCHelpMan{
         "addnode",
         "Attempts to add or remove a node from the addnode list.\n"
         "Or try a connection to a node once.\n"
         "Nodes added using addnode (or -connect) are protected from "
         "DoS disconnection and are not required to be\n"
         "full nodes as other outbound peers are (though such peers "
         "will not be synced from).\n",
         {
             {"node", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The node (see getpeerinfo for nodes)"},
             {"command", RPCArg::Type::STR, RPCArg::Optional::NO,
              "'add' to add a node to the list, 'remove' to remove a "
              "node from the list, 'onetry' to try a connection to the "
              "node once"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{
             HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") +
             HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::string strCommand;
             if (!request.params[1].isNull()) {
                 strCommand = request.params[1].get_str();
             }
 
             if (strCommand != "onetry" && strCommand != "add" &&
                 strCommand != "remove") {
                 throw std::runtime_error(self.ToString());
             }
 
             NodeContext &node = EnsureAnyNodeContext(request.context);
             CConnman &connman = EnsureConnman(node);
 
             std::string strNode = request.params[0].get_str();
 
             if (strCommand == "onetry") {
                 CAddress addr;
                 connman.OpenNetworkConnection(addr, false, nullptr,
                                               strNode.c_str(),
                                               ConnectionType::MANUAL);
                 return NullUniValue;
             }
 
             if ((strCommand == "add") && (!connman.AddNode(strNode))) {
                 throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
                                    "Error: Node already added");
             } else if ((strCommand == "remove") &&
                        (!connman.RemoveAddedNode(strNode))) {
                 throw JSONRPCError(
                     RPC_CLIENT_NODE_NOT_ADDED,
                     "Error: Node could not be removed. It has not been "
                     "added previously.");
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan addconnection() {
     return RPCHelpMan{
         "addconnection",
         "\nOpen an outbound connection to a specified node. This RPC is for "
         "testing only.\n",
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The IP address and port to attempt connecting to."},
             {"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO,
              "Type of connection to open (\"outbound-full-relay\", "
              "\"block-relay-only\", \"addr-fetch\", \"feeler\" or "
              "\"avalanche\")."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "address",
                        "Address of newly added connection."},
                       {RPCResult::Type::STR, "connection_type",
                        "Type of connection opened."},
                   }},
         RPCExamples{
             HelpExampleCli("addconnection",
                            "\"192.168.0.6:8333\" \"outbound-full-relay\"") +
             HelpExampleRpc("addconnection",
                            "\"192.168.0.6:8333\" \"outbound-full-relay\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             if (config.GetChainParams().NetworkIDString() !=
                 CBaseChainParams::REGTEST) {
                 throw std::runtime_error("addconnection is for regression "
                                          "testing (-regtest mode) only.");
             }
 
             NodeContext &node = EnsureAnyNodeContext(request.context);
             const ArgsManager &args{EnsureArgsman(node)};
 
             const std::string address = request.params[0].get_str();
             const std::string conn_type_in{
                 TrimString(request.params[1].get_str())};
             ConnectionType conn_type{};
             if (conn_type_in == "outbound-full-relay") {
                 conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
             } else if (conn_type_in == "block-relay-only") {
                 conn_type = ConnectionType::BLOCK_RELAY;
             } else if (conn_type_in == "addr-fetch") {
                 conn_type = ConnectionType::ADDR_FETCH;
             } else if (conn_type_in == "feeler") {
                 conn_type = ConnectionType::FEELER;
             } else if (conn_type_in == "avalanche") {
                 if (!g_avalanche || !isAvalancheEnabled(args)) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Error: avalanche outbound requested "
                                        "but avalanche is not enabled.");
                 }
                 conn_type = ConnectionType::AVALANCHE_OUTBOUND;
             } else {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
             }
 
             CConnman &connman = EnsureConnman(node);
 
             const bool success = connman.AddConnection(address, conn_type);
             if (!success) {
                 throw JSONRPCError(RPC_CLIENT_NODE_CAPACITY_REACHED,
                                    "Error: Already at capacity for specified "
                                    "connection type.");
             }
 
             UniValue info(UniValue::VOBJ);
             info.pushKV("address", address);
             info.pushKV("connection_type", conn_type_in);
 
             return info;
         },
     };
 }
 
 static RPCHelpMan disconnectnode() {
     return RPCHelpMan{
         "disconnectnode",
         "Immediately disconnects from the specified peer node.\n"
         "\nStrictly one out of 'address' and 'nodeid' can be provided to "
         "identify the node.\n"
         "\nTo disconnect by nodeid, either set 'address' to the empty string, "
         "or call using the named 'nodeid' argument only.\n",
         {
             {"address", RPCArg::Type::STR,
              RPCArg::DefaultHint{"fallback to nodeid"},
              "The IP address/port of the node"},
             {"nodeid", RPCArg::Type::NUM,
              RPCArg::DefaultHint{"fallback to address"},
              "The node ID (see getpeerinfo for node IDs)"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
                     HelpExampleCli("disconnectnode", "\"\" 1") +
                     HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
                     HelpExampleRpc("disconnectnode", "\"\", 1")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             CConnman &connman = EnsureConnman(node);
 
             bool success;
             const UniValue &address_arg = request.params[0];
             const UniValue &id_arg = request.params[1];
 
             if (!address_arg.isNull() && id_arg.isNull()) {
                 /* handle disconnect-by-address */
                 success = connman.DisconnectNode(address_arg.get_str());
             } else if (!id_arg.isNull() && (address_arg.isNull() ||
                                             (address_arg.isStr() &&
                                              address_arg.get_str().empty()))) {
                 /* handle disconnect-by-id */
                 NodeId nodeid = (NodeId)id_arg.getInt<int64_t>();
                 success = connman.DisconnectNode(nodeid);
             } else {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMS,
                     "Only one of address and nodeid should be provided.");
             }
 
             if (!success) {
                 throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED,
                                    "Node not found in connected nodes");
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan getaddednodeinfo() {
     return RPCHelpMan{
         "getaddednodeinfo",
         "Returns information about the given added node, or all added nodes\n"
         "(note that onetry addnodes are not listed here)\n",
         {
             {"node", RPCArg::Type::STR, RPCArg::DefaultHint{"all nodes"},
              "If provided, return information about this specific node, "
              "otherwise all nodes are returned."},
         },
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {
                 {RPCResult::Type::OBJ,
                  "",
                  "",
                  {
                      {RPCResult::Type::STR, "addednode",
                       "The node IP address or name (as provided to addnode)"},
                      {RPCResult::Type::BOOL, "connected", "If connected"},
                      {RPCResult::Type::ARR,
                       "addresses",
                       "Only when connected = true",
                       {
                           {RPCResult::Type::OBJ,
                            "",
                            "",
                            {
                                {RPCResult::Type::STR, "address",
                                 "The bitcoin server IP and port we're "
                                 "connected to"},
                                {RPCResult::Type::STR, "connected",
                                 "connection, inbound or outbound"},
                            }},
                       }},
                  }},
             }},
         RPCExamples{HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") +
                     HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             const CConnman &connman = EnsureConnman(node);
 
             std::vector<AddedNodeInfo> vInfo = connman.GetAddedNodeInfo();
 
             if (!request.params[0].isNull()) {
                 bool found = false;
                 for (const AddedNodeInfo &info : vInfo) {
                     if (info.strAddedNode == request.params[0].get_str()) {
                         vInfo.assign(1, info);
                         found = true;
                         break;
                     }
                 }
                 if (!found) {
                     throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
                                        "Error: Node has not been added.");
                 }
             }
 
             UniValue ret(UniValue::VARR);
 
             for (const AddedNodeInfo &info : vInfo) {
                 UniValue obj(UniValue::VOBJ);
                 obj.pushKV("addednode", info.strAddedNode);
                 obj.pushKV("connected", info.fConnected);
                 UniValue addresses(UniValue::VARR);
                 if (info.fConnected) {
                     UniValue address(UniValue::VOBJ);
                     address.pushKV("address", info.resolvedAddress.ToString());
                     address.pushKV("connected",
                                    info.fInbound ? "inbound" : "outbound");
                     addresses.push_back(address);
                 }
                 obj.pushKV("addresses", addresses);
                 ret.push_back(obj);
             }
 
             return ret;
         },
     };
 }
 
 static RPCHelpMan getnettotals() {
     return RPCHelpMan{
         "getnettotals",
         "Returns information about network traffic, including bytes in, "
         "bytes out,\n"
         "and current time.\n",
         {},
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::NUM, "totalbytesrecv",
                  "Total bytes received"},
                 {RPCResult::Type::NUM, "totalbytessent", "Total bytes sent"},
                 {RPCResult::Type::NUM_TIME, "timemillis",
                  "Current " + UNIX_EPOCH_TIME + " in milliseconds"},
                 {RPCResult::Type::OBJ,
                  "uploadtarget",
                  "",
                  {
                      {RPCResult::Type::NUM, "timeframe",
                       "Length of the measuring timeframe in seconds"},
                      {RPCResult::Type::NUM, "target", "Target in bytes"},
                      {RPCResult::Type::BOOL, "target_reached",
                       "True if target is reached"},
                      {RPCResult::Type::BOOL, "serve_historical_blocks",
                       "True if serving historical blocks"},
                      {RPCResult::Type::NUM, "bytes_left_in_cycle",
                       "Bytes left in current time cycle"},
                      {RPCResult::Type::NUM, "time_left_in_cycle",
                       "Seconds left in current time cycle"},
                  }},
             }},
         RPCExamples{HelpExampleCli("getnettotals", "") +
                     HelpExampleRpc("getnettotals", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             const CConnman &connman = EnsureConnman(node);
 
             UniValue obj(UniValue::VOBJ);
             obj.pushKV("totalbytesrecv", connman.GetTotalBytesRecv());
             obj.pushKV("totalbytessent", connman.GetTotalBytesSent());
             obj.pushKV("timemillis", GetTimeMillis());
 
             UniValue outboundLimit(UniValue::VOBJ);
             outboundLimit.pushKV(
                 "timeframe", count_seconds(connman.GetMaxOutboundTimeframe()));
             outboundLimit.pushKV("target", connman.GetMaxOutboundTarget());
             outboundLimit.pushKV("target_reached",
                                  connman.OutboundTargetReached(false));
             outboundLimit.pushKV("serve_historical_blocks",
                                  !connman.OutboundTargetReached(true));
             outboundLimit.pushKV("bytes_left_in_cycle",
                                  connman.GetOutboundTargetBytesLeft());
             outboundLimit.pushKV(
                 "time_left_in_cycle",
                 count_seconds(connman.GetMaxOutboundTimeLeftInCycle()));
             obj.pushKV("uploadtarget", outboundLimit);
             return obj;
         },
     };
 }
 
 static UniValue GetNetworksInfo() {
     UniValue networks(UniValue::VARR);
     for (int n = 0; n < NET_MAX; ++n) {
         enum Network network = static_cast<enum Network>(n);
         if (network == NET_UNROUTABLE || network == NET_CJDNS ||
             network == NET_INTERNAL) {
             continue;
         }
         proxyType proxy;
         UniValue obj(UniValue::VOBJ);
         GetProxy(network, proxy);
         obj.pushKV("name", GetNetworkName(network));
         obj.pushKV("limited", !IsReachable(network));
         obj.pushKV("reachable", IsReachable(network));
         obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort()
                                             : std::string());
         obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials);
         networks.push_back(obj);
     }
     return networks;
 }
 
 static RPCHelpMan getnetworkinfo() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "getnetworkinfo",
         "Returns an object containing various state info regarding P2P "
         "networking.\n",
         {},
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::NUM, "version", "the server version"},
                 {RPCResult::Type::STR, "subversion",
                  "the server subversion string"},
                 {RPCResult::Type::NUM, "protocolversion",
                  "the protocol version"},
                 {RPCResult::Type::STR_HEX, "localservices",
                  "the services we offer to the network"},
                 {RPCResult::Type::ARR,
                  "localservicesnames",
                  "the services we offer to the network, in human-readable form",
                  {
                      {RPCResult::Type::STR, "SERVICE_NAME", "the service name"},
                  }},
                 {RPCResult::Type::BOOL, "localrelay",
                  "true if transaction relay is requested from peers"},
                 {RPCResult::Type::NUM, "timeoffset", "the time offset"},
                 {RPCResult::Type::NUM, "connections",
                  "the total number of connections"},
                 {RPCResult::Type::NUM, "connections_in",
                  "the number of inbound connections"},
                 {RPCResult::Type::NUM, "connections_out",
                  "the number of outbound connections"},
                 {RPCResult::Type::BOOL, "networkactive",
                  "whether p2p networking is enabled"},
                 {RPCResult::Type::ARR,
                  "networks",
                  "information per network",
                  {
                      {RPCResult::Type::OBJ,
                       "",
                       "",
                       {
                           {RPCResult::Type::STR, "name",
                            "network (" + Join(GetNetworkNames(), ", ") + ")"},
                           {RPCResult::Type::BOOL, "limited",
                            "is the network limited using -onlynet?"},
                           {RPCResult::Type::BOOL, "reachable",
                            "is the network reachable?"},
                           {RPCResult::Type::STR, "proxy",
                            "(\"host:port\") the proxy that is used for this "
                            "network, or empty if none"},
                           {RPCResult::Type::BOOL, "proxy_randomize_credentials",
                            "Whether randomized credentials are used"},
                       }},
                  }},
                 {RPCResult::Type::NUM, "relayfee",
                  "minimum relay fee for transactions in " + ticker + "/kB"},
                 {RPCResult::Type::ARR,
                  "localaddresses",
                  "list of local addresses",
                  {
                      {RPCResult::Type::OBJ,
                       "",
                       "",
                       {
                           {RPCResult::Type::STR, "address", "network address"},
                           {RPCResult::Type::NUM, "port", "network port"},
                           {RPCResult::Type::NUM, "score", "relative score"},
                       }},
                  }},
                 {RPCResult::Type::STR, "warnings",
                  "any network and blockchain warnings"},
             }},
         RPCExamples{HelpExampleCli("getnetworkinfo", "") +
                     HelpExampleRpc("getnetworkinfo", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             LOCK(cs_main);
             UniValue obj(UniValue::VOBJ);
             obj.pushKV("version", CLIENT_VERSION);
             obj.pushKV("subversion", userAgent(config));
             obj.pushKV("protocolversion", PROTOCOL_VERSION);
             NodeContext &node = EnsureAnyNodeContext(request.context);
             if (node.connman) {
                 ServiceFlags services = node.connman->GetLocalServices();
                 obj.pushKV("localservices", strprintf("%016x", services));
                 obj.pushKV("localservicesnames", GetServicesNames(services));
             }
             if (node.peerman) {
                 obj.pushKV("localrelay", !node.peerman->IgnoresIncomingTxs());
             }
             obj.pushKV("timeoffset", GetTimeOffset());
             if (node.connman) {
                 obj.pushKV("networkactive", node.connman->GetNetworkActive());
-                obj.pushKV("connections", int(node.connman->GetNodeCount(
-                                              CConnman::CONNECTIONS_ALL)));
-                obj.pushKV("connections_in", int(node.connman->GetNodeCount(
-                                                 CConnman::CONNECTIONS_IN)));
-                obj.pushKV("connections_out", int(node.connman->GetNodeCount(
-                                                  CConnman::CONNECTIONS_OUT)));
+                obj.pushKV("connections", node.connman->GetNodeCount(
+                                              CConnman::CONNECTIONS_ALL));
+                obj.pushKV("connections_in", node.connman->GetNodeCount(
+                                                 CConnman::CONNECTIONS_IN));
+                obj.pushKV("connections_out", node.connman->GetNodeCount(
+                                                  CConnman::CONNECTIONS_OUT));
             }
             obj.pushKV("networks", GetNetworksInfo());
             if (node.mempool) {
                 // This field can be deprecated, to be replaced by the
                 // getmempoolinfo fields
                 obj.pushKV("relayfee",
                            node.mempool->m_min_relay_feerate.GetFeePerK());
             }
             UniValue localAddresses(UniValue::VARR);
             {
                 LOCK(g_maplocalhost_mutex);
                 for (const std::pair<const CNetAddr, LocalServiceInfo> &item :
                      mapLocalHost) {
                     UniValue rec(UniValue::VOBJ);
                     rec.pushKV("address", item.first.ToString());
                     rec.pushKV("port", item.second.nPort);
                     rec.pushKV("score", item.second.nScore);
                     localAddresses.push_back(rec);
                 }
             }
             obj.pushKV("localaddresses", localAddresses);
             obj.pushKV("warnings", GetWarnings(false).original);
             return obj;
         },
     };
 }
 
 static RPCHelpMan setban() {
     return RPCHelpMan{
         "setban",
         "Attempts to add or remove an IP/Subnet from the banned list.\n",
         {
             {"subnet", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The IP/Subnet (see getpeerinfo for nodes IP) with an optional "
              "netmask (default is /32 = single IP)"},
             {"command", RPCArg::Type::STR, RPCArg::Optional::NO,
              "'add' to add an IP/Subnet to the list, 'remove' to remove an "
              "IP/Subnet from the list"},
             {"bantime", RPCArg::Type::NUM, RPCArg::Default{0},
              "time in seconds how long (or until when if [absolute] is set) "
              "the IP is banned (0 or empty means using the default time of 24h "
              "which can also be overwritten by the -bantime startup argument)"},
             {"absolute", RPCArg::Type::BOOL, RPCArg::Default{false},
              "If set, the bantime must be an absolute timestamp expressed in " +
                  UNIX_EPOCH_TIME},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{
             HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") +
             HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") +
             HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400")},
         [&](const RPCHelpMan &help, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::string strCommand;
             if (!request.params[1].isNull()) {
                 strCommand = request.params[1].get_str();
             }
 
             if (strCommand != "add" && strCommand != "remove") {
                 throw std::runtime_error(help.ToString());
             }
 
             NodeContext &node = EnsureAnyNodeContext(request.context);
             if (!node.banman) {
                 throw JSONRPCError(RPC_DATABASE_ERROR,
                                    "Error: Ban database not loaded");
             }
 
             CSubNet subNet;
             CNetAddr netAddr;
             bool isSubnet = false;
 
             if (request.params[0].get_str().find('/') != std::string::npos) {
                 isSubnet = true;
             }
 
             if (!isSubnet) {
                 CNetAddr resolved;
                 LookupHost(request.params[0].get_str(), resolved, false);
                 netAddr = resolved;
             } else {
                 LookupSubNet(request.params[0].get_str(), subNet);
             }
 
             if (!(isSubnet ? subNet.IsValid() : netAddr.IsValid())) {
                 throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
                                    "Error: Invalid IP/Subnet");
             }
 
             if (strCommand == "add") {
                 if (isSubnet ? node.banman->IsBanned(subNet)
                              : node.banman->IsBanned(netAddr)) {
                     throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
                                        "Error: IP/Subnet already banned");
                 }
 
                 // Use standard bantime if not specified.
                 int64_t banTime = 0;
                 if (!request.params[2].isNull()) {
                     banTime = request.params[2].getInt<int64_t>();
                 }
 
                 bool absolute = false;
                 if (request.params[3].isTrue()) {
                     absolute = true;
                 }
 
                 if (isSubnet) {
                     node.banman->Ban(subNet, banTime, absolute);
                     if (node.connman) {
                         node.connman->DisconnectNode(subNet);
                     }
                 } else {
                     node.banman->Ban(netAddr, banTime, absolute);
                     if (node.connman) {
                         node.connman->DisconnectNode(netAddr);
                     }
                 }
             } else if (strCommand == "remove") {
                 if (!(isSubnet ? node.banman->Unban(subNet)
                                : node.banman->Unban(netAddr))) {
                     throw JSONRPCError(
                         RPC_CLIENT_INVALID_IP_OR_SUBNET,
                         "Error: Unban failed. Requested address/subnet "
                         "was not previously manually banned.");
                 }
             }
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan listbanned() {
     return RPCHelpMan{
         "listbanned",
         "List all manually banned IPs/Subnets.\n",
         {},
         RPCResult{RPCResult::Type::ARR,
                   "",
                   "",
                   {
                       {RPCResult::Type::OBJ,
                        "",
                        "",
                        {
                            {RPCResult::Type::STR, "address", ""},
                            {RPCResult::Type::NUM_TIME, "banned_until", ""},
                            {RPCResult::Type::NUM_TIME, "ban_created", ""},
                            {RPCResult::Type::STR, "ban_reason", ""},
                        }},
                   }},
         RPCExamples{HelpExampleCli("listbanned", "") +
                     HelpExampleRpc("listbanned", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             if (!node.banman) {
                 throw JSONRPCError(RPC_DATABASE_ERROR,
                                    "Error: Ban database not loaded");
             }
 
             banmap_t banMap;
             node.banman->GetBanned(banMap);
 
             UniValue bannedAddresses(UniValue::VARR);
             for (const auto &entry : banMap) {
                 const CBanEntry &banEntry = entry.second;
                 UniValue rec(UniValue::VOBJ);
                 rec.pushKV("address", entry.first.ToString());
                 rec.pushKV("banned_until", banEntry.nBanUntil);
                 rec.pushKV("ban_created", banEntry.nCreateTime);
 
                 bannedAddresses.push_back(rec);
             }
 
             return bannedAddresses;
         },
     };
 }
 
 static RPCHelpMan clearbanned() {
     return RPCHelpMan{
         "clearbanned",
         "Clear all banned IPs.\n",
         {},
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("clearbanned", "") +
                     HelpExampleRpc("clearbanned", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             if (!node.banman) {
                 throw JSONRPCError(
                     RPC_CLIENT_P2P_DISABLED,
                     "Error: Peer-to-peer functionality missing or disabled");
             }
 
             node.banman->ClearBanned();
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan setnetworkactive() {
     return RPCHelpMan{
         "setnetworkactive",
         "Disable/enable all p2p network activity.\n",
         {
             {"state", RPCArg::Type::BOOL, RPCArg::Optional::NO,
              "true to enable networking, false to disable"},
         },
         RPCResult{RPCResult::Type::BOOL, "", "The value that was passed in"},
         RPCExamples{""},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             CConnman &connman = EnsureConnman(node);
 
             connman.SetNetworkActive(request.params[0].get_bool());
 
             return connman.GetNetworkActive();
         },
     };
 }
 
 static RPCHelpMan getnodeaddresses() {
     return RPCHelpMan{
         "getnodeaddresses",
         "Return known addresses, which can potentially be used to find new "
         "nodes in the network.\n",
         {
             {"count", RPCArg::Type::NUM, RPCArg::Default{1},
              "The maximum number of addresses to return. Specify 0 to return "
              "all known addresses."},
             {"network", RPCArg::Type::STR, RPCArg::DefaultHint{"all networks"},
              "Return only addresses of the specified network. Can be one of: " +
                  Join(GetNetworkNames(), ", ") + "."},
         },
         RPCResult{RPCResult::Type::ARR,
                   "",
                   "",
                   {
                       {RPCResult::Type::OBJ,
                        "",
                        "",
                        {
                            {RPCResult::Type::NUM_TIME, "time",
                             "The " + UNIX_EPOCH_TIME +
                                 " when the node was last seen"},
                            {RPCResult::Type::NUM, "services",
                             "The services offered by the node"},
                            {RPCResult::Type::STR, "address",
                             "The address of the node"},
                            {RPCResult::Type::NUM, "port",
                             "The port number of the node"},
                            {RPCResult::Type::STR, "network",
                             "The network (" + Join(GetNetworkNames(), ", ") +
                                 ") the node connected through"},
                        }},
                   }},
         RPCExamples{HelpExampleCli("getnodeaddresses", "8") +
                     HelpExampleCli("getnodeaddresses", "4 \"i2p\"") +
                     HelpExampleCli("-named getnodeaddresses",
                                    "network=onion count=12") +
                     HelpExampleRpc("getnodeaddresses", "8") +
                     HelpExampleRpc("getnodeaddresses", "4, \"i2p\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             const CConnman &connman = EnsureConnman(node);
 
             const int count{request.params[0].isNull()
                                 ? 1
                                 : request.params[0].getInt<int>()};
             if (count < 0) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Address count out of range");
             }
 
             const std::optional<Network> network{
                 request.params[1].isNull()
                     ? std::nullopt
                     : std::optional<Network>{
                           ParseNetwork(request.params[1].get_str())}};
             if (network == NET_UNROUTABLE) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    strprintf("Network not recognized: %s",
                                              request.params[1].get_str()));
             }
             // returns a shuffled list of CAddress
             const std::vector<CAddress> vAddr{
                 connman.GetAddresses(count, /* max_pct */ 0, network)};
             UniValue ret(UniValue::VARR);
 
             for (const CAddress &addr : vAddr) {
                 UniValue obj(UniValue::VOBJ);
                 obj.pushKV(
                     "time",
                     int64_t{TicksSinceEpoch<std::chrono::seconds>(addr.nTime)});
                 obj.pushKV("services", uint64_t(addr.nServices));
                 obj.pushKV("address", addr.ToStringIP());
                 obj.pushKV("port", addr.GetPort());
                 obj.pushKV("network", GetNetworkName(addr.GetNetClass()));
                 ret.push_back(obj);
             }
             return ret;
         },
     };
 }
 
 static RPCHelpMan addpeeraddress() {
     return RPCHelpMan{
         "addpeeraddress",
         "Add the address of a potential peer to the address manager. This "
         "RPC is for testing only.\n",
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The IP address of the peer"},
             {"port", RPCArg::Type::NUM, RPCArg::Optional::NO,
              "The port of the peer"},
             {"tried", RPCArg::Type::BOOL, RPCArg::Default{false},
              "If true, attempt to add the peer to the tried addresses table"},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::BOOL, "success",
                  "whether the peer address was successfully added to the "
                  "address manager"},
             },
         },
         RPCExamples{
             HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333 true") +
             HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333, true")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             NodeContext &node = EnsureAnyNodeContext(request.context);
             if (!node.addrman) {
                 throw JSONRPCError(
                     RPC_CLIENT_P2P_DISABLED,
                     "Error: Address manager functionality missing or disabled");
             }
 
             const std::string &addr_string{request.params[0].get_str()};
             const uint16_t port{
                 static_cast<uint16_t>(request.params[1].getInt<int>())};
             const bool tried{request.params[2].isTrue()};
 
             UniValue obj(UniValue::VOBJ);
             CNetAddr net_addr;
             bool success{false};
 
             if (LookupHost(addr_string, net_addr, false)) {
                 CAddress address{{net_addr, port}, ServiceFlags(NODE_NETWORK)};
                 address.nTime = AdjustedTime();
                 // The source address is set equal to the address. This is
                 // equivalent to the peer announcing itself.
                 if (node.addrman->Add({address}, address)) {
                     success = true;
                     if (tried) {
                         // Attempt to move the address to the tried addresses
                         // table.
                         node.addrman->Good(address);
                     }
                 }
             }
 
             obj.pushKV("success", success);
             return obj;
         },
     };
 }
 
 void RegisterNetRPCCommands(CRPCTable &t) {
     // clang-format off
     static const CRPCCommand commands[] = {
         //  category            actor (function)
         //  ------------------  ----------------------
         { "network",            getconnectioncount,      },
         { "network",            ping,                    },
         { "network",            getpeerinfo,             },
         { "network",            addnode,                 },
         { "network",            disconnectnode,          },
         { "network",            getaddednodeinfo,        },
         { "network",            getnettotals,            },
         { "network",            getnetworkinfo,          },
         { "network",            setban,                  },
         { "network",            listbanned,              },
         { "network",            clearbanned,             },
         { "network",            setnetworkactive,        },
         { "network",            getnodeaddresses,        },
         { "hidden",             addconnection,           },
         { "hidden",             addpeeraddress,          },
     };
     // clang-format on
     for (const auto &c : commands) {
         t.appendCommand(c.name, &c);
     }
 }
diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h
index 254c555b4..cb15c88a7 100644
--- a/src/univalue/include/univalue.h
+++ b/src/univalue/include/univalue.h
@@ -1,217 +1,215 @@
 // Copyright 2014 BitPay Inc.
 // Copyright 2015 Bitcoin Core Developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or https://opensource.org/licenses/mit-license.php.
 
 #ifndef BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H
 #define BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H
 
 #include <charconv>
 #include <cstdint>
 #include <cstring>
 #include <map>
 #include <stdexcept>
 #include <string>
 #include <type_traits>
 #include <vector>
 
 namespace {
     struct UniValueStreamWriter;
 }
 
 class UniValue {
     friend struct ::UniValueStreamWriter;
 
 public:
     enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, };
 
     UniValue() : typ(VNULL) {}
     UniValue(UniValue::VType type, const std::string& value = std::string()) : typ(type), val(value) {}
-    UniValue(uint64_t val_) {
-        setInt(val_);
-    }
-    UniValue(int64_t val_) {
-        setInt(val_);
-    }
-    UniValue(bool val_) {
-        setBool(val_);
-    }
-    UniValue(int val_) {
-        setInt(val_);
-    }
-    UniValue(double val_) {
-        setFloat(val_);
-    }
-    UniValue(const std::string& val_) {
-        setStr(val_);
-    }
-    UniValue(const char *val_) {
-        std::string s(val_);
-        setStr(s);
+    template <typename Ref, typename T = std::remove_cv_t<std::remove_reference_t<Ref>>,
+              std::enable_if_t<std::is_floating_point_v<T> ||                      // setFloat
+                                   std::is_same_v<bool, T> ||                      // setBool
+                                   std::is_signed_v<T> || std::is_unsigned_v<T> || // setInt
+                                   std::is_constructible_v<std::string, T>,        // setStr
+                               bool> = true>
+    UniValue(Ref&& val)
+    {
+        if constexpr (std::is_floating_point_v<T>) {
+            setFloat(val);
+        } else if constexpr (std::is_same_v<bool, T>) {
+            setBool(val);
+        } else if constexpr (std::is_signed_v<T>) {
+            setInt(int64_t{val});
+        } else if constexpr (std::is_unsigned_v<T>) {
+            setInt(uint64_t{val});
+        } else {
+            setStr(std::string{std::forward<Ref>(val)});
+        }
     }
 
     void clear();
     void reserve(size_t n) {
         if (typ == VOBJ || typ == VARR) {
             if (typ == VOBJ)
                 keys.reserve(n);
             values.reserve(n);
         } else if (typ != VNULL) {
             val.reserve(n);
         }
     }
 
     bool setNull();
     bool setBool(bool val);
     bool setNumStr(const std::string& val);
     bool setInt(uint64_t val);
     bool setInt(int64_t val);
     bool setInt(int val_) { return setInt((int64_t)val_); }
     bool setFloat(double val);
     bool setStr(const std::string& val);
     bool setArray();
     bool setObject();
 
     enum VType getType() const { return typ; }
     const std::string& getValStr() const { return val; }
     bool empty() const { return (values.size() == 0); }
 
     size_t size() const { return values.size(); }
 
     bool getBool() const { return isTrue(); }
     void getObjMap(std::map<std::string,UniValue>& kv) const;
     bool checkObject(const std::map<std::string,UniValue::VType>& memberTypes) const;
     const UniValue& operator[](const std::string& key) const;
     const UniValue& operator[](size_t index) const;
     bool exists(const std::string& key) const { size_t i; return findKey(key, i); }
 
     bool isNull() const { return (typ == VNULL); }
     bool isTrue() const { return (typ == VBOOL) && (val == "1"); }
     bool isFalse() const { return (typ == VBOOL) && (val != "1"); }
     bool isBool() const { return (typ == VBOOL); }
     bool isStr() const { return (typ == VSTR); }
     bool isNum() const { return (typ == VNUM); }
     bool isArray() const { return (typ == VARR); }
     bool isObject() const { return (typ == VOBJ); }
 
     void push_back(const UniValue& val);
     void push_backV(const std::vector<UniValue>& vec);
     template <class It>
     void push_backV(It first, It last);
 
     void __pushKV(const std::string& key, const UniValue& val);
     void pushKV(const std::string& key, const UniValue& val);
     void pushKVs(const UniValue& obj);
 
     std::string write(unsigned int prettyIndent = 0,
                       unsigned int indentLevel = 0) const;
 
     bool read(const char *raw, size_t len);
     bool read(const char *raw) { return read(raw, strlen(raw)); }
     bool read(const std::string& rawStr) {
         return read(rawStr.data(), rawStr.size());
     }
 
 private:
     UniValue::VType typ;
     std::string val;                       // numbers are stored as C++ strings
     std::vector<std::string> keys;
     std::vector<UniValue> values;
 
     bool findKey(const std::string& key, size_t& retIdx) const;
 
 public:
     // Strict type-specific getters, these throw std::runtime_error if the
     // value is of unexpected type
     const std::vector<std::string>& getKeys() const;
     const std::vector<UniValue>& getValues() const;
     template <typename Int>
     auto getInt() const
     {
         static_assert(std::is_integral<Int>::value);
         if (typ != VNUM) {
             throw std::runtime_error("JSON value is not an integer as expected");
         }
         Int result;
         const auto [first_nonmatching, error_condition] = std::from_chars(val.data(), val.data() + val.size(), result);
         if (first_nonmatching != val.data() + val.size() || error_condition != std::errc{}) {
             throw std::runtime_error("JSON integer out of range");
         }
         return result;
     }
     bool get_bool() const;
     const std::string& get_str() const;
     double get_real() const;
     const UniValue& get_obj() const;
     const UniValue& get_array() const;
 
     enum VType type() const { return getType(); }
 
     const UniValue& find_value(std::string_view key) const;
 };
 
 template <class It>
 void UniValue::push_backV(It first, It last)
 {
     if (typ != VARR) {
         throw std::runtime_error{"JSON value is not an array as expected"};
     }
     values.insert(values.end(), first, last);
 }
 
 enum jtokentype {
     JTOK_ERR        = -1,
     JTOK_NONE       = 0,                           // eof
     JTOK_OBJ_OPEN,
     JTOK_OBJ_CLOSE,
     JTOK_ARR_OPEN,
     JTOK_ARR_CLOSE,
     JTOK_COLON,
     JTOK_COMMA,
     JTOK_KW_NULL,
     JTOK_KW_TRUE,
     JTOK_KW_FALSE,
     JTOK_NUMBER,
     JTOK_STRING,
 };
 
 extern enum jtokentype getJsonToken(std::string& tokenVal,
                                     unsigned int& consumed, const char *raw, const char *end);
 extern const char *uvTypeName(UniValue::VType t);
 
 static inline bool jsonTokenIsValue(enum jtokentype jtt)
 {
     switch (jtt) {
     case JTOK_KW_NULL:
     case JTOK_KW_TRUE:
     case JTOK_KW_FALSE:
     case JTOK_NUMBER:
     case JTOK_STRING:
         return true;
 
     default:
         return false;
     }
 
     // not reached
 }
 
 static inline bool json_isspace(int ch)
 {
     switch (ch) {
     case 0x20:
     case 0x09:
     case 0x0a:
     case 0x0d:
         return true;
 
     default:
         return false;
     }
 
     // not reached
 }
 
 extern const UniValue NullUniValue;
 
 #endif // BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index de4607bc7..50f8f046d 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,4993 +1,4993 @@
 // Copyright (c) 2010 Satoshi Nakamoto
 // Copyright (c) 2009-2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <chainparams.h> // for GetConsensus.
 #include <coins.h>
 #include <common/system.h>
 #include <config.h>
 #include <consensus/amount.h>
 #include <consensus/validation.h>
 #include <core_io.h>
 #include <interfaces/chain.h>
 #include <key_io.h>
 #include <node/context.h>
 #include <outputtype.h>
 #include <policy/fees.h>
 #include <policy/policy.h>
 #include <rpc/rawtransaction_util.h>
 #include <rpc/server.h>
 #include <rpc/util.h>
 #include <script/descriptor.h>
 #include <util/bip32.h>
 #include <util/error.h>
 #include <util/moneystr.h>
 #include <util/string.h>
 #include <util/translation.h>
 #include <util/url.h>
 #include <util/vector.h>
 #include <wallet/coincontrol.h>
 #include <wallet/context.h>
 #include <wallet/load.h>
 #include <wallet/receive.h>
 #include <wallet/rpc/util.h>
 #include <wallet/rpcwallet.h>
 #include <wallet/spend.h>
 #include <wallet/wallet.h>
 #include <wallet/walletdb.h>
 #include <wallet/walletutil.h>
 
 #include <univalue.h>
 
 #include <event2/http.h>
 
 #include <optional>
 #include <variant>
 
 using interfaces::FoundBlock;
 
 /**
  * Checks if a CKey is in the given CWallet compressed or otherwise
  */
 bool HaveKey(const SigningProvider &wallet, const CKey &key) {
     CKey key2;
     key2.Set(key.begin(), key.end(), !key.IsCompressed());
     return wallet.HaveKey(key.GetPubKey().GetID()) ||
            wallet.HaveKey(key2.GetPubKey().GetID());
 }
 
 static void WalletTxToJSON(const CWallet &wallet, const CWalletTx &wtx,
                            UniValue &entry)
     EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) {
     interfaces::Chain &chain = wallet.chain();
     int confirms = wallet.GetTxDepthInMainChain(wtx);
     entry.pushKV("confirmations", confirms);
     if (wtx.IsCoinBase()) {
         entry.pushKV("generated", true);
     }
     if (confirms > 0) {
         entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
         entry.pushKV("blockheight", wtx.m_confirm.block_height);
         entry.pushKV("blockindex", wtx.m_confirm.nIndex);
         int64_t block_time;
         CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock,
                                        FoundBlock().time(block_time)));
         entry.pushKV("blocktime", block_time);
     } else {
         entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));
     }
     uint256 hash = wtx.GetId();
     entry.pushKV("txid", hash.GetHex());
     UniValue conflicts(UniValue::VARR);
     for (const uint256 &conflict : wallet.GetTxConflicts(wtx)) {
         conflicts.push_back(conflict.GetHex());
     }
     entry.pushKV("walletconflicts", conflicts);
     entry.pushKV("time", wtx.GetTxTime());
     entry.pushKV("timereceived", int64_t{wtx.nTimeReceived});
 
     for (const std::pair<const std::string, std::string> &item : wtx.mapValue) {
         entry.pushKV(item.first, item.second);
     }
 }
 
 static RPCHelpMan getnewaddress() {
     return RPCHelpMan{
         "getnewaddress",
         "Returns a new Bitcoin address for receiving payments.\n"
         "If 'label' is specified, it is added to the address book \n"
         "so payments received with the address will be associated with "
         "'label'.\n",
         {
             {"label", RPCArg::Type::STR, RPCArg::Default{""},
              "The label name for the address to be linked to. If not provided, "
              "the default label \"\" is used. 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."},
             {"address_type", RPCArg::Type::STR,
              RPCArg::DefaultHint{"set by -addresstype"},
              "The address type to use. Options are \"legacy\"."},
         },
         RPCResult{RPCResult::Type::STR, "address", "The new bitcoin address"},
         RPCExamples{HelpExampleCli("getnewaddress", "") +
                     HelpExampleRpc("getnewaddress", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
             LOCK(pwallet->cs_wallet);
 
             if (!pwallet->CanGetAddresses()) {
                 throw JSONRPCError(RPC_WALLET_ERROR,
                                    "Error: This wallet has no available keys");
             }
 
             // Parse the label first so we don't generate a key if there's an
             // error
             std::string label;
             if (!request.params[0].isNull()) {
                 label = LabelFromValue(request.params[0]);
             }
 
             OutputType output_type = pwallet->m_default_address_type;
             if (!request.params[1].isNull()) {
                 if (!ParseOutputType(request.params[1].get_str(),
                                      output_type)) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        strprintf("Unknown address type '%s'",
                                                  request.params[1].get_str()));
                 }
             }
 
             CTxDestination dest;
             std::string error;
             if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
                 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
             }
 
             return EncodeDestination(dest, config);
         },
     };
 }
 
 static RPCHelpMan getrawchangeaddress() {
     return RPCHelpMan{
         "getrawchangeaddress",
         "Returns a new Bitcoin address, for receiving change.\n"
         "This is for use with raw transactions, NOT normal use.\n",
         {},
         RPCResult{RPCResult::Type::STR, "address", "The address"},
         RPCExamples{HelpExampleCli("getrawchangeaddress", "") +
                     HelpExampleRpc("getrawchangeaddress", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             if (!pwallet->CanGetAddresses(true)) {
                 throw JSONRPCError(RPC_WALLET_ERROR,
                                    "Error: This wallet has no available keys");
             }
 
             OutputType output_type = pwallet->m_default_change_type.value_or(
                 pwallet->m_default_address_type);
             if (!request.params[0].isNull()) {
                 if (!ParseOutputType(request.params[0].get_str(),
                                      output_type)) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        strprintf("Unknown address type '%s'",
                                                  request.params[0].get_str()));
                 }
             }
 
             CTxDestination dest;
             std::string error;
             if (!pwallet->GetNewChangeDestination(output_type, dest, error)) {
                 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
             }
             return EncodeDestination(dest, config);
         },
     };
 }
 
 static RPCHelpMan setlabel() {
     return RPCHelpMan{
         "setlabel",
         "Sets the label associated with the given address.\n",
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The bitcoin address to be associated with a label."},
             {"label", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The label to assign to the address."},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{
             HelpExampleCli("setlabel",
                            "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") +
             HelpExampleRpc(
                 "setlabel",
                 "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             CTxDestination dest = DecodeDestination(request.params[0].get_str(),
                                                     wallet->GetChainParams());
             if (!IsValidDestination(dest)) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Invalid Bitcoin address");
             }
 
             std::string label = LabelFromValue(request.params[1]);
 
             if (pwallet->IsMine(dest)) {
                 pwallet->SetAddressBook(dest, label, "receive");
             } else {
                 pwallet->SetAddressBook(dest, label, "send");
             }
 
             return NullUniValue;
         },
     };
 }
 
 void ParseRecipients(const UniValue &address_amounts,
                      const UniValue &subtract_fee_outputs,
                      std::vector<CRecipient> &recipients,
                      const CChainParams &chainParams) {
     std::set<CTxDestination> destinations;
     int i = 0;
     for (const std::string &address : address_amounts.getKeys()) {
         CTxDestination dest = DecodeDestination(address, chainParams);
         if (!IsValidDestination(dest)) {
             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                std::string("Invalid Bitcoin address: ") +
                                    address);
         }
 
         if (destinations.count(dest)) {
             throw JSONRPCError(
                 RPC_INVALID_PARAMETER,
                 std::string("Invalid parameter, duplicated address: ") +
                     address);
         }
         destinations.insert(dest);
 
         CScript script_pub_key = GetScriptForDestination(dest);
         Amount amount = AmountFromValue(address_amounts[i++]);
 
         bool subtract_fee = false;
         for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
             const UniValue &addr = subtract_fee_outputs[idx];
             if (addr.get_str() == address) {
                 subtract_fee = true;
             }
         }
 
         CRecipient recipient = {script_pub_key, amount, subtract_fee};
         recipients.push_back(recipient);
     }
 }
 
 UniValue SendMoney(CWallet *const pwallet, const CCoinControl &coin_control,
                    std::vector<CRecipient> &recipients, mapValue_t map_value,
                    bool broadcast = true) {
     EnsureWalletIsUnlocked(pwallet);
 
     // Shuffle recipient list
     std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
 
     // Send
     Amount nFeeRequired = Amount::zero();
     int nChangePosRet = -1;
     bilingual_str error;
     CTransactionRef tx;
     bool fCreated = CreateTransaction(
         *pwallet, recipients, tx, nFeeRequired, nChangePosRet, error,
         coin_control,
         !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
     if (!fCreated) {
         throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
     }
     pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */,
                                broadcast);
     return tx->GetId().GetHex();
 }
 
 static RPCHelpMan sendtoaddress() {
     return RPCHelpMan{
         "sendtoaddress",
         "Send an amount to a given address.\n" + HELP_REQUIRING_PASSPHRASE,
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The bitcoin address to send to."},
             {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
              "The amount in " + Currency::get().ticker + " to send. eg 0.1"},
             {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
              "A comment used to store what the transaction is for.\n"
              "                             This is not part of the "
              "transaction, just kept in your wallet."},
             {"comment_to", RPCArg::Type::STR,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "A comment to store the name of the person or organization\n"
              "                             to which you're sending the "
              "transaction. This is not part of the \n"
              "                             transaction, just kept in "
              "your wallet."},
             {"subtractfeefromamount", RPCArg::Type::BOOL,
              RPCArg::Default{false},
              "The fee will be deducted from the amount being sent.\n"
              "                             The recipient will receive "
              "less bitcoins than you enter in the amount field."},
             {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true},
              "(only available if avoid_reuse wallet flag is set) Avoid "
              "spending from dirty addresses; addresses are considered\n"
              "                             dirty if they have previously "
              "been used in a transaction."},
         },
         RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
         RPCExamples{
             HelpExampleCli("sendtoaddress",
                            "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 100000") +
             HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
                                             "dd\" 100000 \"donation\" \"seans "
                                             "outpost\"") +
             HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44"
                                             "Jvaydd\" 100000 \"\" \"\" true") +
             HelpExampleRpc("sendtoaddress",
                            "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
                            "dd\", 100000, \"donation\", \"seans "
                            "outpost\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             // Wallet comments
             mapValue_t mapValue;
             if (!request.params[2].isNull() &&
                 !request.params[2].get_str().empty()) {
                 mapValue["comment"] = request.params[2].get_str();
             }
             if (!request.params[3].isNull() &&
                 !request.params[3].get_str().empty()) {
                 mapValue["to"] = request.params[3].get_str();
             }
 
             bool fSubtractFeeFromAmount = false;
             if (!request.params[4].isNull()) {
                 fSubtractFeeFromAmount = request.params[4].get_bool();
             }
 
             CCoinControl coin_control;
             coin_control.m_avoid_address_reuse =
                 GetAvoidReuseFlag(pwallet, request.params[5]);
             // We also enable partial spend avoidance if reuse avoidance is set.
             coin_control.m_avoid_partial_spends |=
                 coin_control.m_avoid_address_reuse;
 
             EnsureWalletIsUnlocked(pwallet);
 
             UniValue address_amounts(UniValue::VOBJ);
             const std::string address = request.params[0].get_str();
             address_amounts.pushKV(address, request.params[1]);
             UniValue subtractFeeFromAmount(UniValue::VARR);
             if (fSubtractFeeFromAmount) {
                 subtractFeeFromAmount.push_back(address);
             }
 
             std::vector<CRecipient> recipients;
             ParseRecipients(address_amounts, subtractFeeFromAmount, recipients,
                             wallet->GetChainParams());
 
             return SendMoney(pwallet, coin_control, recipients, mapValue);
         },
     };
 }
 
 static RPCHelpMan listaddressgroupings() {
     return RPCHelpMan{
         "listaddressgroupings",
         "Lists groups of addresses which have had their common ownership\n"
         "made public by common use as inputs or as the resulting change\n"
         "in past transactions\n",
         {},
         RPCResult{RPCResult::Type::ARR,
                   "",
                   "",
                   {
                       {RPCResult::Type::ARR,
                        "",
                        "",
                        {
                            {RPCResult::Type::ARR_FIXED,
                             "",
                             "",
                             {
                                 {RPCResult::Type::STR, "address",
                                  "The bitcoin address"},
                                 {RPCResult::Type::STR_AMOUNT, "amount",
                                  "The amount in " + Currency::get().ticker},
                                 {RPCResult::Type::STR, "label",
                                  /* optional */ true, "The label"},
                             }},
                        }},
                   }},
         RPCExamples{HelpExampleCli("listaddressgroupings", "") +
                     HelpExampleRpc("listaddressgroupings", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             UniValue jsonGroupings(UniValue::VARR);
             std::map<CTxDestination, Amount> balances =
                 GetAddressBalances(*pwallet);
             for (const std::set<CTxDestination> &grouping :
                  GetAddressGroupings(*pwallet)) {
                 UniValue jsonGrouping(UniValue::VARR);
                 for (const CTxDestination &address : grouping) {
                     UniValue addressInfo(UniValue::VARR);
                     addressInfo.push_back(EncodeDestination(address, config));
                     addressInfo.push_back(balances[address]);
 
                     const auto *address_book_entry =
                         pwallet->FindAddressBookEntry(address);
                     if (address_book_entry) {
                         addressInfo.push_back(address_book_entry->GetLabel());
                     }
                     jsonGrouping.push_back(addressInfo);
                 }
                 jsonGroupings.push_back(jsonGrouping);
             }
 
             return jsonGroupings;
         },
     };
 }
 
 static Amount GetReceived(const CWallet &wallet, const UniValue &params,
                           bool by_label)
     EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) {
     std::set<CTxDestination> address_set;
 
     if (by_label) {
         // Get the set of addresses assigned to label
         std::string label = LabelFromValue(params[0]);
         address_set = wallet.GetLabelAddresses(label);
     } else {
         // Get the address
         CTxDestination dest =
             DecodeDestination(params[0].get_str(), wallet.GetChainParams());
         if (!IsValidDestination(dest)) {
             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                "Invalid Bitcoin address");
         }
         CScript script_pub_key = GetScriptForDestination(dest);
         if (!wallet.IsMine(script_pub_key)) {
             throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
         }
         address_set.insert(dest);
     }
 
     // Minimum confirmations
     int min_depth = 1;
     if (!params[1].isNull()) {
         min_depth = params[1].getInt<int>();
     }
 
     // Tally
     Amount amount = Amount::zero();
     for (const std::pair<const TxId, CWalletTx> &wtx_pair : wallet.mapWallet) {
         const CWalletTx &wtx = wtx_pair.second;
         if (wtx.IsCoinBase() || wallet.GetTxDepthInMainChain(wtx) < min_depth) {
             continue;
         }
 
         for (const CTxOut &txout : wtx.tx->vout) {
             CTxDestination address;
             if (ExtractDestination(txout.scriptPubKey, address) &&
                 wallet.IsMine(address) && address_set.count(address)) {
                 amount += txout.nValue;
             }
         }
     }
 
     return amount;
 }
 
 static RPCHelpMan getreceivedbyaddress() {
     return RPCHelpMan{
         "getreceivedbyaddress",
         "Returns the total amount received by the given address in "
         "transactions with at least minconf confirmations.\n",
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The bitcoin address for transactions."},
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{1},
              "Only include transactions confirmed at least this many times."},
         },
         RPCResult{RPCResult::Type::STR_AMOUNT, "amount",
                   "The total amount in " + Currency::get().ticker +
                       " received at this address."},
         RPCExamples{
             "\nThe amount from transactions with at least 1 confirmation\n" +
             HelpExampleCli("getreceivedbyaddress",
                            "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") +
             "\nThe amount including unconfirmed transactions, zero "
             "confirmations\n" +
             HelpExampleCli("getreceivedbyaddress",
                            "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") +
             "\nThe amount with at least 6 confirmations\n" +
             HelpExampleCli("getreceivedbyaddress",
                            "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") +
             "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("getreceivedbyaddress",
                            "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             return GetReceived(*pwallet, request.params,
                                /* by_label */ false);
         },
     };
 }
 
 static RPCHelpMan getreceivedbylabel() {
     return RPCHelpMan{
         "getreceivedbylabel",
         "Returns the total amount received by addresses with <label> in "
         "transactions with at least [minconf] confirmations.\n",
         {
             {"label", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The selected label, may be the default label using \"\"."},
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{1},
              "Only include transactions confirmed at least this many times."},
         },
         RPCResult{RPCResult::Type::STR_AMOUNT, "amount",
                   "The total amount in " + Currency::get().ticker +
                       " received for this label."},
         RPCExamples{"\nAmount received by the default label with at least 1 "
                     "confirmation\n" +
                     HelpExampleCli("getreceivedbylabel", "\"\"") +
                     "\nAmount received at the tabby label including "
                     "unconfirmed amounts with zero confirmations\n" +
                     HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") +
                     "\nThe amount with at least 6 confirmations\n" +
                     HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") +
                     "\nAs a JSON-RPC call\n" +
                     HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             return GetReceived(*pwallet, request.params,
                                /* by_label */ true);
         },
     };
 }
 
 static RPCHelpMan getbalance() {
     return RPCHelpMan{
         "getbalance",
         "Returns the total available balance.\n"
         "The available balance is what the wallet considers currently "
         "spendable, and is\n"
         "thus affected by options which limit spendability such as "
         "-spendzeroconfchange.\n",
         {
             {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
              "Remains for backward compatibility. Must be excluded or set to "
              "\"*\"."},
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{0},
              "Only include transactions confirmed at least this many times."},
             {"include_watchonly", RPCArg::Type::BOOL,
              RPCArg::DefaultHint{
                  "true for watch-only wallets, otherwise false"},
              "Also include balance in watch-only addresses (see "
              "'importaddress')"},
             {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true},
              "(only available if avoid_reuse wallet flag is set) Do not "
              "include balance in dirty outputs; addresses are considered dirty "
              "if they have previously been used in a transaction."},
         },
         RPCResult{RPCResult::Type::STR_AMOUNT, "amount",
                   "The total amount in " + Currency::get().ticker +
                       " received for this wallet."},
         RPCExamples{
             "\nThe total amount in the wallet with 0 or more confirmations\n" +
             HelpExampleCli("getbalance", "") +
             "\nThe total amount in the wallet with at least 6 confirmations\n" +
             HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("getbalance", "\"*\", 6")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             const UniValue &dummy_value = request.params[0];
             if (!dummy_value.isNull() && dummy_value.get_str() != "*") {
                 throw JSONRPCError(
                     RPC_METHOD_DEPRECATED,
                     "dummy first argument must be excluded or set to \"*\".");
             }
 
             int min_depth = 0;
             if (!request.params[1].isNull()) {
                 min_depth = request.params[1].getInt<int>();
             }
 
             bool include_watchonly =
                 ParseIncludeWatchonly(request.params[2], *pwallet);
 
             bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]);
 
             const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse);
 
             return bal.m_mine_trusted + (include_watchonly
                                              ? bal.m_watchonly_trusted
                                              : Amount::zero());
         },
     };
 }
 
 static RPCHelpMan getunconfirmedbalance() {
     return RPCHelpMan{
         "getunconfirmedbalance",
         "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
         {},
         RPCResult{RPCResult::Type::NUM, "", "The balance"},
         RPCExamples{""},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             return GetBalance(*pwallet).m_mine_untrusted_pending;
         },
     };
 }
 
 static RPCHelpMan sendmany() {
     return RPCHelpMan{
         "sendmany",
         "Send multiple times. Amounts are double-precision "
         "floating point numbers." +
             HELP_REQUIRING_PASSPHRASE,
         {
             {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO,
              "Must be set to \"\" for backwards compatibility.",
              RPCArgOptions{.skip_type_check = true,
                            .oneline_description = "\"\""}},
             {
                 "amounts",
                 RPCArg::Type::OBJ,
                 RPCArg::Optional::NO,
                 "The addresses and amounts",
                 {
                     {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
                      "The bitcoin address is the key, the numeric amount (can "
                      "be string) in " +
                          Currency::get().ticker + " is the value"},
                 },
             },
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{1},
              "Only use the balance confirmed at least this many times."},
             {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
              "A comment"},
             {
                 "subtractfeefrom",
                 RPCArg::Type::ARR,
                 RPCArg::Optional::OMITTED_NAMED_ARG,
                 "The addresses.\n"
                 "                           The fee will be equally deducted "
                 "from the amount of each selected address.\n"
                 "                           Those recipients will receive less "
                 "bitcoins than you enter in their corresponding amount field.\n"
                 "                           If no addresses are specified "
                 "here, the sender pays the fee.",
                 {
                     {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                      "Subtract fee from this address"},
                 },
             },
         },
         RPCResult{RPCResult::Type::STR_HEX, "txid",
                   "The transaction id for the send. Only 1 transaction is "
                   "created regardless of the number of addresses."},
         RPCExamples{
             "\nSend two amounts to two different addresses:\n" +
             HelpExampleCli(
                 "sendmany",
                 "\"\" "
                 "\"{\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\":"
                 "0.01,"
                 "\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\":0."
                 "02}\"") +
             "\nSend two amounts to two different addresses setting the "
             "confirmation and comment:\n" +
             HelpExampleCli(
                 "sendmany",
                 "\"\" "
                 "\"{\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\":"
                 "0.01,"
                 "\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\":0."
                 "02}\" "
                 "6 \"testing\"") +
             "\nSend two amounts to two different addresses, subtract fee "
             "from amount:\n" +
             HelpExampleCli(
                 "sendmany",
                 "\"\" "
                 "\"{\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\":"
                 "0.01,"
                 "\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\":0."
                 "02}\" 1 \"\" "
                 "\"[\\\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\\\","
                 "\\\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\\\"]"
                 "\"") +
             "\nAs a JSON-RPC call\n" +
             HelpExampleRpc(
                 "sendmany",
                 "\"\", "
                 "{\"bchtest:qplljx455cznj2yrtdhj0jcm7syxlzqnaqt0ku5kjl\":0.01,"
                 "\"bchtest:qzmnuh8t24yrxq4mvjakt84r7j3f9tunlvm2p7qef9\":0.02}, "
                 "6, "
                 "\"testing\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             if (!request.params[0].isNull() &&
                 !request.params[0].get_str().empty()) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Dummy value must be set to \"\"");
             }
             UniValue sendTo = request.params[1].get_obj();
 
             mapValue_t mapValue;
             if (!request.params[3].isNull() &&
                 !request.params[3].get_str().empty()) {
                 mapValue["comment"] = request.params[3].get_str();
             }
 
             UniValue subtractFeeFromAmount(UniValue::VARR);
             if (!request.params[4].isNull()) {
                 subtractFeeFromAmount = request.params[4].get_array();
             }
 
             std::vector<CRecipient> recipients;
             ParseRecipients(sendTo, subtractFeeFromAmount, recipients,
                             wallet->GetChainParams());
 
             CCoinControl coin_control;
             return SendMoney(pwallet, coin_control, recipients,
                              std::move(mapValue));
         },
     };
 }
 
 static RPCHelpMan addmultisigaddress() {
     return RPCHelpMan{
         "addmultisigaddress",
         "Add an nrequired-to-sign multisignature address to the wallet. "
         "Requires a new wallet backup.\n"
         "Each key is a Bitcoin address or hex-encoded public key.\n"
         "This functionality is only intended for use with non-watchonly "
         "addresses.\n"
         "See `importaddress` for watchonly p2sh address support.\n"
         "If 'label' is specified (DEPRECATED), assign address to that label.\n",
         {
             {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO,
              "The number of required signatures out of the n keys or "
              "addresses."},
             {
                 "keys",
                 RPCArg::Type::ARR,
                 RPCArg::Optional::NO,
                 "The bitcoin addresses or hex-encoded public keys",
                 {
                     {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                      "bitcoin address or hex-encoded public key"},
                 },
             },
             {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
              "A label to assign the addresses to."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "address",
                        "The value of the new multisig address"},
                       {RPCResult::Type::STR_HEX, "redeemScript",
                        "The string value of the hex-encoded redemption script"},
                       {RPCResult::Type::STR, "descriptor",
                        "The descriptor for this multisig"},
                   }},
         RPCExamples{
             "\nAdd a multisig address from 2 addresses\n" +
             HelpExampleCli("addmultisigaddress",
                            "2 "
                            "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\","
                            "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
             "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("addmultisigaddress",
                            "2, "
                            "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\","
                            "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             LegacyScriptPubKeyMan &spk_man =
                 EnsureLegacyScriptPubKeyMan(*pwallet);
 
             LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
 
             std::string label;
             if (!request.params[2].isNull()) {
                 label = LabelFromValue(request.params[2]);
             }
 
             int required = request.params[0].getInt<int>();
 
             // Get the public keys
             const UniValue &keys_or_addrs = request.params[1].get_array();
             std::vector<CPubKey> pubkeys;
             for (size_t i = 0; i < keys_or_addrs.size(); ++i) {
                 if (IsHex(keys_or_addrs[i].get_str()) &&
                     (keys_or_addrs[i].get_str().length() == 66 ||
                      keys_or_addrs[i].get_str().length() == 130)) {
                     pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
                 } else {
                     pubkeys.push_back(AddrToPubKey(wallet->GetChainParams(),
                                                    spk_man,
                                                    keys_or_addrs[i].get_str()));
                 }
             }
 
             OutputType output_type = pwallet->m_default_address_type;
 
             // Construct using pay-to-script-hash:
             CScript inner;
             CTxDestination dest = AddAndGetMultisigDestination(
                 required, pubkeys, output_type, spk_man, inner);
             pwallet->SetAddressBook(dest, label, "send");
 
             // Make the descriptor
             std::unique_ptr<Descriptor> descriptor =
                 InferDescriptor(GetScriptForDestination(dest), spk_man);
 
             UniValue result(UniValue::VOBJ);
             result.pushKV("address", EncodeDestination(dest, config));
             result.pushKV("redeemScript", HexStr(inner));
             result.pushKV("descriptor", descriptor->ToString());
             return result;
         },
     };
 }
 
 struct tallyitem {
     Amount nAmount{Amount::zero()};
     int nConf{std::numeric_limits<int>::max()};
     std::vector<uint256> txids;
     bool fIsWatchonly{false};
     tallyitem() {}
 };
 
 static UniValue ListReceived(const Config &config, const CWallet *const pwallet,
                              const UniValue &params, bool by_label)
     EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
     // Minimum confirmations
     int nMinDepth = 1;
     if (!params[0].isNull()) {
         nMinDepth = params[0].getInt<int>();
     }
 
     // Whether to include empty labels
     bool fIncludeEmpty = false;
     if (!params[1].isNull()) {
         fIncludeEmpty = params[1].get_bool();
     }
 
     isminefilter filter = ISMINE_SPENDABLE;
     if (ParseIncludeWatchonly(params[2], *pwallet)) {
         filter |= ISMINE_WATCH_ONLY;
     }
 
     bool has_filtered_address = false;
     CTxDestination filtered_address = CNoDestination();
     if (!by_label && params.size() > 3) {
         if (!IsValidDestinationString(params[3].get_str(),
                                       pwallet->GetChainParams())) {
             throw JSONRPCError(RPC_WALLET_ERROR,
                                "address_filter parameter was invalid");
         }
         filtered_address =
             DecodeDestination(params[3].get_str(), pwallet->GetChainParams());
         has_filtered_address = true;
     }
 
     // Tally
     std::map<CTxDestination, tallyitem> mapTally;
     for (const std::pair<const TxId, CWalletTx> &pairWtx : pwallet->mapWallet) {
         const CWalletTx &wtx = pairWtx.second;
 
         if (wtx.IsCoinBase()) {
             continue;
         }
 
         int nDepth = pwallet->GetTxDepthInMainChain(wtx);
         if (nDepth < nMinDepth) {
             continue;
         }
 
         for (const CTxOut &txout : wtx.tx->vout) {
             CTxDestination address;
             if (!ExtractDestination(txout.scriptPubKey, address)) {
                 continue;
             }
 
             if (has_filtered_address && !(filtered_address == address)) {
                 continue;
             }
 
             isminefilter mine = pwallet->IsMine(address);
             if (!(mine & filter)) {
                 continue;
             }
 
             tallyitem &item = mapTally[address];
             item.nAmount += txout.nValue;
             item.nConf = std::min(item.nConf, nDepth);
             item.txids.push_back(wtx.GetId());
             if (mine & ISMINE_WATCH_ONLY) {
                 item.fIsWatchonly = true;
             }
         }
     }
 
     // Reply
     UniValue ret(UniValue::VARR);
     std::map<std::string, tallyitem> label_tally;
 
     // Create m_address_book iterator
     // If we aren't filtering, go from begin() to end()
     auto start = pwallet->m_address_book.begin();
     auto end = pwallet->m_address_book.end();
     // If we are filtering, find() the applicable entry
     if (has_filtered_address) {
         start = pwallet->m_address_book.find(filtered_address);
         if (start != end) {
             end = std::next(start);
         }
     }
 
     for (auto item_it = start; item_it != end; ++item_it) {
         if (item_it->second.IsChange()) {
             continue;
         }
         const CTxDestination &address = item_it->first;
         const std::string &label = item_it->second.GetLabel();
         std::map<CTxDestination, tallyitem>::iterator it =
             mapTally.find(address);
         if (it == mapTally.end() && !fIncludeEmpty) {
             continue;
         }
 
         Amount nAmount = Amount::zero();
         int nConf = std::numeric_limits<int>::max();
         bool fIsWatchonly = false;
         if (it != mapTally.end()) {
             nAmount = (*it).second.nAmount;
             nConf = (*it).second.nConf;
             fIsWatchonly = (*it).second.fIsWatchonly;
         }
 
         if (by_label) {
             tallyitem &_item = label_tally[label];
             _item.nAmount += nAmount;
             _item.nConf = std::min(_item.nConf, nConf);
             _item.fIsWatchonly = fIsWatchonly;
         } else {
             UniValue obj(UniValue::VOBJ);
             if (fIsWatchonly) {
                 obj.pushKV("involvesWatchonly", true);
             }
             obj.pushKV("address", EncodeDestination(address, config));
             obj.pushKV("amount", nAmount);
             obj.pushKV("confirmations",
                        (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
             obj.pushKV("label", label);
             UniValue transactions(UniValue::VARR);
             if (it != mapTally.end()) {
                 for (const uint256 &_item : (*it).second.txids) {
                     transactions.push_back(_item.GetHex());
                 }
             }
             obj.pushKV("txids", transactions);
             ret.push_back(obj);
         }
     }
 
     if (by_label) {
         for (const auto &entry : label_tally) {
             Amount nAmount = entry.second.nAmount;
             int nConf = entry.second.nConf;
             UniValue obj(UniValue::VOBJ);
             if (entry.second.fIsWatchonly) {
                 obj.pushKV("involvesWatchonly", true);
             }
             obj.pushKV("amount", nAmount);
             obj.pushKV("confirmations",
                        (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
             obj.pushKV("label", entry.first);
             ret.push_back(obj);
         }
     }
 
     return ret;
 }
 
 static RPCHelpMan listreceivedbyaddress() {
     return RPCHelpMan{
         "listreceivedbyaddress",
         "List balances by receiving address.\n",
         {
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{1},
              "The minimum number of confirmations before payments are "
              "included."},
             {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false},
              "Whether to include addresses that haven't received any "
              "payments."},
             {"include_watchonly", RPCArg::Type::BOOL,
              RPCArg::DefaultHint{
                  "true for watch-only wallets, otherwise false"},
              "Whether to include watch-only addresses (see 'importaddress')."},
             {"address_filter", RPCArg::Type::STR,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "If present, only return information on this address."},
         },
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {
                 {RPCResult::Type::OBJ,
                  "",
                  "",
                  {
                      {RPCResult::Type::BOOL, "involvesWatchonly",
                       "Only returns true if imported addresses were involved "
                       "in transaction"},
                      {RPCResult::Type::STR, "address", "The receiving address"},
                      {RPCResult::Type::STR_AMOUNT, "amount",
                       "The total amount in " + Currency::get().ticker +
                           " received by the address"},
                      {RPCResult::Type::NUM, "confirmations",
                       "The number of confirmations of the most recent "
                       "transaction included"},
                      {RPCResult::Type::STR, "label",
                       "The label of the receiving address. The default label "
                       "is \"\""},
                      {RPCResult::Type::ARR,
                       "txids",
                       "",
                       {
                           {RPCResult::Type::STR_HEX, "txid",
                            "The ids of transactions received with the address"},
                       }},
                  }},
             }},
         RPCExamples{
             HelpExampleCli("listreceivedbyaddress", "") +
             HelpExampleCli("listreceivedbyaddress", "6 true") +
             HelpExampleRpc("listreceivedbyaddress", "6, true, true") +
             HelpExampleRpc(
                 "listreceivedbyaddress",
                 "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             return ListReceived(config, pwallet, request.params, false);
         },
     };
 }
 
 static RPCHelpMan listreceivedbylabel() {
     return RPCHelpMan{
         "listreceivedbylabel",
         "List received transactions by label.\n",
         {
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{1},
              "The minimum number of confirmations before payments are "
              "included."},
             {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false},
              "Whether to include labels that haven't received any payments."},
             {"include_watchonly", RPCArg::Type::BOOL,
              RPCArg::DefaultHint{
                  "true for watch-only wallets, otherwise false"},
              "Whether to include watch-only addresses (see 'importaddress')."},
         },
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {
                 {RPCResult::Type::OBJ,
                  "",
                  "",
                  {
                      {RPCResult::Type::BOOL, "involvesWatchonly",
                       "Only returns true if imported addresses were involved "
                       "in transaction"},
                      {RPCResult::Type::STR_AMOUNT, "amount",
                       "The total amount received by addresses with this label"},
                      {RPCResult::Type::NUM, "confirmations",
                       "The number of confirmations of the most recent "
                       "transaction included"},
                      {RPCResult::Type::STR, "label",
                       "The label of the receiving address. The default label "
                       "is \"\""},
                  }},
             }},
         RPCExamples{HelpExampleCli("listreceivedbylabel", "") +
                     HelpExampleCli("listreceivedbylabel", "6 true") +
                     HelpExampleRpc("listreceivedbylabel", "6, true, true")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             return ListReceived(config, pwallet, request.params, true);
         },
     };
 }
 
 static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) {
     if (IsValidDestination(dest)) {
         entry.pushKV("address", EncodeDestination(dest, GetConfig()));
     }
 }
 
 /**
  * List transactions based on the given criteria.
  *
  * @param  pwallet        The wallet.
  * @param  wtx            The wallet transaction.
  * @param  nMinDepth      The minimum confirmation depth.
  * @param  fLong          Whether to include the JSON version of the
  * transaction.
  * @param  ret            The vector into which the result is stored.
  * @param  filter_ismine  The "is mine" filter flags.
  * @param  filter_label   Optional label string to filter incoming transactions.
  */
 template <class Vec>
 static void ListTransactions(const CWallet *const pwallet, const CWalletTx &wtx,
                              int nMinDepth, bool fLong, Vec &ret,
                              const isminefilter &filter_ismine,
                              const std::string *filter_label)
     EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
     Amount nFee;
     std::list<COutputEntry> listReceived;
     std::list<COutputEntry> listSent;
 
     CachedTxGetAmounts(*pwallet, wtx, listReceived, listSent, nFee,
                        filter_ismine);
 
     bool involvesWatchonly = CachedTxIsFromMe(*pwallet, wtx, ISMINE_WATCH_ONLY);
 
     // Sent
     if (!filter_label) {
         for (const COutputEntry &s : listSent) {
             UniValue entry(UniValue::VOBJ);
             if (involvesWatchonly ||
                 (pwallet->IsMine(s.destination) & ISMINE_WATCH_ONLY)) {
                 entry.pushKV("involvesWatchonly", true);
             }
             MaybePushAddress(entry, s.destination);
             entry.pushKV("category", "send");
             entry.pushKV("amount", -s.amount);
             const auto *address_book_entry =
                 pwallet->FindAddressBookEntry(s.destination);
             if (address_book_entry) {
                 entry.pushKV("label", address_book_entry->GetLabel());
             }
             entry.pushKV("vout", s.vout);
             entry.pushKV("fee", -1 * nFee);
             if (fLong) {
                 WalletTxToJSON(*pwallet, wtx, entry);
             }
             entry.pushKV("abandoned", wtx.isAbandoned());
             ret.push_back(entry);
         }
     }
 
     // Received
     if (listReceived.size() > 0 &&
         pwallet->GetTxDepthInMainChain(wtx) >= nMinDepth) {
         for (const COutputEntry &r : listReceived) {
             std::string label;
             const auto *address_book_entry =
                 pwallet->FindAddressBookEntry(r.destination);
             if (address_book_entry) {
                 label = address_book_entry->GetLabel();
             }
             if (filter_label && label != *filter_label) {
                 continue;
             }
             UniValue entry(UniValue::VOBJ);
             if (involvesWatchonly ||
                 (pwallet->IsMine(r.destination) & ISMINE_WATCH_ONLY)) {
                 entry.pushKV("involvesWatchonly", true);
             }
             MaybePushAddress(entry, r.destination);
             if (wtx.IsCoinBase()) {
                 if (pwallet->GetTxDepthInMainChain(wtx) < 1) {
                     entry.pushKV("category", "orphan");
                 } else if (pwallet->IsTxImmatureCoinBase(wtx)) {
                     entry.pushKV("category", "immature");
                 } else {
                     entry.pushKV("category", "generate");
                 }
             } else {
                 entry.pushKV("category", "receive");
             }
             entry.pushKV("amount", r.amount);
             if (address_book_entry) {
                 entry.pushKV("label", label);
             }
             entry.pushKV("vout", r.vout);
             if (fLong) {
                 WalletTxToJSON(*pwallet, wtx, entry);
             }
             ret.push_back(entry);
         }
     }
 }
 
 static const std::vector<RPCResult> TransactionDescriptionString() {
     return {
         {RPCResult::Type::NUM, "confirmations",
          "The number of confirmations for the transaction. Negative "
          "confirmations means the\n"
          "transaction conflicted that many blocks ago."},
         {RPCResult::Type::BOOL, "generated",
          "Only present if transaction only input is a coinbase one."},
         {RPCResult::Type::BOOL, "trusted",
          "Only present if we consider transaction to be trusted and so safe to "
          "spend from."},
         {RPCResult::Type::STR_HEX, "blockhash",
          "The block hash containing the transaction."},
         {RPCResult::Type::NUM, "blockheight",
          "The block height containing the transaction."},
         {RPCResult::Type::NUM, "blockindex",
          "The index of the transaction in the block that includes it."},
         {RPCResult::Type::NUM_TIME, "blocktime",
          "The block time expressed in " + UNIX_EPOCH_TIME + "."},
         {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
         {RPCResult::Type::ARR,
          "walletconflicts",
          "Conflicting transaction ids.",
          {
              {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
          }},
         {RPCResult::Type::NUM_TIME, "time",
          "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
         {RPCResult::Type::NUM_TIME, "timereceived",
          "The time received expressed in " + UNIX_EPOCH_TIME + "."},
         {RPCResult::Type::STR, "comment",
          "If a comment is associated with the transaction, only present if not "
          "empty."},
     };
 }
 
 RPCHelpMan listtransactions() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "listtransactions",
         "If a label name is provided, this will return only incoming "
         "transactions paying to addresses with the specified label.\n"
         "\nReturns up to 'count' most recent transactions skipping the first "
         "'from' transactions.\n",
         {
             {"label|dummy", RPCArg::Type::STR,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "If set, should be a valid label name to return only incoming "
              "transactions with the specified label, or \"*\" to disable "
              "filtering and return all transactions."},
             {"count", RPCArg::Type::NUM, RPCArg::Default{10},
              "The number of transactions to return"},
             {"skip", RPCArg::Type::NUM, RPCArg::Default{0},
              "The number of transactions to skip"},
             {"include_watchonly", RPCArg::Type::BOOL,
              RPCArg::DefaultHint{
                  "true for watch-only wallets, otherwise false"},
              "Include transactions to watch-only addresses (see "
              "'importaddress')"},
         },
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {
                 {RPCResult::Type::OBJ, "", "",
                  Cat(Cat<std::vector<RPCResult>>(
                          {
                              {RPCResult::Type::BOOL, "involvesWatchonly",
                               "Only returns true if imported addresses were "
                               "involved in transaction."},
                              {RPCResult::Type::STR, "address",
                               "The bitcoin address of the transaction."},
                              {RPCResult::Type::STR, "category",
                               "The transaction category.\n"
                               "\"send\"                  Transactions sent.\n"
                               "\"receive\"               Non-coinbase "
                               "transactions received.\n"
                               "\"generate\"              Coinbase transactions "
                               "received with more than 100 confirmations.\n"
                               "\"immature\"              Coinbase transactions "
                               "received with 100 or fewer confirmations.\n"
                               "\"orphan\"                Orphaned coinbase "
                               "transactions received."},
                              {RPCResult::Type::STR_AMOUNT, "amount",
                               "The amount in " + ticker +
                                   ". This is negative for the 'send' category, "
                                   "and is positive\n"
                                   "for all other categories"},
                              {RPCResult::Type::STR, "label",
                               "A comment for the address/transaction, if any"},
                              {RPCResult::Type::NUM, "vout", "the vout value"},
                              {RPCResult::Type::STR_AMOUNT, "fee",
                               "The amount of the fee in " + ticker +
                                   ". This is negative and only available for "
                                   "the\n"
                                   "'send' category of transactions."},
                          },
                          TransactionDescriptionString()),
                      {
                          {RPCResult::Type::BOOL, "abandoned",
                           "'true' if the transaction has been abandoned "
                           "(inputs are respendable). Only available for the \n"
                           "'send' category of transactions."},
                      })},
             }},
         RPCExamples{"\nList the most recent 10 transactions in the systems\n" +
                     HelpExampleCli("listtransactions", "") +
                     "\nList transactions 100 to 120\n" +
                     HelpExampleCli("listtransactions", "\"*\" 20 100") +
                     "\nAs a JSON-RPC call\n" +
                     HelpExampleRpc("listtransactions", "\"*\", 20, 100")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             const std::string *filter_label = nullptr;
             if (!request.params[0].isNull() &&
                 request.params[0].get_str() != "*") {
                 filter_label = &request.params[0].get_str();
                 if (filter_label->empty()) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Label argument must be a valid label name or \"*\".");
                 }
             }
             int nCount = 10;
             if (!request.params[1].isNull()) {
                 nCount = request.params[1].getInt<int>();
             }
 
             int nFrom = 0;
             if (!request.params[2].isNull()) {
                 nFrom = request.params[2].getInt<int>();
             }
 
             isminefilter filter = ISMINE_SPENDABLE;
             if (ParseIncludeWatchonly(request.params[3], *pwallet)) {
                 filter |= ISMINE_WATCH_ONLY;
             }
 
             if (nCount < 0) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
             }
             if (nFrom < 0) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
             }
 
             std::vector<UniValue> ret;
             {
                 LOCK(pwallet->cs_wallet);
 
                 const CWallet::TxItems &txOrdered = pwallet->wtxOrdered;
 
                 // iterate backwards until we have nCount items to return:
                 for (CWallet::TxItems::const_reverse_iterator it =
                          txOrdered.rbegin();
                      it != txOrdered.rend(); ++it) {
                     CWalletTx *const pwtx = (*it).second;
                     ListTransactions(pwallet, *pwtx, 0, true, ret, filter,
                                      filter_label);
                     if (int(ret.size()) >= (nCount + nFrom)) {
                         break;
                     }
                 }
             }
 
             // ret is newest to oldest
 
             if (nFrom > (int)ret.size()) {
                 nFrom = ret.size();
             }
             if ((nFrom + nCount) > (int)ret.size()) {
                 nCount = ret.size() - nFrom;
             }
 
             auto txs_rev_it{std::make_move_iterator(ret.rend())};
             UniValue result{UniValue::VARR};
             // Return oldest to newest
             result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom);
             return result;
         },
     };
 }
 
 static RPCHelpMan listsinceblock() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "listsinceblock",
         "Get all transactions in blocks since block [blockhash], or all "
         "transactions if omitted.\n"
         "If \"blockhash\" is no longer a part of the main chain, transactions "
         "from the fork point onward are included.\n"
         "Additionally, if include_removed is set, transactions affecting the "
         "wallet which were removed are returned in the \"removed\" array.\n",
         {
             {"blockhash", RPCArg::Type::STR,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "If set, the block hash to list transactions since, otherwise "
              "list all transactions."},
             {"target_confirmations", RPCArg::Type::NUM, RPCArg::Default{1},
              "Return the nth block hash from the main chain. e.g. 1 would mean "
              "the best block hash. Note: this is not used as a filter, but "
              "only affects [lastblock] in the return value"},
             {"include_watchonly", RPCArg::Type::BOOL,
              RPCArg::DefaultHint{
                  "true for watch-only wallets, otherwise false"},
              "Include transactions to watch-only addresses (see "
              "'importaddress')"},
             {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true},
              "Show transactions that were removed due to a reorg in the "
              "\"removed\" array\n"
              "                                                           (not "
              "guaranteed to work on pruned nodes)"},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::ARR,
                  "transactions",
                  "",
                  {
                      {RPCResult::Type::OBJ, "", "",
                       Cat(Cat<std::vector<RPCResult>>(
                               {
                                   {RPCResult::Type::BOOL, "involvesWatchonly",
                                    "Only returns true if imported addresses "
                                    "were involved in transaction."},
                                   {RPCResult::Type::STR, "address",
                                    "The bitcoin address of the transaction."},
                                   {RPCResult::Type::STR, "category",
                                    "The transaction category.\n"
                                    "\"send\"                  Transactions "
                                    "sent.\n"
                                    "\"receive\"               Non-coinbase "
                                    "transactions received.\n"
                                    "\"generate\"              Coinbase "
                                    "transactions received with more than 100 "
                                    "confirmations.\n"
                                    "\"immature\"              Coinbase "
                                    "transactions received with 100 or fewer "
                                    "confirmations.\n"
                                    "\"orphan\"                Orphaned "
                                    "coinbase transactions received."},
                                   {RPCResult::Type::STR_AMOUNT, "amount",
                                    "The amount in " + ticker +
                                        ". This is negative for the 'send' "
                                        "category, and is positive\n"
                                        "for all other categories"},
                                   {RPCResult::Type::NUM, "vout",
                                    "the vout value"},
                                   {RPCResult::Type::STR_AMOUNT, "fee",
                                    "The amount of the fee in " + ticker +
                                        ". This is negative and only available "
                                        "for the\n"
                                        "'send' category of transactions."},
                               },
                               TransactionDescriptionString()),
                           {
                               {RPCResult::Type::BOOL, "abandoned",
                                "'true' if the transaction has been abandoned "
                                "(inputs are respendable). Only available for "
                                "the \n"
                                "'send' category of transactions."},
                               {RPCResult::Type::STR, "comment",
                                "If a comment is associated with the "
                                "transaction."},
                               {RPCResult::Type::STR, "label",
                                "A comment for the address/transaction, if any"},
                               {RPCResult::Type::STR, "to",
                                "If a comment to is associated with the "
                                "transaction."},
                           })},
                  }},
                 {RPCResult::Type::ARR,
                  "removed",
                  "<structure is the same as \"transactions\" above, only "
                  "present if include_removed=true>\n"
                  "Note: transactions that were re-added in the active chain "
                  "will appear as-is in this array, and may thus have a "
                  "positive confirmation count.",
                  {
                      {RPCResult::Type::ELISION, "", ""},
                  }},
                 {RPCResult::Type::STR_HEX, "lastblock",
                  "The hash of the block (target_confirmations-1) from the best "
                  "block on the main chain, or the genesis hash if the "
                  "referenced block does not exist yet. This is typically used "
                  "to feed back into listsinceblock the next time you call it. "
                  "So you would generally use a target_confirmations of say 6, "
                  "so you will be continually re-notified of transactions until "
                  "they've reached 6 confirmations plus any new ones"},
             }},
         RPCExamples{HelpExampleCli("listsinceblock", "") +
                     HelpExampleCli("listsinceblock",
                                    "\"000000000000000bacf66f7497b7dc45ef753ee9a"
                                    "7d38571037cdb1a57f663ad\" 6") +
                     HelpExampleRpc("listsinceblock",
                                    "\"000000000000000bacf66f7497b7dc45ef753ee9a"
                                    "7d38571037cdb1a57f663ad\", 6")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const pwallet =
                 GetWalletForJSONRPCRequest(request);
 
             if (!pwallet) {
                 return NullUniValue;
             }
 
             const CWallet &wallet = *pwallet;
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             wallet.BlockUntilSyncedToCurrentChain();
 
             LOCK(wallet.cs_wallet);
 
             // Height of the specified block or the common ancestor, if the
             // block provided was in a deactivated chain.
             std::optional<int> height;
 
             // Height of the specified block, even if it's in a deactivated
             // chain.
             std::optional<int> altheight;
             int target_confirms = 1;
             isminefilter filter = ISMINE_SPENDABLE;
 
             BlockHash blockId;
             if (!request.params[0].isNull() &&
                 !request.params[0].get_str().empty()) {
                 blockId = BlockHash(ParseHashV(request.params[0], "blockhash"));
                 height = int{};
                 altheight = int{};
                 if (!wallet.chain().findCommonAncestor(
                         blockId, wallet.GetLastBlockHash(),
                         /* ancestor out */ FoundBlock().height(*height),
                         /* blockId out */ FoundBlock().height(*altheight))) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Block not found");
                 }
             }
 
             if (!request.params[1].isNull()) {
                 target_confirms = request.params[1].getInt<int>();
 
                 if (target_confirms < 1) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Invalid parameter");
                 }
             }
 
             if (ParseIncludeWatchonly(request.params[2], wallet)) {
                 filter |= ISMINE_WATCH_ONLY;
             }
 
             bool include_removed =
                 (request.params[3].isNull() || request.params[3].get_bool());
 
             int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
 
             UniValue transactions(UniValue::VARR);
 
             for (const std::pair<const TxId, CWalletTx> &pairWtx :
                  wallet.mapWallet) {
                 const CWalletTx &tx = pairWtx.second;
 
                 if (depth == -1 || wallet.GetTxDepthInMainChain(tx) < depth) {
                     ListTransactions(&wallet, tx, 0, true, transactions, filter,
                                      nullptr /* filter_label */);
                 }
             }
 
             // when a reorg'd block is requested, we also list any relevant
             // transactions in the blocks of the chain that was detached
             UniValue removed(UniValue::VARR);
             while (include_removed && altheight && *altheight > *height) {
                 CBlock block;
                 if (!wallet.chain().findBlock(blockId,
                                               FoundBlock().data(block)) ||
                     block.IsNull()) {
                     throw JSONRPCError(RPC_INTERNAL_ERROR,
                                        "Can't read block from disk");
                 }
                 for (const CTransactionRef &tx : block.vtx) {
                     auto it = wallet.mapWallet.find(tx->GetId());
                     if (it != wallet.mapWallet.end()) {
                         // We want all transactions regardless of confirmation
                         // count to appear here, even negative confirmation
                         // ones, hence the big negative.
                         ListTransactions(&wallet, it->second, -100000000, true,
                                          removed, filter,
                                          nullptr /* filter_label */);
                     }
                 }
                 blockId = block.hashPrevBlock;
                 --*altheight;
             }
 
             BlockHash lastblock;
             target_confirms =
                 std::min(target_confirms, wallet.GetLastBlockHeight() + 1);
             CHECK_NONFATAL(wallet.chain().findAncestorByHeight(
                 wallet.GetLastBlockHash(),
                 wallet.GetLastBlockHeight() + 1 - target_confirms,
                 FoundBlock().hash(lastblock)));
 
             UniValue ret(UniValue::VOBJ);
             ret.pushKV("transactions", transactions);
             if (include_removed) {
                 ret.pushKV("removed", removed);
             }
             ret.pushKV("lastblock", lastblock.GetHex());
 
             return ret;
         },
     };
 }
 
 static RPCHelpMan gettransaction() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "gettransaction",
         "Get detailed information about in-wallet transaction <txid>\n",
         {
             {"txid", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The transaction id"},
             {"include_watchonly", RPCArg::Type::BOOL,
              RPCArg::DefaultHint{
                  "true for watch-only wallets, otherwise false"},
              "Whether to include watch-only addresses in balance calculation "
              "and details[]"},
             {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
              "Whether to include a `decoded` field containing the decoded "
              "transaction (equivalent to RPC decoderawtransaction)"},
         },
         RPCResult{
             RPCResult::Type::OBJ, "", "",
             Cat(Cat<std::vector<RPCResult>>(
                     {
                         {RPCResult::Type::STR_AMOUNT, "amount",
                          "The amount in " + ticker},
                         {RPCResult::Type::STR_AMOUNT, "fee",
                          "The amount of the fee in " + ticker +
                              ". This is negative and only available for the\n"
                              "'send' category of transactions."},
                     },
                     TransactionDescriptionString()),
                 {
                     {RPCResult::Type::ARR,
                      "details",
                      "",
                      {
                          {RPCResult::Type::OBJ,
                           "",
                           "",
                           {
                               {RPCResult::Type::BOOL, "involvesWatchonly",
                                "Only returns true if imported addresses were "
                                "involved in transaction."},
                               {RPCResult::Type::STR, "address",
                                "The bitcoin address involved in the "
                                "transaction."},
                               {RPCResult::Type::STR, "category",
                                "The transaction category.\n"
                                "\"send\"                  Transactions sent.\n"
                                "\"receive\"               Non-coinbase "
                                "transactions received.\n"
                                "\"generate\"              Coinbase "
                                "transactions received with more than 100 "
                                "confirmations.\n"
                                "\"immature\"              Coinbase "
                                "transactions received with 100 or fewer "
                                "confirmations.\n"
                                "\"orphan\"                Orphaned coinbase "
                                "transactions received."},
                               {RPCResult::Type::STR_AMOUNT, "amount",
                                "The amount in " + ticker},
                               {RPCResult::Type::STR, "label",
                                "A comment for the address/transaction, if any"},
                               {RPCResult::Type::NUM, "vout", "the vout value"},
                               {RPCResult::Type::STR_AMOUNT, "fee",
                                "The amount of the fee in " + ticker +
                                    ". This is negative and only available for "
                                    "the \n"
                                    "'send' category of transactions."},
                               {RPCResult::Type::BOOL, "abandoned",
                                "'true' if the transaction has been abandoned "
                                "(inputs are respendable). Only available for "
                                "the \n"
                                "'send' category of transactions."},
                           }},
                      }},
                     {RPCResult::Type::STR_HEX, "hex",
                      "Raw data for transaction"},
                     {RPCResult::Type::OBJ,
                      "decoded",
                      "Optional, the decoded transaction (only present when "
                      "`verbose` is passed)",
                      {
                          {RPCResult::Type::ELISION, "",
                           "Equivalent to the RPC decoderawtransaction method, "
                           "or the RPC getrawtransaction method when `verbose` "
                           "is passed."},
                      }},
                 })},
         RPCExamples{HelpExampleCli("gettransaction",
                                    "\"1075db55d416d3ca199f55b6084e2115b9345e16c"
                                    "5cf302fc80e9d5fbf5d48d\"") +
                     HelpExampleCli("gettransaction",
                                    "\"1075db55d416d3ca199f55b6084e2115b9345e16c"
                                    "5cf302fc80e9d5fbf5d48d\" true") +
                     HelpExampleCli("gettransaction",
                                    "\"1075db55d416d3ca199f55b6084e2115b9345e16c"
                                    "5cf302fc80e9d5fbf5d48d\" false true") +
                     HelpExampleRpc("gettransaction",
                                    "\"1075db55d416d3ca199f55b6084e2115b9345e16c"
                                    "5cf302fc80e9d5fbf5d48d\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             TxId txid(ParseHashV(request.params[0], "txid"));
 
             isminefilter filter = ISMINE_SPENDABLE;
             if (ParseIncludeWatchonly(request.params[1], *pwallet)) {
                 filter |= ISMINE_WATCH_ONLY;
             }
 
             bool verbose = request.params[2].isNull()
                                ? false
                                : request.params[2].get_bool();
 
             UniValue entry(UniValue::VOBJ);
             auto it = pwallet->mapWallet.find(txid);
             if (it == pwallet->mapWallet.end()) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Invalid or non-wallet transaction id");
             }
             const CWalletTx &wtx = it->second;
 
             Amount nCredit = CachedTxGetCredit(*pwallet, wtx, filter);
             Amount nDebit = CachedTxGetDebit(*pwallet, wtx, filter);
             Amount nNet = nCredit - nDebit;
             Amount nFee = (CachedTxIsFromMe(*pwallet, wtx, filter)
                                ? wtx.tx->GetValueOut() - nDebit
                                : Amount::zero());
 
             entry.pushKV("amount", nNet - nFee);
             if (CachedTxIsFromMe(*pwallet, wtx, filter)) {
                 entry.pushKV("fee", nFee);
             }
 
             WalletTxToJSON(*pwallet, wtx, entry);
 
             UniValue details(UniValue::VARR);
             ListTransactions(pwallet, wtx, 0, false, details, filter,
                              nullptr /* filter_label */);
             entry.pushKV("details", details);
 
             std::string strHex =
                 EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags());
             entry.pushKV("hex", strHex);
 
             if (verbose) {
                 UniValue decoded(UniValue::VOBJ);
                 TxToUniv(*wtx.tx, BlockHash(), decoded, false);
                 entry.pushKV("decoded", decoded);
             }
 
             return entry;
         },
     };
 }
 
 static RPCHelpMan abandontransaction() {
     return RPCHelpMan{
         "abandontransaction",
         "Mark in-wallet transaction <txid> as abandoned\n"
         "This will mark this transaction and all its in-wallet descendants as "
         "abandoned which will allow\n"
         "for their inputs to be respent.  It can be used to replace \"stuck\" "
         "or evicted transactions.\n"
         "It only works on transactions which are not included in a block and "
         "are not currently in the mempool.\n"
         "It has no effect on transactions which are already abandoned.\n",
         {
             {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The transaction id"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("abandontransaction",
                                    "\"1075db55d416d3ca199f55b6084e2115b9345e16c"
                                    "5cf302fc80e9d5fbf5d48d\"") +
                     HelpExampleRpc("abandontransaction",
                                    "\"1075db55d416d3ca199f55b6084e2115b9345e16c"
                                    "5cf302fc80e9d5fbf5d48d\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             TxId txid(ParseHashV(request.params[0], "txid"));
 
             if (!pwallet->mapWallet.count(txid)) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Invalid or non-wallet transaction id");
             }
 
             if (!pwallet->AbandonTransaction(txid)) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Transaction not eligible for abandonment");
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan keypoolrefill() {
     return RPCHelpMan{
         "keypoolrefill",
         "Fills the keypool." + HELP_REQUIRING_PASSPHRASE,
         {
             {"newsize", RPCArg::Type::NUM, RPCArg::Default{100},
              "The new keypool size"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("keypoolrefill", "") +
                     HelpExampleRpc("keypoolrefill", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             if (pwallet->IsLegacy() &&
                 pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
                 throw JSONRPCError(
                     RPC_WALLET_ERROR,
                     "Error: Private keys are disabled for this wallet");
             }
 
             LOCK(pwallet->cs_wallet);
 
             // 0 is interpreted by TopUpKeyPool() as the default keypool size
             // given by -keypool
             unsigned int kpSize = 0;
             if (!request.params[0].isNull()) {
                 if (request.params[0].getInt<int>() < 0) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, expected valid size.");
                 }
                 kpSize = (unsigned int)request.params[0].getInt<int>();
             }
 
             EnsureWalletIsUnlocked(pwallet);
             pwallet->TopUpKeyPool(kpSize);
 
             if (pwallet->GetKeyPoolSize() < kpSize) {
                 throw JSONRPCError(RPC_WALLET_ERROR,
                                    "Error refreshing keypool.");
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan lockunspent() {
     return RPCHelpMan{
         "lockunspent",
         "Updates list of temporarily unspendable outputs.\n"
         "Temporarily lock (unlock=false) or unlock (unlock=true) specified "
         "transaction outputs.\n"
         "If no transaction outputs are specified when unlocking then all "
         "current locked transaction outputs are unlocked.\n"
         "A locked transaction output will not be chosen by automatic coin "
         "selection, when spending bitcoins.\n"
         "Manually selected coins are automatically unlocked.\n"
         "Locks are stored in memory only. Nodes start with zero locked "
         "outputs, and the locked output list\n"
         "is always cleared (by virtue of process exit) when a node stops or "
         "fails.\n"
         "Also see the listunspent call\n",
         {
             {"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO,
              "Whether to unlock (true) or lock (false) the specified "
              "transactions"},
             {
                 "transactions",
                 RPCArg::Type::ARR,
                 RPCArg::Default{UniValue::VARR},
                 "The transaction outputs and within each, txid (string) vout "
                 "(numeric).",
                 {
                     {
                         "",
                         RPCArg::Type::OBJ,
                         RPCArg::Optional::OMITTED,
                         "",
                         {
                             {"txid", RPCArg::Type::STR_HEX,
                              RPCArg::Optional::NO, "The transaction id"},
                             {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
                              "The output number"},
                         },
                     },
                 },
             },
         },
         RPCResult{RPCResult::Type::BOOL, "",
                   "Whether the command was successful or not"},
         RPCExamples{
             "\nList the unspent transactions\n" +
             HelpExampleCli("listunspent", "") +
             "\nLock an unspent transaction\n" +
             HelpExampleCli("lockunspent", "false "
                                           "\"[{\\\"txid\\\":"
                                           "\\\"a08e6907dbbd3d809776dbfc5d82e371"
                                           "b764ed838b5655e72f463568df1aadf0\\\""
                                           ",\\\"vout\\\":1}]\"") +
             "\nList the locked transactions\n" +
             HelpExampleCli("listlockunspent", "") +
             "\nUnlock the transaction again\n" +
             HelpExampleCli("lockunspent", "true "
                                           "\"[{\\\"txid\\\":"
                                           "\\\"a08e6907dbbd3d809776dbfc5d82e371"
                                           "b764ed838b5655e72f463568df1aadf0\\\""
                                           ",\\\"vout\\\":1}]\"") +
             "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("lockunspent", "false, "
                                           "\"[{\\\"txid\\\":"
                                           "\\\"a08e6907dbbd3d809776dbfc5d82e371"
                                           "b764ed838b5655e72f463568df1aadf0\\\""
                                           ",\\\"vout\\\":1}]\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
 
             bool fUnlock = request.params[0].get_bool();
 
             if (request.params[1].isNull()) {
                 if (fUnlock) {
                     pwallet->UnlockAllCoins();
                 }
                 return true;
             }
 
             RPCTypeCheckArgument(request.params[1], UniValue::VARR);
 
             const UniValue &output_params = request.params[1];
 
             // Create and validate the COutPoints first.
 
             std::vector<COutPoint> outputs;
             outputs.reserve(output_params.size());
 
             for (size_t idx = 0; idx < output_params.size(); idx++) {
                 const UniValue &o = output_params[idx].get_obj();
 
                 RPCTypeCheckObj(o, {
                                        {"txid", UniValueType(UniValue::VSTR)},
                                        {"vout", UniValueType(UniValue::VNUM)},
                                    });
 
                 const int nOutput = o.find_value("vout").getInt<int>();
                 if (nOutput < 0) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, vout cannot be negative");
                 }
 
                 const TxId txid(ParseHashO(o, "txid"));
                 const auto it = pwallet->mapWallet.find(txid);
                 if (it == pwallet->mapWallet.end()) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, unknown transaction");
                 }
 
                 const COutPoint output(txid, nOutput);
                 const CWalletTx &trans = it->second;
                 if (output.GetN() >= trans.tx->vout.size()) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, vout index out of bounds");
                 }
 
                 if (pwallet->IsSpent(output)) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, expected unspent output");
                 }
 
                 const bool is_locked = pwallet->IsLockedCoin(output);
                 if (fUnlock && !is_locked) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, expected locked output");
                 }
 
                 if (!fUnlock && is_locked) {
                     throw JSONRPCError(
                         RPC_INVALID_PARAMETER,
                         "Invalid parameter, output already locked");
                 }
 
                 outputs.push_back(output);
             }
 
             // Atomically set (un)locked status for the outputs.
             for (const COutPoint &output : outputs) {
                 if (fUnlock) {
                     pwallet->UnlockCoin(output);
                 } else {
                     pwallet->LockCoin(output);
                 }
             }
 
             return true;
         },
     };
 }
 
 static RPCHelpMan listlockunspent() {
     return RPCHelpMan{
         "listlockunspent",
         "Returns list of temporarily unspendable outputs.\n"
         "See the lockunspent call to lock and unlock transactions for "
         "spending.\n",
         {},
         RPCResult{RPCResult::Type::ARR,
                   "",
                   "",
                   {
                       {RPCResult::Type::OBJ,
                        "",
                        "",
                        {
                            {RPCResult::Type::STR_HEX, "txid",
                             "The transaction id locked"},
                            {RPCResult::Type::NUM, "vout", "The vout value"},
                        }},
                   }},
         RPCExamples{
             "\nList the unspent transactions\n" +
             HelpExampleCli("listunspent", "") +
             "\nLock an unspent transaction\n" +
             HelpExampleCli("lockunspent", "false "
                                           "\"[{\\\"txid\\\":"
                                           "\\\"a08e6907dbbd3d809776dbfc5d82e371"
                                           "b764ed838b5655e72f463568df1aadf0\\\""
                                           ",\\\"vout\\\":1}]\"") +
             "\nList the locked transactions\n" +
             HelpExampleCli("listlockunspent", "") +
             "\nUnlock the transaction again\n" +
             HelpExampleCli("lockunspent", "true "
                                           "\"[{\\\"txid\\\":"
                                           "\\\"a08e6907dbbd3d809776dbfc5d82e371"
                                           "b764ed838b5655e72f463568df1aadf0\\\""
                                           ",\\\"vout\\\":1}]\"") +
             "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             std::vector<COutPoint> vOutpts;
             pwallet->ListLockedCoins(vOutpts);
 
             UniValue ret(UniValue::VARR);
 
             for (const COutPoint &output : vOutpts) {
                 UniValue o(UniValue::VOBJ);
 
                 o.pushKV("txid", output.GetTxId().GetHex());
                 o.pushKV("vout", int(output.GetN()));
                 ret.push_back(o);
             }
 
             return ret;
         },
     };
 }
 
 static RPCHelpMan settxfee() {
     return RPCHelpMan{
         "settxfee",
         "Set the transaction fee per kB for this wallet. Overrides the "
         "global -paytxfee command line parameter.\n"
         "Can be deactivated by passing 0 as the fee. In that case automatic "
         "fee selection will be used by default.\n",
         {
             {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
              "The transaction fee in " + Currency::get().ticker + "/kB"},
         },
         RPCResult{RPCResult::Type::BOOL, "", "Returns true if successful"},
         RPCExamples{HelpExampleCli("settxfee", "0.00001") +
                     HelpExampleRpc("settxfee", "0.00001")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             Amount nAmount = AmountFromValue(request.params[0]);
             CFeeRate tx_fee_rate(nAmount, 1000);
             CFeeRate max_tx_fee_rate(pwallet->m_default_max_tx_fee, 1000);
             if (tx_fee_rate == CFeeRate()) {
                 // automatic selection
             } else if (tx_fee_rate < pwallet->chain().relayMinFee()) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     strprintf("txfee cannot be less than min relay tx fee (%s)",
                               pwallet->chain().relayMinFee().ToString()));
             } else if (tx_fee_rate < pwallet->m_min_fee) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     strprintf("txfee cannot be less than wallet min fee (%s)",
                               pwallet->m_min_fee.ToString()));
             } else if (tx_fee_rate > max_tx_fee_rate) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     strprintf(
                         "txfee cannot be more than wallet max tx fee (%s)",
                         max_tx_fee_rate.ToString()));
             }
 
             pwallet->m_pay_tx_fee = tx_fee_rate;
             return true;
         },
     };
 }
 
 static RPCHelpMan getbalances() {
     return RPCHelpMan{
         "getbalances",
         "Returns an object with all balances in " + Currency::get().ticker +
             ".\n",
         {},
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::OBJ,
                        "mine",
                        "balances from outputs that the wallet can sign",
                        {
                            {RPCResult::Type::STR_AMOUNT, "trusted",
                             "trusted balance (outputs created by the wallet or "
                             "confirmed outputs)"},
                            {RPCResult::Type::STR_AMOUNT, "untrusted_pending",
                             "untrusted pending balance (outputs created by "
                             "others that are in the mempool)"},
                            {RPCResult::Type::STR_AMOUNT, "immature",
                             "balance from immature coinbase outputs"},
                            {RPCResult::Type::STR_AMOUNT, "used",
                             "(only present if avoid_reuse is set) balance from "
                             "coins sent to addresses that were previously "
                             "spent from (potentially privacy violating)"},
                        }},
                       {RPCResult::Type::OBJ,
                        "watchonly",
                        "watchonly balances (not present if wallet does not "
                        "watch anything)",
                        {
                            {RPCResult::Type::STR_AMOUNT, "trusted",
                             "trusted balance (outputs created by the wallet or "
                             "confirmed outputs)"},
                            {RPCResult::Type::STR_AMOUNT, "untrusted_pending",
                             "untrusted pending balance (outputs created by "
                             "others that are in the mempool)"},
                            {RPCResult::Type::STR_AMOUNT, "immature",
                             "balance from immature coinbase outputs"},
                        }},
                   }},
         RPCExamples{HelpExampleCli("getbalances", "") +
                     HelpExampleRpc("getbalances", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const rpc_wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!rpc_wallet) {
                 return NullUniValue;
             }
             CWallet &wallet = *rpc_wallet;
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             wallet.BlockUntilSyncedToCurrentChain();
 
             LOCK(wallet.cs_wallet);
 
             const auto bal = GetBalance(wallet);
             UniValue balances{UniValue::VOBJ};
             {
                 UniValue balances_mine{UniValue::VOBJ};
                 balances_mine.pushKV("trusted", bal.m_mine_trusted);
                 balances_mine.pushKV("untrusted_pending",
                                      bal.m_mine_untrusted_pending);
                 balances_mine.pushKV("immature", bal.m_mine_immature);
                 if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
                     // If the AVOID_REUSE flag is set, bal has been set to just
                     // the un-reused address balance. Get the total balance, and
                     // then subtract bal to get the reused address balance.
                     const auto full_bal = GetBalance(wallet, 0, false);
                     balances_mine.pushKV("used",
                                          full_bal.m_mine_trusted +
                                              full_bal.m_mine_untrusted_pending -
                                              bal.m_mine_trusted -
                                              bal.m_mine_untrusted_pending);
                 }
                 balances.pushKV("mine", balances_mine);
             }
             auto spk_man = wallet.GetLegacyScriptPubKeyMan();
             if (spk_man && spk_man->HaveWatchOnly()) {
                 UniValue balances_watchonly{UniValue::VOBJ};
                 balances_watchonly.pushKV("trusted", bal.m_watchonly_trusted);
                 balances_watchonly.pushKV("untrusted_pending",
                                           bal.m_watchonly_untrusted_pending);
                 balances_watchonly.pushKV("immature", bal.m_watchonly_immature);
                 balances.pushKV("watchonly", balances_watchonly);
             }
             return balances;
         },
     };
 }
 
 static RPCHelpMan getwalletinfo() {
     return RPCHelpMan{
         "getwalletinfo",
         "Returns an object containing various wallet state info.\n",
         {},
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {{
                 {RPCResult::Type::STR, "walletname", "the wallet name"},
                 {RPCResult::Type::NUM, "walletversion", "the wallet version"},
                 {RPCResult::Type::STR_AMOUNT, "balance",
                  "DEPRECATED. Identical to getbalances().mine.trusted"},
                 {RPCResult::Type::STR_AMOUNT, "unconfirmed_balance",
                  "DEPRECATED. Identical to "
                  "getbalances().mine.untrusted_pending"},
                 {RPCResult::Type::STR_AMOUNT, "immature_balance",
                  "DEPRECATED. Identical to getbalances().mine.immature"},
                 {RPCResult::Type::NUM, "txcount",
                  "the total number of transactions in the wallet"},
                 {RPCResult::Type::NUM_TIME, "keypoololdest",
                  "the " + UNIX_EPOCH_TIME +
                      " of the oldest pre-generated key in the key pool. "
                      "Legacy wallets only."},
                 {RPCResult::Type::NUM, "keypoolsize",
                  "how many new keys are pre-generated (only counts external "
                  "keys)"},
                 {RPCResult::Type::NUM, "keypoolsize_hd_internal",
                  "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)"},
                 {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)"},
                 {RPCResult::Type::STR_AMOUNT, "paytxfee",
                  "the transaction fee configuration, set in " +
                      Currency::get().ticker + "/kB"},
                 {RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true,
                  "the Hash160 of the HD seed (only present when HD is "
                  "enabled)"},
                 {RPCResult::Type::BOOL, "private_keys_enabled",
                  "false if privatekeys are disabled for this wallet (enforced "
                  "watch-only wallet)"},
                 {RPCResult::Type::OBJ,
                  "scanning",
                  "current scanning details, or false if no scan is in "
                  "progress",
                  {
                      {RPCResult::Type::NUM, "duration",
                       "elapsed seconds since scan start"},
                      {RPCResult::Type::NUM, "progress",
                       "scanning progress percentage [0.0, 1.0]"},
                  },
                  /*skip_type_check=*/true},
                 {RPCResult::Type::BOOL, "avoid_reuse",
                  "whether this wallet tracks clean/dirty coins in terms of "
                  "reuse"},
                 {RPCResult::Type::BOOL, "descriptors",
                  "whether this wallet uses descriptors for scriptPubKey "
                  "management"},
             }},
         },
         RPCExamples{HelpExampleCli("getwalletinfo", "") +
                     HelpExampleRpc("getwalletinfo", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             UniValue obj(UniValue::VOBJ);
 
             size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
             const auto bal = GetBalance(*pwallet);
             int64_t kp_oldest = pwallet->GetOldestKeyPoolTime();
             obj.pushKV("walletname", pwallet->GetName());
             obj.pushKV("walletversion", pwallet->GetVersion());
             obj.pushKV("balance", bal.m_mine_trusted);
             obj.pushKV("unconfirmed_balance", bal.m_mine_untrusted_pending);
             obj.pushKV("immature_balance", bal.m_mine_immature);
             obj.pushKV("txcount", (int)pwallet->mapWallet.size());
             if (kp_oldest > 0) {
                 obj.pushKV("keypoololdest", kp_oldest);
             }
             obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
 
             LegacyScriptPubKeyMan *spk_man =
                 pwallet->GetLegacyScriptPubKeyMan();
             if (spk_man) {
                 CKeyID seed_id = spk_man->GetHDChain().seed_id;
                 if (!seed_id.IsNull()) {
                     obj.pushKV("hdseedid", seed_id.GetHex());
                 }
             }
 
             if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
                 obj.pushKV("keypoolsize_hd_internal",
                            int64_t(pwallet->GetKeyPoolSize() - kpExternalSize));
             }
             if (pwallet->IsCrypted()) {
                 obj.pushKV("unlocked_until", pwallet->nRelockTime);
             }
             obj.pushKV("paytxfee", pwallet->m_pay_tx_fee.GetFeePerK());
             obj.pushKV(
                 "private_keys_enabled",
                 !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
             if (pwallet->IsScanning()) {
                 UniValue scanning(UniValue::VOBJ);
                 scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
                 scanning.pushKV("progress", pwallet->ScanningProgress());
                 obj.pushKV("scanning", scanning);
             } else {
                 obj.pushKV("scanning", false);
             }
             obj.pushKV("avoid_reuse",
                        pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
             obj.pushKV("descriptors",
                        pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
             return obj;
         },
     };
 }
 
 static RPCHelpMan listwalletdir() {
     return RPCHelpMan{
         "listwalletdir",
         "Returns a list of wallets in the wallet directory.\n",
         {},
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::ARR,
                  "wallets",
                  "",
                  {
                      {RPCResult::Type::OBJ,
                       "",
                       "",
                       {
                           {RPCResult::Type::STR, "name", "The wallet name"},
                       }},
                  }},
             }},
         RPCExamples{HelpExampleCli("listwalletdir", "") +
                     HelpExampleRpc("listwalletdir", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             UniValue wallets(UniValue::VARR);
             for (const auto &path : ListWalletDir()) {
                 UniValue wallet(UniValue::VOBJ);
                 wallet.pushKV("name", path.u8string());
                 wallets.push_back(wallet);
             }
 
             UniValue result(UniValue::VOBJ);
             result.pushKV("wallets", wallets);
             return result;
         },
     };
 }
 
 static RPCHelpMan listwallets() {
     return RPCHelpMan{
         "listwallets",
         "Returns a list of currently loaded wallets.\n"
         "For full information on the wallet, use \"getwalletinfo\"\n",
         {},
         RPCResult{RPCResult::Type::ARR,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "walletname", "the wallet name"},
                   }},
         RPCExamples{HelpExampleCli("listwallets", "") +
                     HelpExampleRpc("listwallets", "")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             UniValue obj(UniValue::VARR);
 
             for (const std::shared_ptr<CWallet> &wallet : GetWallets()) {
                 LOCK(wallet->cs_wallet);
                 obj.push_back(wallet->GetName());
             }
 
             return obj;
         },
     };
 }
 
 static RPCHelpMan loadwallet() {
     return RPCHelpMan{
         "loadwallet",
         "Loads a wallet from a wallet file or directory."
         "\nNote that all wallet command-line options used when starting "
         "bitcoind will be"
         "\napplied to the new wallet (eg -rescan, etc).\n",
         {
             {"filename", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The wallet directory or .dat file."},
             {"load_on_startup", RPCArg::Type::BOOL,
              RPCArg::Default{UniValue::VNULL},
              "Save wallet name to persistent settings and load on startup. "
              "True to add wallet to startup list, false to remove, null to "
              "leave unchanged."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "name",
                        "The wallet name if loaded successfully."},
                       {RPCResult::Type::STR, "warning",
                        "Warning message if wallet was not loaded cleanly."},
                   }},
         RPCExamples{HelpExampleCli("loadwallet", "\"test.dat\"") +
                     HelpExampleRpc("loadwallet", "\"test.dat\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             WalletContext &context = EnsureWalletContext(request.context);
             const std::string name(request.params[0].get_str());
 
             auto [wallet, warnings] =
                 LoadWalletHelper(context, request.params[1], name);
 
             UniValue obj(UniValue::VOBJ);
             obj.pushKV("name", wallet->GetName());
             obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
 
             return obj;
         },
     };
 }
 
 static RPCHelpMan setwalletflag() {
     std::string flags = "";
     for (auto &it : WALLET_FLAG_MAP) {
         if (it.second & MUTABLE_WALLET_FLAGS) {
             flags += (flags == "" ? "" : ", ") + it.first;
         }
     }
     return RPCHelpMan{
         "setwalletflag",
         "Change the state of the given wallet flag for a wallet.\n",
         {
             {"flag", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The name of the flag to change. Current available flags: " +
                  flags},
             {"value", RPCArg::Type::BOOL, RPCArg::Default{true},
              "The new state."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "flag_name",
                        "The name of the flag that was modified"},
                       {RPCResult::Type::BOOL, "flag_state",
                        "The new state of the flag"},
                       {RPCResult::Type::STR, "warnings",
                        "Any warnings associated with the change"},
                   }},
         RPCExamples{HelpExampleCli("setwalletflag", "avoid_reuse") +
                     HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             std::string flag_str = request.params[0].get_str();
             bool value =
                 request.params[1].isNull() || request.params[1].get_bool();
 
             if (!WALLET_FLAG_MAP.count(flag_str)) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     strprintf("Unknown wallet flag: %s", flag_str));
             }
 
             auto flag = WALLET_FLAG_MAP.at(flag_str);
 
             if (!(flag & MUTABLE_WALLET_FLAGS)) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     strprintf("Wallet flag is immutable: %s", flag_str));
             }
 
             UniValue res(UniValue::VOBJ);
 
             if (pwallet->IsWalletFlagSet(flag) == value) {
                 throw JSONRPCError(
                     RPC_INVALID_PARAMETER,
                     strprintf("Wallet flag is already set to %s: %s",
                               value ? "true" : "false", flag_str));
             }
 
             res.pushKV("flag_name", flag_str);
             res.pushKV("flag_state", value);
 
             if (value) {
                 pwallet->SetWalletFlag(flag);
             } else {
                 pwallet->UnsetWalletFlag(flag);
             }
 
             if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
                 res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
             }
 
             return res;
         },
     };
 }
 
 static RPCHelpMan createwallet() {
     return RPCHelpMan{
         "createwallet",
         "Creates and loads a new wallet.\n",
         {
             {"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."},
             {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false},
              "Disable the possibility of private keys (only watchonlys are "
              "possible in this mode)."},
             {"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."},
             {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
              "Encrypt the wallet with this passphrase."},
             {"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."},
             {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{false},
              "Create a native descriptor wallet. The wallet will use "
              "descriptors internally to handle address creation"},
             {"load_on_startup", RPCArg::Type::BOOL,
              RPCArg::Default{UniValue::VNULL},
              "Save wallet name to persistent settings and load on startup. "
              "True to add wallet to startup list, false to remove, null to "
              "leave unchanged."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {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."},
                       {RPCResult::Type::STR, "warning",
                        "Warning message if wallet was not loaded cleanly."},
                   }},
         RPCExamples{
             HelpExampleCli("createwallet", "\"testwallet\"") +
             HelpExampleRpc("createwallet", "\"testwallet\"") +
             HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"},
                                                  {"avoid_reuse", true},
                                                  {"descriptors", true},
                                                  {"load_on_startup", true}}) +
             HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"},
                                                  {"avoid_reuse", true},
                                                  {"descriptors", true},
                                                  {"load_on_startup", true}})},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             WalletContext &context = EnsureWalletContext(request.context);
             uint64_t flags = 0;
             if (!request.params[1].isNull() && request.params[1].get_bool()) {
                 flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
             }
 
             if (!request.params[2].isNull() && request.params[2].get_bool()) {
                 flags |= WALLET_FLAG_BLANK_WALLET;
             }
 
             SecureString passphrase;
             passphrase.reserve(100);
             std::vector<bilingual_str> warnings;
             if (!request.params[3].isNull()) {
                 passphrase = request.params[3].get_str().c_str();
                 if (passphrase.empty()) {
                     // Empty string means unencrypted
                     warnings.emplace_back(Untranslated(
                         "Empty string given as passphrase, wallet will "
                         "not be encrypted."));
                 }
             }
 
             if (!request.params[4].isNull() && request.params[4].get_bool()) {
                 flags |= WALLET_FLAG_AVOID_REUSE;
             }
             if (!request.params[5].isNull() && request.params[5].get_bool()) {
                 flags |= WALLET_FLAG_DESCRIPTORS;
                 warnings.emplace_back(Untranslated(
                     "Wallet is an experimental descriptor wallet"));
             }
 
             DatabaseOptions options;
             DatabaseStatus status;
             options.require_create = true;
             options.create_flags = flags;
             options.create_passphrase = passphrase;
             bilingual_str error;
             std::optional<bool> load_on_start =
                 request.params[6].isNull()
                     ? std::nullopt
                     : std::make_optional<bool>(request.params[6].get_bool());
             std::shared_ptr<CWallet> wallet =
                 CreateWallet(*context.chain, request.params[0].get_str(),
                              load_on_start, options, status, error, warnings);
             if (!wallet) {
                 RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT
                                         ? RPC_WALLET_ENCRYPTION_FAILED
                                         : RPC_WALLET_ERROR;
                 throw JSONRPCError(code, error.original);
             }
 
             UniValue obj(UniValue::VOBJ);
             obj.pushKV("name", wallet->GetName());
             obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
 
             return obj;
         },
     };
 }
 
 static RPCHelpMan unloadwallet() {
     return RPCHelpMan{
         "unloadwallet",
         "Unloads the wallet referenced by the request endpoint otherwise "
         "unloads the wallet specified in the argument.\n"
         "Specifying the wallet name on a wallet endpoint is invalid.",
         {
             {"wallet_name", RPCArg::Type::STR,
              RPCArg::DefaultHint{"the wallet name from the RPC request"},
              "The name of the wallet to unload."},
             {"load_on_startup", RPCArg::Type::BOOL,
              RPCArg::Default{UniValue::VNULL},
              "Save wallet name to persistent settings and load on startup. "
              "True to add wallet to startup list, false to remove, null to "
              "leave unchanged."},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "warning",
                        "Warning message if wallet was not unloaded cleanly."},
                   }},
         RPCExamples{HelpExampleCli("unloadwallet", "wallet_name") +
                     HelpExampleRpc("unloadwallet", "wallet_name")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::string wallet_name;
             if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
                 if (!request.params[0].isNull()) {
                     throw JSONRPCError(RPC_INVALID_PARAMETER,
                                        "Cannot unload the requested wallet");
                 }
             } else {
                 wallet_name = request.params[0].get_str();
             }
 
             std::shared_ptr<CWallet> wallet = GetWallet(wallet_name);
             if (!wallet) {
                 throw JSONRPCError(
                     RPC_WALLET_NOT_FOUND,
                     "Requested wallet does not exist or is not loaded");
             }
 
             // Release the "main" shared pointer and prevent further
             // notifications. Note that any attempt to load the same wallet
             // would fail until the wallet is destroyed (see CheckUniqueFileid).
             std::vector<bilingual_str> warnings;
             std::optional<bool> load_on_start =
                 request.params[1].isNull()
                     ? std::nullopt
                     : std::make_optional<bool>(request.params[1].get_bool());
             if (!RemoveWallet(wallet, load_on_start, warnings)) {
                 throw JSONRPCError(RPC_MISC_ERROR,
                                    "Requested wallet already unloaded");
             }
 
             UnloadWallet(std::move(wallet));
 
             UniValue result(UniValue::VOBJ);
             result.pushKV("warning",
                           Join(warnings, Untranslated("\n")).original);
             return result;
         },
     };
 }
 
 static RPCHelpMan listunspent() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "listunspent",
         "Returns array of unspent transaction outputs\n"
         "with between minconf and maxconf (inclusive) confirmations.\n"
         "Optionally filter to only include txouts paid to specified "
         "addresses.\n",
         {
             {"minconf", RPCArg::Type::NUM, RPCArg::Default{1},
              "The minimum confirmations to filter"},
             {"maxconf", RPCArg::Type::NUM, RPCArg::Default{9999999},
              "The maximum confirmations to filter"},
             {
                 "addresses",
                 RPCArg::Type::ARR,
                 RPCArg::Default{UniValue::VARR},
                 "The bitcoin addresses to filter",
                 {
                     {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                      "bitcoin address"},
                 },
             },
             {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{true},
              "Include outputs that are not safe to spend\n"
              "                  See description of \"safe\" attribute below."},
             {"query_options",
              RPCArg::Type::OBJ,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "JSON with query options",
              {
                  {"minimumAmount", RPCArg::Type::AMOUNT,
                   RPCArg::Default{FormatMoney(Amount::zero())},
                   "Minimum value of each UTXO in " + ticker + ""},
                  {"maximumAmount", RPCArg::Type::AMOUNT,
                   RPCArg::DefaultHint{"unlimited"},
                   "Maximum value of each UTXO in " + ticker + ""},
                  {"maximumCount", RPCArg::Type::NUM,
                   RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"},
                  {"minimumSumAmount", RPCArg::Type::AMOUNT,
                   RPCArg::DefaultHint{"unlimited"},
                   "Minimum sum value of all UTXOs in " + ticker + ""},
              },
              RPCArgOptions{.oneline_description = "query_options"}},
         },
         RPCResult{
             RPCResult::Type::ARR,
             "",
             "",
             {
                 {RPCResult::Type::OBJ,
                  "",
                  "",
                  {
                      {RPCResult::Type::STR_HEX, "txid", "the transaction id"},
                      {RPCResult::Type::NUM, "vout", "the vout value"},
                      {RPCResult::Type::STR, "address", "the bitcoin address"},
                      {RPCResult::Type::STR, "label",
                       "The associated label, or \"\" for the default label"},
                      {RPCResult::Type::STR, "scriptPubKey", "the script key"},
                      {RPCResult::Type::STR_AMOUNT, "amount",
                       "the transaction output amount in " + ticker},
                      {RPCResult::Type::NUM, "confirmations",
                       "The number of confirmations"},
                      {RPCResult::Type::NUM, "ancestorcount",
                       /* optional */ true,
                       "DEPRECATED: The number of in-mempool ancestor "
                       "transactions, including this one (if transaction is in "
                       "the mempool). Only displayed if the "
                       "-deprecatedrpc=mempool_ancestors_descendants option is "
                       "set"},
                      {RPCResult::Type::NUM, "ancestorsize", /* optional */ true,
                       "DEPRECATED: The virtual transaction size of in-mempool "
                       " ancestors, including this one (if transaction is in "
                       "the mempool). Only displayed if the "
                       "-deprecatedrpc=mempool_ancestors_descendants option is "
                       "set"},
                      {RPCResult::Type::STR_AMOUNT, "ancestorfees",
                       /* optional */ true,
                       "DEPRECATED: The total fees of in-mempool ancestors "
                       "(including this one) with fee deltas used for mining "
                       "priority in " +
                           ticker +
                           " (if transaction is in the mempool). Only "
                           "displayed if the "
                           "-deprecatedrpc=mempool_ancestors_descendants option "
                           "is "
                           "set"},
                      {RPCResult::Type::STR_HEX, "redeemScript",
                       "The redeemScript if scriptPubKey is P2SH"},
                      {RPCResult::Type::BOOL, "spendable",
                       "Whether we have the private keys to spend this output"},
                      {RPCResult::Type::BOOL, "solvable",
                       "Whether we know how to spend this output, ignoring the "
                       "lack of keys"},
                      {RPCResult::Type::BOOL, "reused",
                       "(only present if avoid_reuse is set) Whether this "
                       "output is reused/dirty (sent to an address that was "
                       "previously spent from)"},
                      {RPCResult::Type::STR, "desc",
                       "(only when solvable) A descriptor for spending this "
                       "output"},
                      {RPCResult::Type::BOOL, "safe",
                       "Whether this output is considered safe to spend. "
                       "Unconfirmed transactions\n"
                       "from outside keys are considered unsafe\n"
                       "and are not eligible for spending by fundrawtransaction "
                       "and sendtoaddress."},
                  }},
             }},
         RPCExamples{
             HelpExampleCli("listunspent", "") +
             HelpExampleCli("listunspent",
                            "6 9999999 "
                            "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\","
                            "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") +
             HelpExampleRpc("listunspent",
                            "6, 9999999 "
                            "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\","
                            "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") +
             HelpExampleCli(
                 "listunspent",
                 "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") +
             HelpExampleRpc(
                 "listunspent",
                 "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             int nMinDepth = 1;
             if (!request.params[0].isNull()) {
                 RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
                 nMinDepth = request.params[0].getInt<int>();
             }
 
             int nMaxDepth = 9999999;
             if (!request.params[1].isNull()) {
                 RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
                 nMaxDepth = request.params[1].getInt<int>();
             }
 
             std::set<CTxDestination> destinations;
             if (!request.params[2].isNull()) {
                 RPCTypeCheckArgument(request.params[2], UniValue::VARR);
                 UniValue inputs = request.params[2].get_array();
                 for (size_t idx = 0; idx < inputs.size(); idx++) {
                     const UniValue &input = inputs[idx];
                     CTxDestination dest = DecodeDestination(
                         input.get_str(), wallet->GetChainParams());
                     if (!IsValidDestination(dest)) {
                         throw JSONRPCError(
                             RPC_INVALID_ADDRESS_OR_KEY,
                             std::string("Invalid Bitcoin address: ") +
                                 input.get_str());
                     }
                     if (!destinations.insert(dest).second) {
                         throw JSONRPCError(
                             RPC_INVALID_PARAMETER,
                             std::string(
                                 "Invalid parameter, duplicated address: ") +
                                 input.get_str());
                     }
                 }
             }
 
             bool include_unsafe = true;
             if (!request.params[3].isNull()) {
                 RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
                 include_unsafe = request.params[3].get_bool();
             }
 
             Amount nMinimumAmount = Amount::zero();
             Amount nMaximumAmount = MAX_MONEY;
             Amount nMinimumSumAmount = MAX_MONEY;
             uint64_t nMaximumCount = 0;
 
             if (!request.params[4].isNull()) {
                 const UniValue &options = request.params[4].get_obj();
 
                 RPCTypeCheckObj(
                     options,
                     {
                         {"minimumAmount", UniValueType()},
                         {"maximumAmount", UniValueType()},
                         {"minimumSumAmount", UniValueType()},
                         {"maximumCount", UniValueType(UniValue::VNUM)},
                     },
                     true, true);
 
                 if (options.exists("minimumAmount")) {
                     nMinimumAmount = AmountFromValue(options["minimumAmount"]);
                 }
 
                 if (options.exists("maximumAmount")) {
                     nMaximumAmount = AmountFromValue(options["maximumAmount"]);
                 }
 
                 if (options.exists("minimumSumAmount")) {
                     nMinimumSumAmount =
                         AmountFromValue(options["minimumSumAmount"]);
                 }
 
                 if (options.exists("maximumCount")) {
                     nMaximumCount = options["maximumCount"].getInt<int64_t>();
                 }
             }
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             UniValue results(UniValue::VARR);
             std::vector<COutput> vecOutputs;
             {
                 CCoinControl cctl;
                 cctl.m_avoid_address_reuse = false;
                 cctl.m_min_depth = nMinDepth;
                 cctl.m_max_depth = nMaxDepth;
                 cctl.m_include_unsafe_inputs = include_unsafe;
                 LOCK(pwallet->cs_wallet);
                 AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount,
                                nMaximumAmount, nMinimumSumAmount,
                                nMaximumCount);
             }
 
             LOCK(pwallet->cs_wallet);
 
             const bool avoid_reuse =
                 pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
 
             for (const COutput &out : vecOutputs) {
                 CTxDestination address;
                 const CScript &scriptPubKey =
                     out.tx->tx->vout[out.i].scriptPubKey;
                 bool fValidAddress = ExtractDestination(scriptPubKey, address);
                 bool reused =
                     avoid_reuse && pwallet->IsSpentKey(out.tx->GetId(), out.i);
 
                 if (destinations.size() &&
                     (!fValidAddress || !destinations.count(address))) {
                     continue;
                 }
 
                 UniValue entry(UniValue::VOBJ);
                 entry.pushKV("txid", out.tx->GetId().GetHex());
                 entry.pushKV("vout", out.i);
 
                 if (fValidAddress) {
                     entry.pushKV("address", EncodeDestination(address, config));
 
                     const auto *address_book_entry =
                         pwallet->FindAddressBookEntry(address);
                     if (address_book_entry) {
                         entry.pushKV("label", address_book_entry->GetLabel());
                     }
 
                     std::unique_ptr<SigningProvider> provider =
                         pwallet->GetSolvingProvider(scriptPubKey);
                     if (provider) {
                         if (scriptPubKey.IsPayToScriptHash()) {
                             const CScriptID &hash =
                                 CScriptID(std::get<ScriptHash>(address));
                             CScript redeemScript;
                             if (provider->GetCScript(hash, redeemScript)) {
                                 entry.pushKV("redeemScript",
                                              HexStr(redeemScript));
                             }
                         }
                     }
                 }
 
                 entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
                 entry.pushKV("amount", out.tx->tx->vout[out.i].nValue);
                 entry.pushKV("confirmations", out.nDepth);
                 entry.pushKV("spendable", out.fSpendable);
                 entry.pushKV("solvable", out.fSolvable);
                 if (out.fSolvable) {
                     std::unique_ptr<SigningProvider> provider =
                         pwallet->GetSolvingProvider(scriptPubKey);
                     if (provider) {
                         auto descriptor =
                             InferDescriptor(scriptPubKey, *provider);
                         entry.pushKV("desc", descriptor->ToString());
                     }
                 }
                 if (avoid_reuse) {
                     entry.pushKV("reused", reused);
                 }
                 entry.pushKV("safe", out.fSafe);
                 results.push_back(entry);
             }
 
             return results;
         },
     };
 }
 
 void FundTransaction(CWallet *const pwallet, CMutableTransaction &tx,
                      Amount &fee_out, int &change_position,
                      const UniValue &options, CCoinControl &coinControl) {
     // Make sure the results are valid at least up to the most recent block
     // the user could have gotten from another RPC command prior to now
     pwallet->BlockUntilSyncedToCurrentChain();
 
     change_position = -1;
     bool lockUnspents = false;
     UniValue subtractFeeFromOutputs;
     std::set<int> setSubtractFeeFromOutputs;
 
     if (!options.isNull()) {
         if (options.type() == UniValue::VBOOL) {
             // backward compatibility bool only fallback
             coinControl.fAllowWatchOnly = options.get_bool();
         } else {
             RPCTypeCheckArgument(options, UniValue::VOBJ);
             RPCTypeCheckObj(
                 options,
                 {
                     {"add_inputs", UniValueType(UniValue::VBOOL)},
                     {"include_unsafe", UniValueType(UniValue::VBOOL)},
                     {"add_to_wallet", UniValueType(UniValue::VBOOL)},
                     {"changeAddress", UniValueType(UniValue::VSTR)},
                     {"change_address", UniValueType(UniValue::VSTR)},
                     {"changePosition", UniValueType(UniValue::VNUM)},
                     {"change_position", UniValueType(UniValue::VNUM)},
                     {"includeWatching", UniValueType(UniValue::VBOOL)},
                     {"include_watching", UniValueType(UniValue::VBOOL)},
                     {"inputs", UniValueType(UniValue::VARR)},
                     {"lockUnspents", UniValueType(UniValue::VBOOL)},
                     {"lock_unspents", UniValueType(UniValue::VBOOL)},
                     {"locktime", UniValueType(UniValue::VNUM)},
                     // will be checked below
                     {"feeRate", UniValueType()},
                     {"fee_rate", UniValueType()},
                     {"psbt", UniValueType(UniValue::VBOOL)},
                     {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
                     {"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
                 },
                 true, true);
 
             if (options.exists("add_inputs")) {
                 coinControl.m_add_inputs = options["add_inputs"].get_bool();
             }
 
             if (options.exists("changeAddress") ||
                 options.exists("change_address")) {
                 const std::string change_address_str =
                     (options.exists("change_address")
                          ? options["change_address"]
                          : options["changeAddress"])
                         .get_str();
                 CTxDestination dest = DecodeDestination(
                     change_address_str, pwallet->GetChainParams());
 
                 if (!IsValidDestination(dest)) {
                     throw JSONRPCError(
                         RPC_INVALID_ADDRESS_OR_KEY,
                         "Change address must be a valid bitcoin address");
                 }
 
                 coinControl.destChange = dest;
             }
 
             if (options.exists("changePosition") ||
                 options.exists("change_position")) {
                 change_position = (options.exists("change_position")
                                        ? options["change_position"]
                                        : options["changePosition"])
                                       .getInt<int>();
             }
 
             const UniValue include_watching_option =
                 options.exists("include_watching") ? options["include_watching"]
                                                    : options["includeWatching"];
             coinControl.fAllowWatchOnly =
                 ParseIncludeWatchonly(include_watching_option, *pwallet);
 
             if (options.exists("lockUnspents") ||
                 options.exists("lock_unspents")) {
                 lockUnspents =
                     (options.exists("lock_unspents") ? options["lock_unspents"]
                                                      : options["lockUnspents"])
                         .get_bool();
             }
 
             if (options.exists("include_unsafe")) {
                 coinControl.m_include_unsafe_inputs =
                     options["include_unsafe"].get_bool();
             }
 
             if (options.exists("feeRate") || options.exists("fee_rate")) {
                 coinControl.m_feerate = CFeeRate(AmountFromValue(
                     options.exists("fee_rate") ? options["fee_rate"]
                                                : options["feeRate"]));
                 coinControl.fOverrideFeeRate = true;
             }
 
             if (options.exists("subtractFeeFromOutputs") ||
                 options.exists("subtract_fee_from_outputs")) {
                 subtractFeeFromOutputs =
                     (options.exists("subtract_fee_from_outputs")
                          ? options["subtract_fee_from_outputs"]
                          : options["subtractFeeFromOutputs"])
                         .get_array();
             }
         }
     } else {
         // if options is null and not a bool
         coinControl.fAllowWatchOnly =
             ParseIncludeWatchonly(NullUniValue, *pwallet);
     }
 
     if (tx.vout.size() == 0) {
         throw JSONRPCError(RPC_INVALID_PARAMETER,
                            "TX must have at least one output");
     }
 
     if (change_position != -1 &&
         (change_position < 0 ||
          (unsigned int)change_position > tx.vout.size())) {
         throw JSONRPCError(RPC_INVALID_PARAMETER,
                            "changePosition out of bounds");
     }
 
     for (size_t idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
         int pos = subtractFeeFromOutputs[idx].getInt<int>();
         if (setSubtractFeeFromOutputs.count(pos)) {
             throw JSONRPCError(
                 RPC_INVALID_PARAMETER,
                 strprintf("Invalid parameter, duplicated position: %d", pos));
         }
         if (pos < 0) {
             throw JSONRPCError(
                 RPC_INVALID_PARAMETER,
                 strprintf("Invalid parameter, negative position: %d", pos));
         }
         if (pos >= int(tx.vout.size())) {
             throw JSONRPCError(
                 RPC_INVALID_PARAMETER,
                 strprintf("Invalid parameter, position too large: %d", pos));
         }
         setSubtractFeeFromOutputs.insert(pos);
     }
 
     bilingual_str error;
 
     if (!FundTransaction(*pwallet, tx, fee_out, change_position, error,
                          lockUnspents, setSubtractFeeFromOutputs,
                          coinControl)) {
         throw JSONRPCError(RPC_WALLET_ERROR, error.original);
     }
 }
 
 static RPCHelpMan fundrawtransaction() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "fundrawtransaction",
         "If the transaction has no inputs, they will be automatically selected "
         "to meet its out value.\n"
         "It will add at most one change output to the outputs.\n"
         "No existing outputs will be modified unless "
         "\"subtractFeeFromOutputs\" is specified.\n"
         "Note that inputs which were signed may need to be resigned after "
         "completion since in/outputs have been added.\n"
         "The inputs added will not be signed, use signrawtransactionwithkey or "
         "signrawtransactionwithwallet for that.\n"
         "Note that all existing inputs must have their previous output "
         "transaction be in the wallet.\n"
         "Note that all inputs selected must be of standard form and P2SH "
         "scripts must be\n"
         "in the wallet using importaddress or addmultisigaddress (to calculate "
         "fees).\n"
         "You can see whether this is the case by checking the \"solvable\" "
         "field in the listunspent output.\n"
         "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently "
         "supported for watch-only\n",
         {
             {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
              "The hex string of the raw transaction"},
             {"options",
              RPCArg::Type::OBJ,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "for backward compatibility: passing in a true instead of an "
              "object will result in {\"includeWatching\":true}",
              {
                  {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{true},
                   "For a transaction with existing inputs, automatically "
                   "include more if they are not enough."},
                  {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "Include inputs that are not safe to spend (unconfirmed "
                   "transactions from outside keys).\n"
                   "Warning: the resulting transaction may become invalid if "
                   "one of the unsafe inputs disappears.\n"
                   "If that happens, you will need to fund the transaction with "
                   "different inputs and republish it."},
                  {"changeAddress", RPCArg::Type::STR,
                   RPCArg::DefaultHint{"pool address"},
                   "The bitcoin address to receive the change"},
                  {"changePosition", RPCArg::Type::NUM,
                   RPCArg::DefaultHint{"random"},
                   "The index of the change output"},
                  {"includeWatching", RPCArg::Type::BOOL,
                   RPCArg::DefaultHint{
                       "true for watch-only wallets, otherwise false"},
                   "Also select inputs which are watch only.\n"
                   "Only solvable inputs can be used. Watch-only destinations "
                   "are solvable if the public key and/or output script was "
                   "imported,\n"
                   "e.g. with 'importpubkey' or 'importmulti' with the "
                   "'pubkeys' or 'desc' field."},
                  {"lockUnspents", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "Lock selected unspent outputs"},
                  {"feeRate", RPCArg::Type::AMOUNT,
                   RPCArg::DefaultHint{
                       "not set: makes wallet determine the fee"},
                   "Set a specific fee rate in " + ticker + "/kB"},
                  {
                      "subtractFeeFromOutputs",
                      RPCArg::Type::ARR,
                      RPCArg::Default{UniValue::VARR},
                      "The integers.\n"
                      "                              The fee will be equally "
                      "deducted from the amount of each specified output.\n"
                      "                              Those recipients will "
                      "receive less bitcoins than you enter in their "
                      "corresponding amount field.\n"
                      "                              If no outputs are "
                      "specified here, the sender pays the fee.",
                      {
                          {"vout_index", RPCArg::Type::NUM,
                           RPCArg::Optional::OMITTED,
                           "The zero-based output index, before a change output "
                           "is added."},
                      },
                  },
              },
              RPCArgOptions{.skip_type_check = true,
                            .oneline_description = "options"}},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR_HEX, "hex",
                        "The resulting raw transaction (hex-encoded string)"},
                       {RPCResult::Type::STR_AMOUNT, "fee",
                        "Fee in " + ticker + " the resulting transaction pays"},
                       {RPCResult::Type::NUM, "changepos",
                        "The position of the added change output, or -1"},
                   }},
         RPCExamples{
             "\nCreate a transaction with no inputs\n" +
             HelpExampleCli("createrawtransaction",
                            "\"[]\" \"{\\\"myaddress\\\":10000}\"") +
             "\nAdd sufficient unsigned inputs to meet the output value\n" +
             HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") +
             "\nSign the transaction\n" +
             HelpExampleCli("signrawtransactionwithwallet",
                            "\"fundedtransactionhex\"") +
             "\nSend the transaction\n" +
             HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // parse hex string from parameter
             CMutableTransaction tx;
             if (!DecodeHexTx(tx, request.params[0].get_str())) {
                 throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
                                    "TX decode failed");
             }
 
             Amount fee;
             int change_position;
             CCoinControl coin_control;
             // Automatically select (additional) coins. Can be overridden by
             // options.add_inputs.
             coin_control.m_add_inputs = true;
             FundTransaction(pwallet, tx, fee, change_position,
                             request.params[1], coin_control);
 
             UniValue result(UniValue::VOBJ);
             result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
             result.pushKV("fee", fee);
             result.pushKV("changepos", change_position);
 
             return result;
         },
     };
 }
 
 RPCHelpMan signrawtransactionwithwallet() {
     return RPCHelpMan{
         "signrawtransactionwithwallet",
         "Sign inputs for raw transaction (serialized, hex-encoded).\n"
         "The second optional argument (may be null) is an array of previous "
         "transaction outputs that\n"
         "this transaction depends on but may not yet be in the block chain.\n" +
             HELP_REQUIRING_PASSPHRASE,
         {
             {"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The transaction hex string"},
             {
                 "prevtxs",
                 RPCArg::Type::ARR,
                 RPCArg::Optional::OMITTED_NAMED_ARG,
                 "The previous dependent transaction outputs",
                 {
                     {
                         "",
                         RPCArg::Type::OBJ,
                         RPCArg::Optional::OMITTED,
                         "",
                         {
                             {"txid", RPCArg::Type::STR_HEX,
                              RPCArg::Optional::NO, "The transaction id"},
                             {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
                              "The output number"},
                             {"scriptPubKey", RPCArg::Type::STR_HEX,
                              RPCArg::Optional::NO, "script key"},
                             {"redeemScript", RPCArg::Type::STR_HEX,
                              RPCArg::Optional::OMITTED, "(required for P2SH)"},
                             {"amount", RPCArg::Type::AMOUNT,
                              RPCArg::Optional::NO, "The amount spent"},
                         },
                     },
                 },
             },
             {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL|FORKID"},
              "The signature hash type. Must be one of\n"
              "       \"ALL|FORKID\"\n"
              "       \"NONE|FORKID\"\n"
              "       \"SINGLE|FORKID\"\n"
              "       \"ALL|FORKID|ANYONECANPAY\"\n"
              "       \"NONE|FORKID|ANYONECANPAY\"\n"
              "       \"SINGLE|FORKID|ANYONECANPAY\""},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::STR_HEX, "hex",
                  "The hex-encoded raw transaction with signature(s)"},
                 {RPCResult::Type::BOOL, "complete",
                  "If the transaction has a complete set of signatures"},
                 {RPCResult::Type::ARR,
                  "errors",
                  /* optional */ true,
                  "Script verification errors (if there are any)",
                  {
                      {RPCResult::Type::OBJ,
                       "",
                       "",
                       {
                           {RPCResult::Type::STR_HEX, "txid",
                            "The hash of the referenced, previous transaction"},
                           {RPCResult::Type::NUM, "vout",
                            "The index of the output to spent and used as "
                            "input"},
                           {RPCResult::Type::STR_HEX, "scriptSig",
                            "The hex-encoded signature script"},
                           {RPCResult::Type::NUM, "sequence",
                            "Script sequence number"},
                           {RPCResult::Type::STR, "error",
                            "Verification or signing error related to the "
                            "input"},
                       }},
                  }},
             }},
         RPCExamples{
             HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
             HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             CMutableTransaction mtx;
             if (!DecodeHexTx(mtx, request.params[0].get_str())) {
                 throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
                                    "TX decode failed");
             }
 
             // Sign the transaction
             LOCK(pwallet->cs_wallet);
             EnsureWalletIsUnlocked(pwallet);
 
             // Fetch previous transactions (inputs):
             std::map<COutPoint, Coin> coins;
             for (const CTxIn &txin : mtx.vin) {
                 // Create empty map entry keyed by prevout.
                 coins[txin.prevout];
             }
             pwallet->chain().findCoins(coins);
 
             // Parse the prevtxs array
             ParsePrevouts(request.params[1], nullptr, coins);
 
             SigHashType nHashType = ParseSighashString(request.params[2]);
             if (!nHashType.hasForkId()) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Signature must use SIGHASH_FORKID");
             }
 
             // Script verification errors
             std::map<int, std::string> input_errors;
 
             bool complete =
                 pwallet->SignTransaction(mtx, coins, nHashType, input_errors);
             UniValue result(UniValue::VOBJ);
             SignTransactionResultToJSON(mtx, complete, coins, input_errors,
                                         result);
             return result;
         },
     };
 }
 
 RPCHelpMan rescanblockchain() {
     return RPCHelpMan{
         "rescanblockchain",
         "Rescan the local blockchain for wallet related transactions.\n"
         "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
         {
             {"start_height", RPCArg::Type::NUM, RPCArg::Default{0},
              "block height where the rescan should start"},
             {"stop_height", RPCArg::Type::NUM,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "the last block height that should be scanned"},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::NUM, "start_height",
                  "The block height where the rescan started (the requested "
                  "height or 0)"},
                 {RPCResult::Type::NUM, "stop_height",
                  "The height of the last rescanned block. May be null in rare "
                  "cases if there was a reorg and the call didn't scan any "
                  "blocks because they were already scanned in the background."},
             }},
         RPCExamples{HelpExampleCli("rescanblockchain", "100000 120000") +
                     HelpExampleRpc("rescanblockchain", "100000, 120000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             WalletRescanReserver reserver(*pwallet);
             if (!reserver.reserve()) {
                 throw JSONRPCError(RPC_WALLET_ERROR,
                                    "Wallet is currently rescanning. Abort "
                                    "existing rescan or wait.");
             }
 
             int start_height = 0;
             std::optional<int> stop_height;
             BlockHash start_block;
             {
                 LOCK(pwallet->cs_wallet);
                 int tip_height = pwallet->GetLastBlockHeight();
 
                 if (!request.params[0].isNull()) {
                     start_height = request.params[0].getInt<int>();
                     if (start_height < 0 || start_height > tip_height) {
                         throw JSONRPCError(RPC_INVALID_PARAMETER,
                                            "Invalid start_height");
                     }
                 }
 
                 if (!request.params[1].isNull()) {
                     stop_height = request.params[1].getInt<int>();
                     if (*stop_height < 0 || *stop_height > tip_height) {
                         throw JSONRPCError(RPC_INVALID_PARAMETER,
                                            "Invalid stop_height");
                     } else if (*stop_height < start_height) {
                         throw JSONRPCError(
                             RPC_INVALID_PARAMETER,
                             "stop_height must be greater than start_height");
                     }
                 }
 
                 // We can't rescan beyond non-pruned blocks, stop and throw an
                 // error
                 if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(),
                                                 start_height, stop_height)) {
                     throw JSONRPCError(
                         RPC_MISC_ERROR,
                         "Can't rescan beyond pruned data. Use RPC call "
                         "getblockchaininfo to determine your pruned height.");
                 }
 
                 CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(
                     pwallet->GetLastBlockHash(), start_height,
                     FoundBlock().hash(start_block)));
             }
 
             CWallet::ScanResult result = pwallet->ScanForWalletTransactions(
                 start_block, start_height, stop_height, reserver,
                 true /* fUpdate */);
             switch (result.status) {
                 case CWallet::ScanResult::SUCCESS:
                     break;
                 case CWallet::ScanResult::FAILURE:
                     throw JSONRPCError(
                         RPC_MISC_ERROR,
                         "Rescan failed. Potentially corrupted data files.");
                 case CWallet::ScanResult::USER_ABORT:
                     throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
                     // no default case, so the compiler can warn about missing
                     // cases
             }
             UniValue response(UniValue::VOBJ);
             response.pushKV("start_height", start_height);
             response.pushKV("stop_height", result.last_scanned_height
                                                ? *result.last_scanned_height
                                                : UniValue());
             return response;
         },
     };
 }
 
 class DescribeWalletAddressVisitor {
 public:
     const SigningProvider *const provider;
 
     void ProcessSubScript(const CScript &subscript, UniValue &obj) const {
         // Always present: script type and redeemscript
         std::vector<std::vector<uint8_t>> solutions_data;
         TxoutType which_type = Solver(subscript, solutions_data);
         obj.pushKV("script", GetTxnOutputType(which_type));
         obj.pushKV("hex", HexStr(subscript));
 
         CTxDestination embedded;
         if (ExtractDestination(subscript, embedded)) {
             // Only when the script corresponds to an address.
             UniValue subobj(UniValue::VOBJ);
             UniValue detail = DescribeAddress(embedded);
             subobj.pushKVs(detail);
             UniValue wallet_detail = std::visit(*this, embedded);
             subobj.pushKVs(wallet_detail);
             subobj.pushKV("address", EncodeDestination(embedded, GetConfig()));
             subobj.pushKV("scriptPubKey", HexStr(subscript));
             // Always report the pubkey at the top level, so that
             // `getnewaddress()['pubkey']` always works.
             if (subobj.exists("pubkey")) {
                 obj.pushKV("pubkey", subobj["pubkey"]);
             }
             obj.pushKV("embedded", std::move(subobj));
         } else if (which_type == TxoutType::MULTISIG) {
             // Also report some information on multisig scripts (which do not
             // have a corresponding address).
             // TODO: abstract out the common functionality between this logic
             // and ExtractDestinations.
             obj.pushKV("sigsrequired", solutions_data[0][0]);
             UniValue pubkeys(UniValue::VARR);
             for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
                 CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
                 pubkeys.push_back(HexStr(key));
             }
             obj.pushKV("pubkeys", std::move(pubkeys));
         }
     }
 
     explicit DescribeWalletAddressVisitor(const SigningProvider *_provider)
         : provider(_provider) {}
 
     UniValue operator()(const CNoDestination &dest) const {
         return UniValue(UniValue::VOBJ);
     }
 
     UniValue operator()(const PKHash &pkhash) const {
         CKeyID keyID(ToKeyID(pkhash));
         UniValue obj(UniValue::VOBJ);
         CPubKey vchPubKey;
         if (provider && provider->GetPubKey(keyID, vchPubKey)) {
             obj.pushKV("pubkey", HexStr(vchPubKey));
             obj.pushKV("iscompressed", vchPubKey.IsCompressed());
         }
         return obj;
     }
 
     UniValue operator()(const ScriptHash &scripthash) const {
         CScriptID scriptID(scripthash);
         UniValue obj(UniValue::VOBJ);
         CScript subscript;
         if (provider && provider->GetCScript(scriptID, subscript)) {
             ProcessSubScript(subscript, obj);
         }
         return obj;
     }
 };
 
 static UniValue DescribeWalletAddress(const CWallet *const pwallet,
                                       const CTxDestination &dest) {
     UniValue ret(UniValue::VOBJ);
     UniValue detail = DescribeAddress(dest);
     CScript script = GetScriptForDestination(dest);
     std::unique_ptr<SigningProvider> provider = nullptr;
     if (pwallet) {
         provider = pwallet->GetSolvingProvider(script);
     }
     ret.pushKVs(detail);
     ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
     return ret;
 }
 
 /** Convert CAddressBookData to JSON record.  */
 static UniValue AddressBookDataToJSON(const CAddressBookData &data,
                                       const bool verbose) {
     UniValue ret(UniValue::VOBJ);
     if (verbose) {
         ret.pushKV("name", data.GetLabel());
     }
     ret.pushKV("purpose", data.purpose);
     return ret;
 }
 
 RPCHelpMan getaddressinfo() {
     return RPCHelpMan{
         "getaddressinfo",
         "Return information about the given bitcoin address.\n"
         "Some of the information will only be present if the address is in the "
         "active wallet.\n",
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The bitcoin address for which to get information."},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {
                 {RPCResult::Type::STR, "address",
                  "The bitcoin address validated."},
                 {RPCResult::Type::STR_HEX, "scriptPubKey",
                  "The hex-encoded scriptPubKey generated by the address."},
                 {RPCResult::Type::BOOL, "ismine", "If the address is yours."},
                 {RPCResult::Type::BOOL, "iswatchonly",
                  "If the address is watchonly."},
                 {RPCResult::Type::BOOL, "solvable",
                  "If we know how to spend coins sent to this address, ignoring "
                  "the possible lack of private keys."},
                 {RPCResult::Type::STR, "desc", /* optional */ true,
                  "A descriptor for spending coins sent to this address (only "
                  "when solvable)."},
                 {RPCResult::Type::BOOL, "isscript", "If the key is a script."},
                 {RPCResult::Type::BOOL, "ischange",
                  "If the address was used for change output."},
                 {RPCResult::Type::STR, "script", /* optional */ true,
                  "The output script type. Only if isscript is true and the "
                  "redeemscript is known. Possible\n"
                  "                                                         "
                  "types: nonstandard, pubkey, pubkeyhash, scripthash, "
                  "multisig, nulldata."},
                 {RPCResult::Type::STR_HEX, "hex", /* optional */ true,
                  "The redeemscript for the p2sh address."},
                 {RPCResult::Type::ARR,
                  "pubkeys",
                  /* optional */ true,
                  "Array of pubkeys associated with the known redeemscript "
                  "(only if script is multisig).",
                  {
                      {RPCResult::Type::STR, "pubkey", ""},
                  }},
                 {RPCResult::Type::NUM, "sigsrequired", /* optional */ true,
                  "The number of signatures required to spend multisig output "
                  "(only if script is multisig)."},
                 {RPCResult::Type::STR_HEX, "pubkey", /* optional */ true,
                  "The hex value of the raw public key for single-key addresses "
                  "(possibly embedded in P2SH)."},
                 {RPCResult::Type::OBJ,
                  "embedded",
                  /* optional */ true,
                  "Information about the address embedded in P2SH, if "
                  "relevant and known.",
                  {
                      {RPCResult::Type::ELISION, "",
                       "Includes all getaddressinfo output fields for the "
                       "embedded address excluding metadata (timestamp, "
                       "hdkeypath, hdseedid)\n"
                       "and relation to the wallet (ismine, iswatchonly)."},
                  }},
                 {RPCResult::Type::BOOL, "iscompressed", /* optional */ true,
                  "If the pubkey is compressed."},
                 {RPCResult::Type::NUM_TIME, "timestamp", /* optional */ true,
                  "The creation time of the key, if available, expressed in " +
                      UNIX_EPOCH_TIME + "."},
                 {RPCResult::Type::STR, "hdkeypath", /* optional */ true,
                  "The HD keypath, if the key is HD and available."},
                 {RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true,
                  "The Hash160 of the HD seed."},
                 {RPCResult::Type::STR_HEX, "hdmasterfingerprint",
                  /* optional */ true, "The fingerprint of the master key."},
                 {RPCResult::Type::ARR,
                  "labels",
                  "Array of labels associated with the address. Currently "
                  "limited to one label but returned\n"
                  "as an array to keep the API stable if multiple labels are "
                  "enabled in the future.",
                  {
                      {RPCResult::Type::STR, "label name",
                       "Label name (defaults to \"\")."},
                  }},
             }},
         RPCExamples{HelpExampleCli("getaddressinfo", EXAMPLE_ADDRESS) +
                     HelpExampleRpc("getaddressinfo", EXAMPLE_ADDRESS)},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             UniValue ret(UniValue::VOBJ);
             CTxDestination dest = DecodeDestination(request.params[0].get_str(),
                                                     wallet->GetChainParams());
             // Make sure the destination is valid
             if (!IsValidDestination(dest)) {
                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                    "Invalid address");
             }
 
             std::string currentAddress = EncodeDestination(dest, config);
             ret.pushKV("address", currentAddress);
 
             CScript scriptPubKey = GetScriptForDestination(dest);
             ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
 
             std::unique_ptr<SigningProvider> provider =
                 pwallet->GetSolvingProvider(scriptPubKey);
 
             isminetype mine = pwallet->IsMine(dest);
             ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
 
             bool solvable = provider && IsSolvable(*provider, scriptPubKey);
             ret.pushKV("solvable", solvable);
 
             if (solvable) {
                 ret.pushKV(
                     "desc",
                     InferDescriptor(scriptPubKey, *provider)->ToString());
             }
 
             ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
 
             UniValue detail = DescribeWalletAddress(pwallet, dest);
             ret.pushKVs(detail);
 
             ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
 
             ScriptPubKeyMan *spk_man =
                 pwallet->GetScriptPubKeyMan(scriptPubKey);
             if (spk_man) {
                 if (const std::unique_ptr<CKeyMetadata> meta =
                         spk_man->GetMetadata(dest)) {
                     ret.pushKV("timestamp", meta->nCreateTime);
                     if (meta->has_key_origin) {
                         ret.pushKV("hdkeypath",
                                    WriteHDKeypath(meta->key_origin.path));
                         ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
                         ret.pushKV("hdmasterfingerprint",
                                    HexStr(meta->key_origin.fingerprint));
                     }
                 }
             }
 
             // Return a `labels` array containing the label associated with the
             // address, equivalent to the `label` field above. Currently only
             // one label can be associated with an address, but we return an
             // array so the API remains stable if we allow multiple labels to be
             // associated with an address in the future.
             UniValue labels(UniValue::VARR);
             const auto *address_book_entry =
                 pwallet->FindAddressBookEntry(dest);
             if (address_book_entry) {
                 labels.push_back(address_book_entry->GetLabel());
             }
             ret.pushKV("labels", std::move(labels));
 
             return ret;
         },
     };
 }
 
 RPCHelpMan getaddressesbylabel() {
     return RPCHelpMan{
         "getaddressesbylabel",
         "Returns the list of addresses assigned the specified label.\n",
         {
             {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."},
         },
         RPCResult{RPCResult::Type::OBJ_DYN,
                   "",
                   "json object with addresses as keys",
                   {
                       {RPCResult::Type::OBJ,
                        "address",
                        "Information about address",
                        {
                            {RPCResult::Type::STR, "purpose",
                             "Purpose of address (\"send\" for sending address, "
                             "\"receive\" for receiving address)"},
                        }},
                   }},
         RPCExamples{HelpExampleCli("getaddressesbylabel", "\"tabby\"") +
                     HelpExampleRpc("getaddressesbylabel", "\"tabby\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             std::string label = LabelFromValue(request.params[0]);
 
             // Find all addresses that have the given label
             UniValue ret(UniValue::VOBJ);
             std::set<std::string> addresses;
             for (const std::pair<const CTxDestination, CAddressBookData> &item :
                  pwallet->m_address_book) {
                 if (item.second.IsChange()) {
                     continue;
                 }
                 if (item.second.GetLabel() == label) {
                     std::string address = EncodeDestination(item.first, config);
                     // CWallet::m_address_book is not expected to contain
                     // duplicate address strings, but build a separate set as a
                     // precaution just in case it does.
                     CHECK_NONFATAL(addresses.emplace(address).second);
                     // UniValue::pushKV checks if the key exists in O(N)
                     // and since duplicate addresses are unexpected (checked
                     // with std::set in O(log(N))), UniValue::__pushKV is used
                     // instead, which currently is O(1).
                     ret.__pushKV(address,
                                  AddressBookDataToJSON(item.second, false));
                 }
             }
 
             if (ret.empty()) {
                 throw JSONRPCError(
                     RPC_WALLET_INVALID_LABEL_NAME,
                     std::string("No addresses with label " + label));
             }
 
             return ret;
         },
     };
 }
 
 RPCHelpMan listlabels() {
     return RPCHelpMan{
         "listlabels",
         "Returns the list of all labels, or labels that are assigned to "
         "addresses with a specific purpose.\n",
         {
             {"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
              "Address purpose to list labels for ('send','receive'). An empty "
              "string is the same as not providing this argument."},
         },
         RPCResult{RPCResult::Type::ARR,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "label", "Label name"},
                   }},
         RPCExamples{"\nList all labels\n" + HelpExampleCli("listlabels", "") +
                     "\nList labels that have receiving addresses\n" +
                     HelpExampleCli("listlabels", "receive") +
                     "\nList labels that have sending addresses\n" +
                     HelpExampleCli("listlabels", "send") +
                     "\nAs a JSON-RPC call\n" +
                     HelpExampleRpc("listlabels", "receive")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             LOCK(pwallet->cs_wallet);
 
             std::string purpose;
             if (!request.params[0].isNull()) {
                 purpose = request.params[0].get_str();
             }
 
             // Add to a set to sort by label name, then insert into Univalue
             // array
             std::set<std::string> label_set;
             for (const std::pair<const CTxDestination, CAddressBookData>
                      &entry : pwallet->m_address_book) {
                 if (entry.second.IsChange()) {
                     continue;
                 }
                 if (purpose.empty() || entry.second.purpose == purpose) {
                     label_set.insert(entry.second.GetLabel());
                 }
             }
 
             UniValue ret(UniValue::VARR);
             for (const std::string &name : label_set) {
                 ret.push_back(name);
             }
 
             return ret;
         },
     };
 }
 
 static RPCHelpMan send() {
     return RPCHelpMan{
         "send",
         "EXPERIMENTAL warning: this call may be changed in future releases.\n"
         "\nSend a transaction.\n",
         {
             {"outputs",
              RPCArg::Type::ARR,
              RPCArg::Optional::NO,
              "A JSON array with outputs (key-value pairs), where none of "
              "the keys are duplicated.\n"
              "That is, each address can only appear once and there can only "
              "be one 'data' object.\n"
              "For convenience, a dictionary, which holds the key-value "
              "pairs directly, is also accepted.",
              {
                  {
                      "",
                      RPCArg::Type::OBJ,
                      RPCArg::Optional::OMITTED,
                      "",
                      {
                          {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
                           "A key-value pair. The key (string) is the "
                           "bitcoin address, the value (float or string) is "
                           "the amount in " +
                               Currency::get().ticker + ""},
                      },
                  },
                  {
                      "",
                      RPCArg::Type::OBJ,
                      RPCArg::Optional::OMITTED,
                      "",
                      {
                          {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
                           "A key-value pair. The key must be \"data\", the "
                           "value is hex-encoded data"},
                      },
                  },
              },
              RPCArgOptions{.skip_type_check = true}},
             {"options",
              RPCArg::Type::OBJ,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "",
              {
                  {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "If inputs are specified, automatically include more if they "
                   "are not enough."},
                  {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "Include inputs that are not safe to spend (unconfirmed "
                   "transactions from outside keys).\n"
                   "Warning: the resulting transaction may become invalid if "
                   "one of the unsafe inputs disappears.\n"
                   "If that happens, you will need to fund the transaction with "
                   "different inputs and republish it."},
                  {"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true},
                   "When false, returns a serialized transaction which will not "
                   "be added to the wallet or broadcast"},
                  {"change_address", RPCArg::Type::STR_HEX,
                   RPCArg::DefaultHint{"pool address"},
                   "The bitcoin address to receive the change"},
                  {"change_position", RPCArg::Type::NUM,
                   RPCArg::DefaultHint{"random"},
                   "The index of the change output"},
                  {"fee_rate", RPCArg::Type::AMOUNT,
                   RPCArg::DefaultHint{
                       "not set: makes wallet determine the fee"},
                   "Set a specific fee rate in " + Currency::get().ticker +
                       "/kB"},
                  {"include_watching", RPCArg::Type::BOOL,
                   RPCArg::DefaultHint{
                       "true for watch-only wallets, otherwise false"},
                   "Also select inputs which are watch only.\n"
                   "Only solvable inputs can be used. Watch-only destinations "
                   "are solvable if the public key and/or output script was "
                   "imported,\n"
                   "e.g. with 'importpubkey' or 'importmulti' with the "
                   "'pubkeys' or 'desc' field."},
                  {
                      "inputs",
                      RPCArg::Type::ARR,
                      RPCArg::Default{UniValue::VARR},
                      "Specify inputs instead of adding them automatically. A "
                      "JSON array of JSON objects",
                      {
                          {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
                           "The transaction id"},
                          {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
                           "The output number"},
                          {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO,
                           "The sequence number"},
                      },
                  },
                  {"locktime", RPCArg::Type::NUM, RPCArg::Default{0},
                   "Raw locktime. Non-0 value also locktime-activates inputs"},
                  {"lock_unspents", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "Lock selected unspent outputs"},
                  {"psbt", RPCArg::Type::BOOL, RPCArg::DefaultHint{"automatic"},
                   "Always return a PSBT, implies add_to_wallet=false."},
                  {
                      "subtract_fee_from_outputs",
                      RPCArg::Type::ARR,
                      RPCArg::Default{UniValue::VARR},
                      "Outputs to subtract the fee from, specified as integer "
                      "indices.\n"
                      "The fee will be equally deducted from the amount of each "
                      "specified output.\n"
                      "Those recipients will receive less bitcoins than you "
                      "enter in their corresponding amount field.\n"
                      "If no outputs are specified here, the sender pays the "
                      "fee.",
                      {
                          {"vout_index", RPCArg::Type::NUM,
                           RPCArg::Optional::OMITTED,
                           "The zero-based output index, before a change output "
                           "is added."},
                      },
                  },
              },
              RPCArgOptions{.oneline_description = "options"}},
         },
         RPCResult{
             RPCResult::Type::OBJ,
             "",
             "",
             {{RPCResult::Type::BOOL, "complete",
               "If the transaction has a complete set of signatures"},
              {RPCResult::Type::STR_HEX, "txid",
               "The transaction id for the send. Only 1 transaction is created "
               "regardless of the number of addresses."},
              {RPCResult::Type::STR_HEX, "hex",
               "If add_to_wallet is false, the hex-encoded raw transaction with "
               "signature(s)"},
              {RPCResult::Type::STR, "psbt",
               "If more signatures are needed, or if add_to_wallet is false, "
               "the base64-encoded (partially) signed transaction"}}},
         RPCExamples{
             ""
             "\nSend with a fee rate of 10 XEC/kB\n" +
             HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS +
                                        "\": 100000}' '{\"fee_rate\": 10}'\n") +
             "\nCreate a transaction with a specific input, and return "
             "result without adding to wallet or broadcasting to the "
             "network\n" +
             HelpExampleCli("send",
                            "'{\"" + EXAMPLE_ADDRESS +
                                "\": 100000}' '{\"add_to_wallet\": "
                                "false, \"inputs\": "
                                "[{\"txid\":"
                                "\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b565"
                                "5e72f463568df1aadf0\", \"vout\":1}]}'")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             UniValue options = request.params[1];
             if (options.exists("changeAddress")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address");
             }
             if (options.exists("changePosition")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Use change_position");
             }
             if (options.exists("includeWatching")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Use include_watching");
             }
             if (options.exists("lockUnspents")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents");
             }
             if (options.exists("subtractFeeFromOutputs")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Use subtract_fee_from_outputs");
             }
             if (options.exists("feeRate")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate");
             }
 
             const bool psbt_opt_in =
                 options.exists("psbt") && options["psbt"].get_bool();
 
             Amount fee;
             int change_position;
             CMutableTransaction rawTx = ConstructTransaction(
                 wallet->GetChainParams(), options["inputs"], request.params[0],
                 options["locktime"]);
             CCoinControl coin_control;
             // Automatically select coins, unless at least one is manually
             // selected. Can be overridden by options.add_inputs.
             coin_control.m_add_inputs = rawTx.vin.size() == 0;
             FundTransaction(pwallet, rawTx, fee, change_position, options,
                             coin_control);
 
             bool add_to_wallet = true;
             if (options.exists("add_to_wallet")) {
                 add_to_wallet = options["add_to_wallet"].get_bool();
             }
 
             // Make a blank psbt
             PartiallySignedTransaction psbtx(rawTx);
 
             // Fill transaction with our data and sign
             bool complete = true;
             const TransactionError err = pwallet->FillPSBT(
                 psbtx, complete, SigHashType().withForkId(), true, false);
             if (err != TransactionError::OK) {
                 throw JSONRPCTransactionError(err);
             }
 
             CMutableTransaction mtx;
             complete = FinalizeAndExtractPSBT(psbtx, mtx);
 
             UniValue result(UniValue::VOBJ);
 
             if (psbt_opt_in || !complete || !add_to_wallet) {
                 // Serialize the PSBT
                 CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
                 ssTx << psbtx;
                 result.pushKV("psbt", EncodeBase64(ssTx.str()));
             }
 
             if (complete) {
                 std::string err_string;
                 std::string hex = EncodeHexTx(CTransaction(mtx));
                 CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
                 result.pushKV("txid", tx->GetHash().GetHex());
                 if (add_to_wallet && !psbt_opt_in) {
                     pwallet->CommitTransaction(tx, {}, {} /* orderForm */);
                 } else {
                     result.pushKV("hex", hex);
                 }
             }
             result.pushKV("complete", complete);
 
             return result;
         }};
 }
 
 static RPCHelpMan sethdseed() {
     return RPCHelpMan{
         "sethdseed",
         "Set or generate a new HD wallet seed. Non-HD wallets will not be "
         "upgraded to being a HD wallet. Wallets that are already\n"
         "HD will have a new HD seed set so that new keys added to the keypool "
         "will be derived from this new seed.\n"
         "\nNote that you will need to MAKE A NEW BACKUP of your wallet after "
         "setting the HD wallet seed.\n" +
             HELP_REQUIRING_PASSPHRASE,
         {
             {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true},
              "Whether to flush old unused addresses, including change "
              "addresses, from the keypool and regenerate it.\n"
              "                             If true, the next address from "
              "getnewaddress and change address from getrawchangeaddress will "
              "be from this new seed.\n"
              "                             If false, addresses (including "
              "change addresses if the wallet already had HD Chain Split "
              "enabled) from the existing\n"
              "                             keypool will be used until it has "
              "been depleted."},
             {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"},
              "The WIF private key to use as the new HD seed.\n"
              "                             The seed value can be retrieved "
              "using the dumpwallet command. It is the private key marked "
              "hdseed=1"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("sethdseed", "") +
                     HelpExampleCli("sethdseed", "false") +
                     HelpExampleCli("sethdseed", "true \"wifkey\"") +
                     HelpExampleRpc("sethdseed", "true, \"wifkey\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             LegacyScriptPubKeyMan &spk_man =
                 EnsureLegacyScriptPubKeyMan(*pwallet, true);
 
             if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
                 throw JSONRPCError(RPC_WALLET_ERROR,
                                    "Cannot set a HD seed to a wallet with "
                                    "private keys disabled");
             }
 
             LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
 
             // Do not do anything to non-HD wallets
             if (!pwallet->CanSupportFeature(FEATURE_HD)) {
                 throw JSONRPCError(
                     RPC_WALLET_ERROR,
                     "Cannot set a HD seed on a non-HD wallet. Use the "
                     "upgradewallet RPC in order to upgrade a non-HD wallet "
                     "to HD");
             }
 
             EnsureWalletIsUnlocked(pwallet);
 
             bool flush_key_pool = true;
             if (!request.params[0].isNull()) {
                 flush_key_pool = request.params[0].get_bool();
             }
 
             CPubKey master_pub_key;
             if (request.params[1].isNull()) {
                 master_pub_key = spk_man.GenerateNewSeed();
             } else {
                 CKey key = DecodeSecret(request.params[1].get_str());
                 if (!key.IsValid()) {
                     throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                        "Invalid private key");
                 }
 
                 if (HaveKey(spk_man, key)) {
                     throw JSONRPCError(
                         RPC_INVALID_ADDRESS_OR_KEY,
                         "Already have this key (either as an HD seed or "
                         "as a loose private key)");
                 }
 
                 master_pub_key = spk_man.DeriveNewSeed(key);
             }
 
             spk_man.SetHDSeed(master_pub_key);
             if (flush_key_pool) {
                 spk_man.NewKeyPool();
             }
 
             return NullUniValue;
         },
     };
 }
 
 static RPCHelpMan walletprocesspsbt() {
     return RPCHelpMan{
         "walletprocesspsbt",
         "Update a PSBT with input information from our wallet and then sign "
         "inputs that we can sign for." +
             HELP_REQUIRING_PASSPHRASE,
         {
             {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The transaction base64 string"},
             {"sign", RPCArg::Type::BOOL, RPCArg::Default{true},
              "Also sign the transaction when updating"},
             {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL|FORKID"},
              "The signature hash type to sign with if not specified by "
              "the PSBT. Must be one of\n"
              "       \"ALL|FORKID\"\n"
              "       \"NONE|FORKID\"\n"
              "       \"SINGLE|FORKID\"\n"
              "       \"ALL|FORKID|ANYONECANPAY\"\n"
              "       \"NONE|FORKID|ANYONECANPAY\"\n"
              "       \"SINGLE|FORKID|ANYONECANPAY\""},
             {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true},
              "Includes the BIP 32 derivation paths for public keys if we know "
              "them"},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "psbt",
                        "The base64-encoded partially signed transaction"},
                       {RPCResult::Type::BOOL, "complete",
                        "If the transaction has a complete set of signatures"},
                   }},
         RPCExamples{HelpExampleCli("walletprocesspsbt", "\"psbt\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             const CWallet *const pwallet = wallet.get();
 
             // Unserialize the transaction
             PartiallySignedTransaction psbtx;
             std::string error;
             if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
                 throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
                                    strprintf("TX decode failed %s", error));
             }
 
             // Get the sighash type
             SigHashType nHashType = ParseSighashString(request.params[2]);
             if (!nHashType.hasForkId()) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER,
                                    "Signature must use SIGHASH_FORKID");
             }
 
             // Fill transaction with our data and also sign
             bool sign = request.params[1].isNull()
                             ? true
                             : request.params[1].get_bool();
             bool bip32derivs = request.params[3].isNull()
                                    ? true
                                    : request.params[3].get_bool();
             bool complete = true;
             const TransactionError err = pwallet->FillPSBT(
                 psbtx, complete, nHashType, sign, bip32derivs);
             if (err != TransactionError::OK) {
                 throw JSONRPCTransactionError(err);
             }
 
             UniValue result(UniValue::VOBJ);
             CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
             ssTx << psbtx;
             result.pushKV("psbt", EncodeBase64(ssTx.str()));
             result.pushKV("complete", complete);
 
             return result;
         },
     };
 }
 
 static RPCHelpMan walletcreatefundedpsbt() {
     const auto &ticker = Currency::get().ticker;
     return RPCHelpMan{
         "walletcreatefundedpsbt",
         "Creates and funds a transaction in the Partially Signed Transaction "
         "format.\n"
         "Implements the Creator and Updater roles.\n",
         {
             {
                 "inputs",
                 RPCArg::Type::ARR,
                 RPCArg::Optional::OMITTED_NAMED_ARG,
                 "Leave empty to add inputs automatically. See add_inputs "
                 "option.",
                 {
                     {
                         "",
                         RPCArg::Type::OBJ,
                         RPCArg::Optional::OMITTED,
                         "",
                         {
                             {"txid", RPCArg::Type::STR_HEX,
                              RPCArg::Optional::NO, "The transaction id"},
                             {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
                              "The output number"},
                             {"sequence", RPCArg::Type::NUM,
                              RPCArg::DefaultHint{
                                  "depends on the value of the 'locktime' and "
                                  "'options.replaceable' arguments"},
                              "The sequence number"},
                         },
                     },
                 },
             },
             {"outputs",
              RPCArg::Type::ARR,
              RPCArg::Optional::NO,
              "The outputs (key-value pairs), where none of "
              "the keys are duplicated.\n"
              "That is, each address can only appear once and there can only "
              "be one 'data' object.\n"
              "For compatibility reasons, a dictionary, which holds the "
              "key-value pairs directly, is also\n"
              "                             accepted as second parameter.",
              {
                  {
                      "",
                      RPCArg::Type::OBJ,
                      RPCArg::Optional::OMITTED,
                      "",
                      {
                          {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
                           "A key-value pair. The key (string) is the "
                           "bitcoin address, the value (float or string) is "
                           "the amount in " +
                               ticker + ""},
                      },
                  },
                  {
                      "",
                      RPCArg::Type::OBJ,
                      RPCArg::Optional::OMITTED,
                      "",
                      {
                          {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
                           "A key-value pair. The key must be \"data\", the "
                           "value is hex-encoded data"},
                      },
                  },
              },
              RPCArgOptions{.skip_type_check = true}},
             {"locktime", RPCArg::Type::NUM, RPCArg::Default{0},
              "Raw locktime. Non-0 value also locktime-activates inputs\n"
              "                             Allows this transaction to be "
              "replaced by a transaction with higher fees. If provided, it is "
              "an error if explicit sequence numbers are incompatible."},
             {"options",
              RPCArg::Type::OBJ,
              RPCArg::Optional::OMITTED_NAMED_ARG,
              "",
              {
                  {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "If inputs are specified, automatically include more if they "
                   "are not enough."},
                  {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "Include inputs that are not safe to spend (unconfirmed "
                   "transactions from outside keys).\n"
                   "Warning: the resulting transaction may become invalid if "
                   "one of the unsafe inputs disappears.\n"
                   "If that happens, you will need to fund the transaction with "
                   "different inputs and republish it."},
                  {"changeAddress", RPCArg::Type::STR_HEX,
                   RPCArg::DefaultHint{"pool address"},
                   "The bitcoin address to receive the change"},
                  {"changePosition", RPCArg::Type::NUM,
                   RPCArg::DefaultHint{"random"},
                   "The index of the change output"},
                  {"includeWatching", RPCArg::Type::BOOL,
                   RPCArg::DefaultHint{
                       "true for watch-only wallets, otherwise false"},
                   "Also select inputs which are watch only"},
                  {"lockUnspents", RPCArg::Type::BOOL, RPCArg::Default{false},
                   "Lock selected unspent outputs"},
                  {"feeRate", RPCArg::Type::AMOUNT,
                   RPCArg::DefaultHint{
                       "not set: makes wallet determine the fee"},
                   "Set a specific fee rate in " + ticker + "/kB"},
                  {
                      "subtractFeeFromOutputs",
                      RPCArg::Type::ARR,
                      RPCArg::Default{UniValue::VARR},
                      "The outputs to subtract the fee from.\n"
                      "                              The fee will be equally "
                      "deducted from the amount of each specified output.\n"
                      "                              Those recipients will "
                      "receive less bitcoins than you enter in their "
                      "corresponding amount field.\n"
                      "                              If no outputs are "
                      "specified here, the sender pays the fee.",
                      {
                          {"vout_index", RPCArg::Type::NUM,
                           RPCArg::Optional::OMITTED,
                           "The zero-based output index, before a change output "
                           "is added."},
                      },
                  },
              },
              RPCArgOptions{.oneline_description = "options"}},
             {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true},
              "Includes the BIP 32 derivation paths for public keys if we know "
              "them"},
         },
         RPCResult{RPCResult::Type::OBJ,
                   "",
                   "",
                   {
                       {RPCResult::Type::STR, "psbt",
                        "The resulting raw transaction (base64-encoded string)"},
                       {RPCResult::Type::STR_AMOUNT, "fee",
                        "Fee in " + ticker + " the resulting transaction pays"},
                       {RPCResult::Type::NUM, "changepos",
                        "The position of the added change output, or -1"},
                   }},
         RPCExamples{
             "\nCreate a transaction with no inputs\n" +
             HelpExampleCli("walletcreatefundedpsbt",
                            "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" "
                            "\"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             Amount fee;
             int change_position;
             CMutableTransaction rawTx = ConstructTransaction(
                 wallet->GetChainParams(), request.params[0], request.params[1],
                 request.params[2]);
             CCoinControl coin_control;
             // Automatically select coins, unless at least one is manually
             // selected. Can be overridden by options.add_inputs.
             coin_control.m_add_inputs = rawTx.vin.size() == 0;
             FundTransaction(pwallet, rawTx, fee, change_position,
                             request.params[3], coin_control);
 
             // Make a blank psbt
             PartiallySignedTransaction psbtx(rawTx);
 
             // Fill transaction with out data but don't sign
             bool bip32derivs = request.params[4].isNull()
                                    ? true
                                    : request.params[4].get_bool();
             bool complete = true;
             const TransactionError err =
                 pwallet->FillPSBT(psbtx, complete, SigHashType().withForkId(),
                                   false, bip32derivs);
             if (err != TransactionError::OK) {
                 throw JSONRPCTransactionError(err);
             }
 
             // Serialize the PSBT
             CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
             ssTx << psbtx;
 
             UniValue result(UniValue::VOBJ);
             result.pushKV("psbt", EncodeBase64(ssTx.str()));
             result.pushKV("fee", fee);
             result.pushKV("changepos", change_position);
             return result;
         },
     };
 }
 
 static RPCHelpMan upgradewallet() {
     return RPCHelpMan{
         "upgradewallet",
         "Upgrade the wallet. Upgrades to the latest version if no "
         "version number is specified\n"
         "New keys may be generated and a new wallet backup will need to "
         "be made.",
-        {{"version", RPCArg::Type::NUM, RPCArg::Default{FEATURE_LATEST},
+        {{"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}},
           "The version number to upgrade to. Default is the latest "
           "wallet version"}},
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("upgradewallet", "200300") +
                     HelpExampleRpc("upgradewallet", "200300")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             EnsureWalletIsUnlocked(pwallet);
 
             int version = 0;
             if (!request.params[0].isNull()) {
                 version = request.params[0].getInt<int>();
             }
             bilingual_str error;
             if (!pwallet->UpgradeWallet(version, error)) {
                 throw JSONRPCError(RPC_WALLET_ERROR, error.original);
             }
             return error.original;
         },
     };
 }
 
 RPCHelpMan signmessage();
 
 static RPCHelpMan createwallettransaction() {
     return RPCHelpMan{
         "createwallettransaction",
         "Create a transaction sending an amount to a given address.\n" +
             HELP_REQUIRING_PASSPHRASE,
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The bitcoin address to send to."},
             {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO,
              "The amount in " + Currency::get().ticker + " to send. eg 0.1"},
         },
         RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
         RPCExamples{
             HelpExampleCli("createwallettransaction",
                            "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 100000") +
             HelpExampleRpc("createwallettransaction",
                            "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 100000")},
         [&](const RPCHelpMan &self, const Config &config,
             const JSONRPCRequest &request) -> UniValue {
             std::shared_ptr<CWallet> const wallet =
                 GetWalletForJSONRPCRequest(request);
             if (!wallet) {
                 return NullUniValue;
             }
             CWallet *const pwallet = wallet.get();
 
             // Make sure the results are valid at least up to the most recent
             // block the user could have gotten from another RPC command prior
             // to now
             pwallet->BlockUntilSyncedToCurrentChain();
 
             LOCK(pwallet->cs_wallet);
 
             EnsureWalletIsUnlocked(pwallet);
 
             UniValue address_amounts(UniValue::VOBJ);
             const std::string address = request.params[0].get_str();
             address_amounts.pushKV(address, request.params[1]);
             UniValue subtractFeeFromAmount(UniValue::VARR);
 
             std::vector<CRecipient> recipients;
             ParseRecipients(address_amounts, subtractFeeFromAmount, recipients,
                             wallet->GetChainParams());
 
             CCoinControl coin_control;
             return SendMoney(pwallet, coin_control, recipients, {}, false);
         },
     };
 }
 
 Span<const CRPCCommand> GetWalletRPCCommands() {
     // clang-format off
     static const CRPCCommand commands[] = {
         //  category            actor (function)
         //  ------------------  ----------------------
         { "rawtransactions",    fundrawtransaction,            },
         { "wallet",             abandontransaction,            },
         { "wallet",             addmultisigaddress,            },
         { "wallet",             createwallet,                  },
         { "wallet",             getaddressesbylabel,           },
         { "wallet",             getaddressinfo,                },
         { "wallet",             getbalance,                    },
         { "wallet",             getnewaddress,                 },
         { "wallet",             getrawchangeaddress,           },
         { "wallet",             getreceivedbyaddress,          },
         { "wallet",             getreceivedbylabel,            },
         { "wallet",             gettransaction,                },
         { "wallet",             getunconfirmedbalance,         },
         { "wallet",             getbalances,                   },
         { "wallet",             getwalletinfo,                 },
         { "wallet",             keypoolrefill,                 },
         { "wallet",             listaddressgroupings,          },
         { "wallet",             listlabels,                    },
         { "wallet",             listlockunspent,               },
         { "wallet",             listreceivedbyaddress,         },
         { "wallet",             listreceivedbylabel,           },
         { "wallet",             listsinceblock,                },
         { "wallet",             listtransactions,              },
         { "wallet",             listunspent,                   },
         { "wallet",             listwalletdir,                 },
         { "wallet",             listwallets,                   },
         { "wallet",             loadwallet,                    },
         { "wallet",             lockunspent,                   },
         { "wallet",             rescanblockchain,              },
         { "wallet",             send,                          },
         { "wallet",             sendmany,                      },
         { "wallet",             sendtoaddress,                 },
         { "wallet",             sethdseed,                     },
         { "wallet",             setlabel,                      },
         { "wallet",             settxfee,                      },
         { "wallet",             setwalletflag,                 },
         { "wallet",             signmessage,                   },
         { "wallet",             signrawtransactionwithwallet,  },
         { "wallet",             unloadwallet,                  },
         { "wallet",             upgradewallet,                 },
         { "wallet",             walletcreatefundedpsbt,        },
         { "wallet",             walletprocesspsbt,             },
         // For testing purpose
         { "hidden",             createwallettransaction,       },
     };
     // clang-format on
 
     return commands;
 }