diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index e45486b17..1bac0ebe2 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,5012 +1,5013 @@
 // 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 <amount.h>
 #include <chainparams.h> // for GetConsensus.
 #include <coins.h>
 #include <config.h>
 #include <consensus/validation.h>
 #include <core_io.h>
 #include <interfaces/chain.h>
 #include <key_io.h>
 #include <network.h>
 #include <node/context.h>
 #include <outputtype.h>
 #include <policy/fees.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/message.h> // For MessageSign()
 #include <util/moneystr.h>
 #include <util/ref.h>
 #include <util/string.h>
 #include <util/system.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/rpcwallet.h>
 #include <wallet/wallet.h>
 #include <wallet/walletdb.h>
 #include <wallet/walletutil.h>
 
 #include <univalue.h>
 
 #include <event2/http.h>
 
 #include <optional>
 
 using interfaces::FoundBlock;
 
 static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
 static const std::string HELP_REQUIRING_PASSPHRASE{
     "\nRequires wallet passphrase to be set with walletpassphrase call if "
     "wallet is encrypted.\n"};
 
 static inline bool GetAvoidReuseFlag(const CWallet *const pwallet,
                                      const UniValue &param) {
     bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
     bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
 
     if (avoid_reuse && !can_avoid_reuse) {
         throw JSONRPCError(
             RPC_WALLET_ERROR,
             "wallet does not have the \"avoid reuse\" feature enabled");
     }
 
     return avoid_reuse;
 }
 
 /**
  * Used by RPC commands that have an include_watchonly parameter. We default to
  * true for watchonly wallets if include_watchonly isn't explicitly set.
  */
 static bool ParseIncludeWatchonly(const UniValue &include_watchonly,
                                   const CWallet &pwallet) {
     if (include_watchonly.isNull()) {
         // if include_watchonly isn't explicitly set, then check if we have a
         // watchonly wallet
         return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
     }
 
     // otherwise return whatever include_watchonly was set to
     return include_watchonly.get_bool();
 }
 
 /**
  * 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());
 }
 
 bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest &request,
                                      std::string &wallet_name) {
     if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) ==
         WALLET_ENDPOINT_BASE) {
         // wallet endpoint was used
         wallet_name =
             urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
         return true;
     }
     return false;
 }
 
 std::shared_ptr<CWallet>
 GetWalletForJSONRPCRequest(const JSONRPCRequest &request) {
     CHECK_NONFATAL(!request.fHelp);
     std::string wallet_name;
     if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
         std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name);
         if (!pwallet) {
             throw JSONRPCError(
                 RPC_WALLET_NOT_FOUND,
                 "Requested wallet does not exist or is not loaded");
         }
         return pwallet;
     }
 
     std::vector<std::shared_ptr<CWallet>> wallets = GetWallets();
     if (wallets.size() == 1) {
         return wallets[0];
     }
 
     if (wallets.empty()) {
         throw JSONRPCError(RPC_METHOD_NOT_FOUND,
                            "Method not found (wallet method is disabled "
                            "because no wallet is loaded)");
     }
 
     throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
                        "Wallet file not specified (must request wallet RPC "
                        "through /wallet/<filename> uri-path).");
 }
 
 void EnsureWalletIsUnlocked(const CWallet *pwallet) {
     if (pwallet->IsLocked()) {
         throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED,
                            "Error: Please enter the wallet passphrase with "
                            "walletpassphrase first.");
     }
 }
 
 WalletContext &EnsureWalletContext(const util::Ref &context) {
     if (!context.Has<WalletContext>()) {
         throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found");
     }
     return context.Get<WalletContext>();
 }
 
 // also_create should only be set to true only when the RPC is expected to add
 // things to a blank wallet and make it no longer blank
 LegacyScriptPubKeyMan &EnsureLegacyScriptPubKeyMan(CWallet &wallet,
                                                    bool also_create) {
     LegacyScriptPubKeyMan *spk_man = wallet.GetLegacyScriptPubKeyMan();
     if (!spk_man && also_create) {
         spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
     }
     if (!spk_man) {
         throw JSONRPCError(RPC_WALLET_ERROR,
                            "This type of wallet does not support this command");
     }
     return *spk_man;
 }
 
 static void WalletTxToJSON(interfaces::Chain &chain, const CWalletTx &wtx,
                            UniValue &entry) {
     int confirms = wtx.GetDepthInMainChain();
     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", wtx.IsTrusted());
     }
     uint256 hash = wtx.GetId();
     entry.pushKV("txid", hash.GetHex());
     UniValue conflicts(UniValue::VARR);
     for (const uint256 &conflict : wtx.GetConflicts()) {
         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 std::string LabelFromValue(const UniValue &value) {
     std::string label = value.get_str();
     if (label == "*") {
         throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
     }
     return label;
 }
 
 static UniValue getnewaddress(const Config &config,
                               const JSONRPCRequest &request) {
     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, /* default */ "null",
              "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,
              /* default */ "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", "")},
     }
         .Check(request);
 
     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 UniValue getrawchangeaddress(const Config &config,
                                     const JSONRPCRequest &request) {
     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", "")},
     }
         .Check(request);
 
     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 UniValue setlabel(const Config &config, const JSONRPCRequest &request) {
     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\"")},
     }
         .Check(request);
 
     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) {
     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 = pwallet->CreateTransaction(
         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 */);
     return tx->GetId().GetHex();
 }
 
 static UniValue sendtoaddress(const Config &config,
                               const JSONRPCRequest &request) {
     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,
              /* 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,
              /* 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\" 0.1") +
             HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
                                             "dd\" 0.1 \"donation\" \"seans "
                                             "outpost\"") +
             HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44"
                                             "Jvaydd\" 0.1 \"\" \"\" true") +
             HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
                                             "dd\", 0.1, \"donation\", \"seans "
                                             "outpost\"")},
     }
         .Check(request);
 
     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 UniValue listaddressgroupings(const Config &config,
                                      const JSONRPCRequest &request) {
     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,
                             "",
                             "",
                             {
                                 {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", "")},
     }
         .Check(request);
 
     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 = pwallet->GetAddressBalances();
     for (const std::set<CTxDestination> &grouping :
          pwallet->GetAddressGroupings()) {
         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 UniValue signmessage(const Config &config,
                             const JSONRPCRequest &request) {
     RPCHelpMan{
         "signmessage",
         "Sign a message with the private key of an address" +
             HELP_REQUIRING_PASSPHRASE,
         {
             {"address", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The bitcoin address to use for the private key."},
             {"message", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The message to create a signature of."},
         },
         RPCResult{RPCResult::Type::STR, "signature",
                   "The signature of the message encoded in base 64"},
         RPCExamples{
             "\nUnlock the wallet for 30 seconds\n" +
             HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
             "\nCreate the signature\n" +
             HelpExampleCli(
                 "signmessage",
                 "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
             "\nVerify the signature\n" +
             HelpExampleCli("verifymessage",
                            "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" "
                            "\"signature\" \"my message\"") +
             "\nAs a JSON-RPC call\n" +
             HelpExampleRpc(
                 "signmessage",
                 "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"")},
     }
         .Check(request);
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     const CWallet *const pwallet = wallet.get();
 
     LOCK(pwallet->cs_wallet);
 
     EnsureWalletIsUnlocked(pwallet);
 
     std::string strAddress = request.params[0].get_str();
     std::string strMessage = request.params[1].get_str();
 
     CTxDestination dest =
         DecodeDestination(strAddress, wallet->GetChainParams());
     if (!IsValidDestination(dest)) {
         throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
     }
 
     const PKHash *pkhash = boost::get<PKHash>(&dest);
     if (!pkhash) {
         throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
     }
 
     std::string signature;
     SigningResult err = pwallet->SignMessage(strMessage, *pkhash, signature);
     if (err == SigningResult::SIGNING_FAILED) {
         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                            SigningResultString(err));
     } else if (err != SigningResult::OK) {
         throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err));
     }
 
     return signature;
 }
 
 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].get_int();
     }
 
     // Tally
     Amount amount = Amount::zero();
     for (const std::pair<const TxId, CWalletTx> &wtx_pair : wallet.mapWallet) {
         const CWalletTx &wtx = wtx_pair.second;
         TxValidationState txState;
         if (wtx.IsCoinBase() ||
             !wallet.chain().contextualCheckTransactionForCurrentBlock(
                 *wtx.tx, txState) ||
             wtx.GetDepthInMainChain() < 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 UniValue getreceivedbyaddress(const Config &config,
                                      const JSONRPCRequest &request) {
     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, /* 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")},
     }
         .Check(request);
 
     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 UniValue getreceivedbylabel(const Config &config,
                                    const JSONRPCRequest &request) {
     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, /* 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")},
     }
         .Check(request);
 
     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 UniValue getbalance(const Config &config,
                            const JSONRPCRequest &request) {
     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, /* default */ "0",
              "Only include transactions confirmed at least this many times."},
             {"include_watchonly", RPCArg::Type::BOOL,
              /* default */ "true for watch-only wallets, otherwise false",
              "Also include balance in watch-only addresses (see "
              "'importaddress')"},
             {"avoid_reuse", RPCArg::Type::BOOL,
              /* 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")},
     }
         .Check(request);
 
     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].get_int();
     }
 
     bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
 
     bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]);
 
     const auto bal = pwallet->GetBalance(min_depth, avoid_reuse);
 
     return bal.m_mine_trusted +
            (include_watchonly ? bal.m_watchonly_trusted : Amount::zero());
 }
 
 static UniValue getunconfirmedbalance(const Config &config,
                                       const JSONRPCRequest &request) {
     RPCHelpMan{
         "getunconfirmedbalance",
         "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
         {},
         RPCResult{RPCResult::Type::NUM, "", "The balance"},
         RPCExamples{""},
     }
         .Check(request);
 
     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 pwallet->GetBalance().m_mine_untrusted_pending;
 }
 
 static UniValue sendmany(const Config &config, const JSONRPCRequest &request) {
     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.", "\"\""},
             {
                 "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, /* 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\"")},
     }
         .Check(request);
 
     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 UniValue addmultisigaddress(const Config &config,
                                    const JSONRPCRequest &request) {
     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\\\"]\"")},
     }
         .Check(request);
 
     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].get_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].get_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;
 
         TxValidationState state;
         if (wtx.IsCoinBase() ||
             !pwallet->chain().contextualCheckTransactionForCurrentBlock(
                 *wtx.tx, state)) {
             continue;
         }
 
         int nDepth = wtx.GetDepthInMainChain();
         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 UniValue listreceivedbyaddress(const Config &config,
                                       const JSONRPCRequest &request) {
     RPCHelpMan{
         "listreceivedbyaddress",
         "List balances by receiving address.\n",
         {
             {"minconf", RPCArg::Type::NUM, /* default */ "1",
              "The minimum number of confirmations before payments are "
              "included."},
             {"include_empty", RPCArg::Type::BOOL, /* default */ "false",
              "Whether to include addresses that haven't received any "
              "payments."},
             {"include_watchonly", RPCArg::Type::BOOL,
              /* default */ "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\"")},
     }
         .Check(request);
 
     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 UniValue listreceivedbylabel(const Config &config,
                                     const JSONRPCRequest &request) {
     RPCHelpMan{
         "listreceivedbylabel",
         "List received transactions by label.\n",
         {
             {"minconf", RPCArg::Type::NUM, /* default */ "1",
              "The minimum number of confirmations before payments are "
              "included."},
             {"include_empty", RPCArg::Type::BOOL, /* default */ "false",
              "Whether to include labels that haven't received any payments."},
             {"include_watchonly", RPCArg::Type::BOOL,
              /* default */ "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")},
     }
         .Check(request);
 
     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 UniValue into which the result is stored.
  * @param  filter_ismine  The "is mine" filter flags.
  * @param  filter_label   Optional label string to filter incoming transactions.
  */
 static void ListTransactions(const CWallet *const pwallet, const CWalletTx &wtx,
                              int nMinDepth, bool fLong, UniValue &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;
 
     wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine);
 
     bool involvesWatchonly = wtx.IsFromMe(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->chain(), wtx, entry);
             }
             entry.pushKV("abandoned", wtx.isAbandoned());
             ret.push_back(entry);
         }
     }
 
     // Received
     if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= 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 (wtx.GetDepthInMainChain() < 1) {
                     entry.pushKV("category", "orphan");
                 } else if (wtx.IsImmatureCoinBase()) {
                     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->chain(), 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."},
     };
 }
 
 UniValue listtransactions(const Config &config, const JSONRPCRequest &request) {
     const auto &ticker = Currency::get().ticker;
     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, /* default */ "10",
              "The number of transactions to return"},
             {"skip", RPCArg::Type::NUM, /* default */ "0",
              "The number of transactions to skip"},
             {"include_watchonly", RPCArg::Type::BOOL,
              /* default */ "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")},
     }
         .Check(request);
 
     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].get_int();
     }
 
     int nFrom = 0;
     if (!request.params[2].isNull()) {
         nFrom = request.params[2].get_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");
     }
     UniValue ret(UniValue::VARR);
 
     {
         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;
     }
 
     const std::vector<UniValue> &txs = ret.getValues();
     UniValue result{UniValue::VARR};
     // Return oldest to newest
     result.push_backV({txs.rend() - nFrom - nCount, txs.rend() - nFrom});
     return result;
 }
 
 static UniValue listsinceblock(const Config &config,
                                const JSONRPCRequest &request) {
     const auto &ticker = Currency::get().ticker;
     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, /* 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,
              /* default */ "true for watch-only wallets, otherwise false",
              "Include transactions to watch-only addresses (see "
              "'importaddress')"},
             {"include_removed", RPCArg::Type::BOOL, /* 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")},
     }
         .Check(request);
 
     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].get_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 || tx.GetDepthInMainChain() < 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 UniValue gettransaction(const Config &config,
                                const JSONRPCRequest &request) {
     const auto &ticker = Currency::get().ticker;
     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,
              /* default */ "true for watch-only wallets, otherwise false",
              "Whether to include watch-only addresses in balance calculation "
              "and details[]"},
             {"verbose", RPCArg::Type::BOOL, /* 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\"")},
     }
         .Check(request);
 
     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 = wtx.GetCredit(filter);
     Amount nDebit = wtx.GetDebit(filter);
     Amount nNet = nCredit - nDebit;
     Amount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit
                                         : Amount::zero());
 
     entry.pushKV("amount", nNet - nFee);
     if (wtx.IsFromMe(filter)) {
         entry.pushKV("fee", nFee);
     }
 
     WalletTxToJSON(pwallet->chain(), 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, uint256(), decoded, false);
         entry.pushKV("decoded", decoded);
     }
 
     return entry;
 }
 
 static UniValue abandontransaction(const Config &config,
                                    const JSONRPCRequest &request) {
     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\"")},
     }
         .Check(request);
 
     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 UniValue backupwallet(const Config &config,
                              const JSONRPCRequest &request) {
     RPCHelpMan{
         "backupwallet",
         "Safely copies current wallet file to destination, which can be a "
         "directory or a path with filename.\n",
         {
             {"destination", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The destination directory or file"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("backupwallet", "\"backup.dat\"") +
                     HelpExampleRpc("backupwallet", "\"backup.dat\"")},
     }
         .Check(request);
 
     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);
 
     std::string strDest = request.params[0].get_str();
     if (!pwallet->BackupWallet(strDest)) {
         throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
     }
 
     return NullUniValue;
 }
 
 static UniValue keypoolrefill(const Config &config,
                               const JSONRPCRequest &request) {
     RPCHelpMan{
         "keypoolrefill",
         "Fills the keypool." + HELP_REQUIRING_PASSPHRASE,
         {
             {"newsize", RPCArg::Type::NUM, /* default */ "100",
              "The new keypool size"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("keypoolrefill", "") +
                     HelpExampleRpc("keypoolrefill", "")},
     }
         .Check(request);
 
     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].get_int() < 0) {
             throw JSONRPCError(RPC_INVALID_PARAMETER,
                                "Invalid parameter, expected valid size.");
         }
         kpSize = (unsigned int)request.params[0].get_int();
     }
 
     EnsureWalletIsUnlocked(pwallet);
     pwallet->TopUpKeyPool(kpSize);
 
     if (pwallet->GetKeyPoolSize() < kpSize) {
         throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
     }
 
     return NullUniValue;
 }
 
 static UniValue walletpassphrase(const Config &config,
                                  const JSONRPCRequest &request) {
     RPCHelpMan{
         "walletpassphrase",
         "Stores the wallet decryption key in memory for 'timeout' seconds.\n"
         "This is needed prior to performing transactions related to private "
         "keys such as sending bitcoins\n"
         "\nNote:\n"
         "Issuing the walletpassphrase command while the wallet is already "
         "unlocked will set a new unlock\n"
         "time that overrides the old one.\n",
         {
             {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The wallet passphrase"},
             {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO,
              "The time to keep the decryption key in seconds; capped at "
              "100000000 (~3 years)."},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{
             "\nUnlock the wallet for 60 seconds\n" +
             HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
             "\nLock the wallet again (before 60 seconds)\n" +
             HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     int64_t nSleepTime;
     int64_t relock_time;
     // Prevent concurrent calls to walletpassphrase with the same wallet.
     LOCK(pwallet->m_unlock_mutex);
     {
         LOCK(pwallet->cs_wallet);
 
         if (!pwallet->IsCrypted()) {
             throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
                                "Error: running with an unencrypted wallet, but "
                                "walletpassphrase was called.");
         }
 
         // Note that the walletpassphrase is stored in request.params[0] which
         // is not mlock()ed
         SecureString strWalletPass;
         strWalletPass.reserve(100);
         // TODO: get rid of this .c_str() by implementing
         // SecureString::operator=(std::string)
         // Alternately, find a way to make request.params[0] mlock()'d to begin
         // with.
         strWalletPass = request.params[0].get_str().c_str();
 
         // Get the timeout
         nSleepTime = request.params[1].get_int64();
         // Timeout cannot be negative, otherwise it will relock immediately
         if (nSleepTime < 0) {
             throw JSONRPCError(RPC_INVALID_PARAMETER,
                                "Timeout cannot be negative.");
         }
         // Clamp timeout
         // larger values trigger a macos/libevent bug?
         constexpr int64_t MAX_SLEEP_TIME = 100000000;
         if (nSleepTime > MAX_SLEEP_TIME) {
             nSleepTime = MAX_SLEEP_TIME;
         }
 
         if (strWalletPass.empty()) {
             throw JSONRPCError(RPC_INVALID_PARAMETER,
                                "passphrase can not be empty");
         }
 
         if (!pwallet->Unlock(strWalletPass)) {
             throw JSONRPCError(
                 RPC_WALLET_PASSPHRASE_INCORRECT,
                 "Error: The wallet passphrase entered was incorrect.");
         }
 
         pwallet->TopUpKeyPool();
 
         pwallet->nRelockTime = GetTime() + nSleepTime;
         relock_time = pwallet->nRelockTime;
     }
 
     // rpcRunLater must be called without cs_wallet held otherwise a deadlock
     // can occur. The deadlock would happen when RPCRunLater removes the
     // previous timer (and waits for the callback to finish if already running)
     // and the callback locks cs_wallet.
     AssertLockNotHeld(wallet->cs_wallet);
     // Keep a weak pointer to the wallet so that it is possible to unload the
     // wallet before the following callback is called. If a valid shared pointer
     // is acquired in the callback then the wallet is still loaded.
     std::weak_ptr<CWallet> weak_wallet = wallet;
     pwallet->chain().rpcRunLater(
         strprintf("lockwallet(%s)", pwallet->GetName()),
         [weak_wallet, relock_time] {
             if (auto shared_wallet = weak_wallet.lock()) {
                 LOCK(shared_wallet->cs_wallet);
                 // Skip if this is not the most recent rpcRunLater callback.
                 if (shared_wallet->nRelockTime != relock_time) {
                     return;
                 }
                 shared_wallet->Lock();
                 shared_wallet->nRelockTime = 0;
             }
         },
         nSleepTime);
 
     return NullUniValue;
 }
 
 static UniValue walletpassphrasechange(const Config &config,
                                        const JSONRPCRequest &request) {
     RPCHelpMan{
         "walletpassphrasechange",
         "Changes the wallet passphrase from 'oldpassphrase' to "
         "'newpassphrase'.\n",
         {
             {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The current passphrase"},
             {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The new passphrase"},
         },
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{HelpExampleCli("walletpassphrasechange",
                                    "\"old one\" \"new one\"") +
                     HelpExampleRpc("walletpassphrasechange",
                                    "\"old one\", \"new one\"")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     LOCK(pwallet->cs_wallet);
 
     if (!pwallet->IsCrypted()) {
         throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
                            "Error: running with an unencrypted wallet, but "
                            "walletpassphrasechange was called.");
     }
 
     // TODO: get rid of these .c_str() calls by implementing
     // SecureString::operator=(std::string)
     // Alternately, find a way to make request.params[0] mlock()'d to begin
     // with.
     SecureString strOldWalletPass;
     strOldWalletPass.reserve(100);
     strOldWalletPass = request.params[0].get_str().c_str();
 
     SecureString strNewWalletPass;
     strNewWalletPass.reserve(100);
     strNewWalletPass = request.params[1].get_str().c_str();
 
     if (strOldWalletPass.empty() || strNewWalletPass.empty()) {
         throw JSONRPCError(RPC_INVALID_PARAMETER,
                            "passphrase can not be empty");
     }
 
     if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
         throw JSONRPCError(
             RPC_WALLET_PASSPHRASE_INCORRECT,
             "Error: The wallet passphrase entered was incorrect.");
     }
 
     return NullUniValue;
 }
 
 static UniValue walletlock(const Config &config,
                            const JSONRPCRequest &request) {
     RPCHelpMan{
         "walletlock",
         "Removes the wallet encryption key from memory, locking the wallet.\n"
         "After calling this method, you will need to call walletpassphrase "
         "again\n"
         "before being able to call any methods which require the wallet to be "
         "unlocked.\n",
         {},
         RPCResult{RPCResult::Type::NONE, "", ""},
         RPCExamples{
             "\nSet the passphrase for 2 minutes to perform a transaction\n" +
             HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
             "\nPerform a send (requires passphrase set)\n" +
             HelpExampleCli("sendtoaddress",
                            "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") +
             "\nClear the passphrase since we are done before 2 minutes is "
             "up\n" +
             HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("walletlock", "")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     LOCK(pwallet->cs_wallet);
 
     if (!pwallet->IsCrypted()) {
         throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
                            "Error: running with an unencrypted wallet, but "
                            "walletlock was called.");
     }
 
     pwallet->Lock();
     pwallet->nRelockTime = 0;
 
     return NullUniValue;
 }
 
 static UniValue encryptwallet(const Config &config,
                               const JSONRPCRequest &request) {
     RPCHelpMan{
         "encryptwallet",
         "Encrypts the wallet with 'passphrase'. This is for first time "
         "encryption.\n"
         "After this, any calls that interact with private keys such as sending "
         "or signing \n"
         "will require the passphrase to be set prior the making these calls.\n"
         "Use the walletpassphrase call for this, and then walletlock call.\n"
         "If the wallet is already encrypted, use the walletpassphrasechange "
         "call.\n",
         {
             {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO,
              "The pass phrase to encrypt the wallet with. It must be at least "
              "1 character, but should be long."},
         },
         RPCResult{RPCResult::Type::STR, "",
                   "A string with further instructions"},
         RPCExamples{
             "\nEncrypt your wallet\n" +
             HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
             "\nNow set the passphrase to use the wallet, such as for signing "
             "or sending bitcoin\n" +
             HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
             "\nNow we can do something like sign\n" +
             HelpExampleCli("signmessage", "\"address\" \"test message\"") +
             "\nNow lock the wallet again by removing the passphrase\n" +
             HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" +
             HelpExampleRpc("encryptwallet", "\"my pass phrase\"")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     LOCK(pwallet->cs_wallet);
 
     if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
         throw JSONRPCError(
             RPC_WALLET_ENCRYPTION_FAILED,
             "Error: wallet does not contain private keys, nothing to encrypt.");
     }
 
     if (pwallet->IsCrypted()) {
         throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
                            "Error: running with an encrypted wallet, but "
                            "encryptwallet was called.");
     }
 
     // TODO: get rid of this .c_str() by implementing
     // SecureString::operator=(std::string)
     // Alternately, find a way to make request.params[0] mlock()'d to begin
     // with.
     SecureString strWalletPass;
     strWalletPass.reserve(100);
     strWalletPass = request.params[0].get_str().c_str();
 
     if (strWalletPass.empty()) {
         throw JSONRPCError(RPC_INVALID_PARAMETER,
                            "passphrase can not be empty");
     }
 
     if (!pwallet->EncryptWallet(strWalletPass)) {
         throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED,
                            "Error: Failed to encrypt the wallet.");
     }
 
     return "wallet encrypted; The keypool has been flushed and a new HD seed "
            "was generated (if you are using HD). You need to make a new "
            "backup.";
 }
 
 static UniValue lockunspent(const Config &config,
                             const JSONRPCRequest &request) {
     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,
                 /* default */ "empty array",
                 "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}]\"")},
     }
         .Check(request);
 
     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 = find_value(o, "vout").get_int();
         if (nOutput < 0) {
             throw JSONRPCError(RPC_INVALID_PARAMETER,
                                "Invalid parameter, vout must be positive");
         }
 
         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 UniValue listlockunspent(const Config &config,
                                 const JSONRPCRequest &request) {
     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", "")},
     }
         .Check(request);
 
     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 UniValue settxfee(const Config &config, const JSONRPCRequest &request) {
     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")},
     }
         .Check(request);
 
     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 UniValue getbalances(const Config &config,
                             const JSONRPCRequest &request) {
     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", "")},
     }
         .Check(request);
 
     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 = wallet.GetBalance();
     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 = wallet.GetBalance(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 UniValue getwalletinfo(const Config &config,
                               const JSONRPCRequest &request) {
     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]"},
                  }},
                 {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", "")},
     }
         .Check(request);
 
     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 = pwallet->GetBalance();
     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 UniValue listwalletdir(const Config &config,
                               const JSONRPCRequest &request) {
     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", "")},
     }
         .Check(request);
 
     UniValue wallets(UniValue::VARR);
     for (const auto &path : ListWalletDir()) {
         UniValue wallet(UniValue::VOBJ);
         wallet.pushKV("name", path.string());
         wallets.push_back(wallet);
     }
 
     UniValue result(UniValue::VOBJ);
     result.pushKV("wallets", wallets);
     return result;
 }
 
 static UniValue listwallets(const Config &config,
                             const JSONRPCRequest &request) {
     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", "")},
     }
         .Check(request);
 
     UniValue obj(UniValue::VARR);
 
     for (const std::shared_ptr<CWallet> &wallet : GetWallets()) {
         LOCK(wallet->cs_wallet);
         obj.push_back(wallet->GetName());
     }
 
     return obj;
 }
 
 static UniValue loadwallet(const Config &config,
                            const JSONRPCRequest &request) {
     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, /* default */ "null",
              "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\"")},
     }
         .Check(request);
 
     WalletContext &context = EnsureWalletContext(request.context);
     const std::string name(request.params[0].get_str());
 
     DatabaseOptions options;
     DatabaseStatus status;
     options.require_existing = true;
     bilingual_str error;
     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());
     std::shared_ptr<CWallet> const wallet = LoadWallet(
         *context.chain, name, load_on_start, options, status, error, warnings);
     if (!wallet) {
         // Map bad format to not found, since bad format is returned when the
         // wallet directory exists, but doesn't contain a data file.
         RPCErrorCode code = status == DatabaseStatus::FAILED_NOT_FOUND ||
                                     status == DatabaseStatus::FAILED_BAD_FORMAT
                                 ? RPC_WALLET_NOT_FOUND
                                 : 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 UniValue setwalletflag(const Config &config,
                               const JSONRPCRequest &request) {
     std::string flags = "";
     for (auto &it : WALLET_FLAG_MAP) {
         if (it.second & MUTABLE_WALLET_FLAGS) {
             flags += (flags == "" ? "" : ", ") + it.first;
         }
     }
     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, /* 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\"")},
     }
         .Check(request);
 
     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 UniValue createwallet(const Config &config,
                              const JSONRPCRequest &request) {
     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, /* default */ "false",
              "Disable the possibility of private keys (only watchonlys are "
              "possible in this mode)."},
             {"blank", RPCArg::Type::BOOL, /* 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, /* default */ "false",
              "Keep track of coin reuse, and treat dirty and clean coins "
              "differently with privacy considerations in mind."},
             {"descriptors", RPCArg::Type::BOOL, /* default */ "false",
              "Create a native descriptor wallet. The wallet will use "
              "descriptors internally to handle address creation"},
             {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null",
              "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\"")},
     }
         .Check(request);
 
     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 UniValue unloadwallet(const Config &config,
                              const JSONRPCRequest &request) {
     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,
              /* default */ "the wallet name from the RPC request",
              "The name of the wallet to unload."},
             {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null",
              "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")},
     }
         .Check(request);
 
     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 UniValue listunspent(const Config &config,
                             const JSONRPCRequest &request) {
     const auto &ticker = Currency::get().ticker;
     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, /* default */ "1",
              "The minimum confirmations to filter"},
             {"maxconf", RPCArg::Type::NUM, /* default */ "9999999",
              "The maximum confirmations to filter"},
             {
                 "addresses",
                 RPCArg::Type::ARR,
                 /* default */ "empty array",
                 "The bitcoin addresses to filter",
                 {
                     {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
                      "bitcoin address"},
                 },
             },
             {"include_unsafe", RPCArg::Type::BOOL, /* 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, /* default */ "0",
                   "Minimum value of each UTXO in " + ticker + ""},
                  {"maximumAmount", RPCArg::Type::AMOUNT,
                   /* default */ "unlimited",
                   "Maximum value of each UTXO in " + ticker + ""},
                  {"maximumCount", RPCArg::Type::NUM, /* default */ "unlimited",
                   "Maximum number of UTXOs"},
                  {"minimumSumAmount", RPCArg::Type::AMOUNT,
                   /* default */ "unlimited",
                   "Minimum sum value of all UTXOs in " + ticker + ""},
              },
              "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::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 and unconfirmed replacement "
                       "transactions 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 } ")},
     }
         .Check(request);
 
     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].get_int();
     }
 
     int nMaxDepth = 9999999;
     if (!request.params[1].isNull()) {
         RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
         nMaxDepth = request.params[1].get_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"].get_int64();
         }
     }
 
     // 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;
         LOCK(pwallet->cs_wallet);
         pwallet->AvailableCoins(vecOutputs, !include_unsafe, &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(boost::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, 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)},
                     {"changeAddress", UniValueType(UniValue::VSTR)},
                     {"changePosition", UniValueType(UniValue::VNUM)},
                     {"includeWatching", UniValueType(UniValue::VBOOL)},
                     {"lockUnspents", UniValueType(UniValue::VBOOL)},
                     // will be checked below
                     {"feeRate", UniValueType()},
                     {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
                 },
                 true, true);
 
             if (options.exists("add_inputs")) {
                 coinControl.m_add_inputs = options["add_inputs"].get_bool();
             }
 
             if (options.exists("changeAddress")) {
                 CTxDestination dest =
                     DecodeDestination(options["changeAddress"].get_str(),
                                       pwallet->GetChainParams());
 
                 if (!IsValidDestination(dest)) {
                     throw JSONRPCError(
                         RPC_INVALID_ADDRESS_OR_KEY,
                         "changeAddress must be a valid bitcoin address");
                 }
 
                 coinControl.destChange = dest;
             }
 
             if (options.exists("changePosition")) {
                 change_position = options["changePosition"].get_int();
             }
 
             coinControl.fAllowWatchOnly =
                 ParseIncludeWatchonly(options["includeWatching"], *pwallet);
 
             if (options.exists("lockUnspents")) {
                 lockUnspents = options["lockUnspents"].get_bool();
             }
 
             if (options.exists("feeRate")) {
                 coinControl.m_feerate =
                     CFeeRate(AmountFromValue(options["feeRate"]));
                 coinControl.fOverrideFeeRate = true;
             }
 
             if (options.exists("subtractFeeFromOutputs")) {
                 subtractFeeFromOutputs =
                     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].get_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 (!pwallet->FundTransaction(tx, fee_out, change_position, error,
                                   lockUnspents, setSubtractFeeFromOutputs,
                                   coinControl)) {
         throw JSONRPCError(RPC_WALLET_ERROR, error.original);
     }
 }
 
 static UniValue fundrawtransaction(const Config &config,
                                    const JSONRPCRequest &request) {
     const auto &ticker = Currency::get().ticker;
     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, /* default */ "true",
                   "For a transaction with existing inputs, automatically "
                   "include more if they are not enough."},
                  {"changeAddress", RPCArg::Type::STR,
                   /* default */ "pool address",
                   "The bitcoin address to receive the change"},
                  {"changePosition", RPCArg::Type::NUM, /* default */ "",
                   "The index of the change output"},
                  {"includeWatching", RPCArg::Type::BOOL,
                   /* default */ "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, /* default */ "false",
                   "Lock selected unspent outputs"},
                  {"feeRate", RPCArg::Type::AMOUNT, /* default */
                   "not set: makes wallet determine the fee",
                   "Set a specific fee rate in " + ticker + "/kB"},
                  {
                      "subtractFeeFromOutputs",
                      RPCArg::Type::ARR,
                      /* default */ "empty array",
                      "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."},
                      },
                  },
              },
              "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\\\":0.01}\"") +
             "\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\"")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()});
 
     // 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;
 }
 
 UniValue signrawtransactionwithwallet(const Config &config,
                                       const JSONRPCRequest &request) {
     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, /* 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\"")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     const CWallet *const pwallet = wallet.get();
 
     RPCTypeCheck(request.params,
                  {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true);
 
     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;
 }
 
 UniValue rescanblockchain(const Config &config, const JSONRPCRequest &request) {
     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, /* 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")},
     }
         .Check(request);
 
     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].get_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].get_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 boost::static_visitor<UniValue> {
 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 = boost::apply_visitor(*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(boost::apply_visitor(
         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;
 }
 
 UniValue getaddressinfo(const Config &config, const JSONRPCRequest &request) {
     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)},
     }
         .Check(request);
 
     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", pwallet->IsChange(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;
 }
 
 UniValue getaddressesbylabel(const Config &config,
                              const JSONRPCRequest &request) {
     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\"")},
     }
         .Check(request);
 
     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.
             bool unique = addresses.emplace(address).second;
             CHECK_NONFATAL(unique);
             // 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;
 }
 
 UniValue listlabels(const Config &config, const JSONRPCRequest &request) {
     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")},
     }
         .Check(request);
 
     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 UniValue sethdseed(const Config &config, const JSONRPCRequest &request) {
     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, /* 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, /* default */ "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\"")},
     }
         .Check(request);
 
     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 UniValue walletprocesspsbt(const Config &config,
                                   const JSONRPCRequest &request) {
     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, /* default */ "true",
              "Also sign the transaction when updating"},
             {"sighashtype", RPCArg::Type::STR, /* 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, /* 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\"")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     const CWallet *const pwallet = wallet.get();
 
     RPCTypeCheck(request.params,
                  {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR});
 
     // 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 UniValue walletcreatefundedpsbt(const Config &config,
                                        const JSONRPCRequest &request) {
     const auto &ticker = Currency::get().ticker;
     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::NO,
                 "The inputs. 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,
                              /* default */
                              "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"},
                         },
                     },
                 },
             },
             {"locktime", RPCArg::Type::NUM, /* 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, /* default */ "false",
                   "If inputs are specified, automatically include more if they "
                   "are not enough."},
                  {"changeAddress", RPCArg::Type::STR_HEX,
                   /* default */ "pool address",
                   "The bitcoin address to receive the change"},
                  {"changePosition", RPCArg::Type::NUM,
                   /* default */ "random", "The index of the change output"},
                  {"includeWatching", RPCArg::Type::BOOL,
                   /* default */ "true for watch-only wallets, otherwise false",
                   "Also select inputs which are watch only"},
                  {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false",
                   "Lock selected unspent outputs"},
                  {"feeRate", RPCArg::Type::AMOUNT, /* default */
                   "not set: makes wallet determine the fee",
                   "Set a specific fee rate in " + ticker + "/kB"},
                  {
                      "subtractFeeFromOutputs",
                      RPCArg::Type::ARR,
                      /* default */ "empty array",
                      "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."},
                      },
                  },
              },
              "options"},
             {"bip32derivs", RPCArg::Type::BOOL, /* 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\\\"}]\"")},
     }
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     RPCTypeCheck(request.params,
                  {UniValue::VARR,
                   UniValueType(), // ARR or OBJ, checked later
                   UniValue::VNUM, UniValue::VOBJ},
                  true);
 
     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 UniValue upgradewallet(const Config &config,
                               const JSONRPCRequest &request) {
     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,
                  /* default */ strprintf("%d", 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")}}
         .Check(request);
 
     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     if (!wallet) {
         return NullUniValue;
     }
     CWallet *const pwallet = wallet.get();
 
     RPCTypeCheck(request.params, {UniValue::VNUM}, true);
 
     EnsureWalletIsUnlocked(pwallet);
 
     int version = 0;
     if (!request.params[0].isNull()) {
         version = request.params[0].get_int();
     }
 
     bilingual_str error;
     std::vector<bilingual_str> warnings;
     if (!pwallet->UpgradeWallet(version, error, warnings)) {
         throw JSONRPCError(RPC_WALLET_ERROR, error.original);
     }
     return error.original;
 }
 
 Span<const CRPCCommand> GetWalletRPCCommands() {
     // clang-format off
     static const CRPCCommand commands[] = {
         //  category            name                            actor (function)              argNames
         //  ------------------- ------------------------        ----------------------        ----------
         { "rawtransactions",    "fundrawtransaction",           fundrawtransaction,           {"hexstring","options"} },
         { "wallet",             "abandontransaction",           abandontransaction,           {"txid"} },
         { "wallet",             "addmultisigaddress",           addmultisigaddress,           {"nrequired","keys","label"} },
         { "wallet",             "backupwallet",                 backupwallet,                 {"destination"} },
         { "wallet",             "createwallet",                 createwallet,                 {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors", "load_on_startup"} },
         { "wallet",             "encryptwallet",                encryptwallet,                {"passphrase"} },
         { "wallet",             "getaddressesbylabel",          getaddressesbylabel,          {"label"} },
         { "wallet",             "getaddressinfo",               getaddressinfo,               {"address"} },
         { "wallet",             "getbalance",                   getbalance,                   {"dummy","minconf","include_watchonly","avoid_reuse"} },
         { "wallet",             "getnewaddress",                getnewaddress,                {"label", "address_type"} },
         { "wallet",             "getrawchangeaddress",          getrawchangeaddress,          {"address_type"} },
         { "wallet",             "getreceivedbyaddress",         getreceivedbyaddress,         {"address","minconf"} },
         { "wallet",             "getreceivedbylabel",           getreceivedbylabel,           {"label","minconf"} },
         { "wallet",             "gettransaction",               gettransaction,               {"txid","include_watchonly","verbose"} },
         { "wallet",             "getunconfirmedbalance",         getunconfirmedbalance,         {} },
         { "wallet",             "getbalances",                  getbalances,                  {} },
         { "wallet",             "getwalletinfo",                getwalletinfo,                {} },
         { "wallet",             "keypoolrefill",                 keypoolrefill,                 {"newsize"} },
         { "wallet",             "listaddressgroupings",         listaddressgroupings,         {} },
         { "wallet",             "listlabels",                   listlabels,                   {"purpose"} },
         { "wallet",             "listlockunspent",              listlockunspent,              {} },
         { "wallet",             "listreceivedbyaddress",        listreceivedbyaddress,        {"minconf","include_empty","include_watchonly","address_filter"} },
         { "wallet",             "listreceivedbylabel",          listreceivedbylabel,          {"minconf","include_empty","include_watchonly"} },
         { "wallet",             "listsinceblock",               listsinceblock,               {"blockhash","target_confirmations","include_watchonly","include_removed"} },
         { "wallet",             "listtransactions",             listtransactions,             {"label|dummy","count","skip","include_watchonly"} },
         { "wallet",             "listunspent",                  listunspent,                  {"minconf","maxconf","addresses","include_unsafe","query_options"} },
         { "wallet",             "listwalletdir",                listwalletdir,                {} },
         { "wallet",             "listwallets",                  listwallets,                  {} },
         { "wallet",             "loadwallet",                   loadwallet,                   {"filename", "load_on_startup"} },
         { "wallet",             "lockunspent",                  lockunspent,                  {"unlock","transactions"} },
         { "wallet",             "rescanblockchain",             rescanblockchain,             {"start_height", "stop_height"} },
         { "wallet",             "sendmany",                     sendmany,                     {"dummy","amounts","minconf","comment","subtractfeefrom"} },
         { "wallet",             "sendtoaddress",                sendtoaddress,                {"address","amount","comment","comment_to","subtractfeefromamount","avoid_reuse"} },
         { "wallet",             "sethdseed",                    sethdseed,                    {"newkeypool","seed"} },
         { "wallet",             "setlabel",                     setlabel,                     {"address","label"} },
         { "wallet",             "settxfee",                     settxfee,                     {"amount"} },
         { "wallet",             "setwalletflag",                 setwalletflag,                 {"flag","value"} },
         { "wallet",             "signmessage",                  signmessage,                  {"address","message"} },
         { "wallet",             "signrawtransactionwithwallet", signrawtransactionwithwallet, {"hextring","prevtxs","sighashtype"} },
         { "wallet",             "unloadwallet",                 unloadwallet,                 {"wallet_name", "load_on_startup"} },
         { "wallet",             "upgradewallet",                upgradewallet,                {"version"} },
         { "wallet",             "walletcreatefundedpsbt",       walletcreatefundedpsbt,       {"inputs","outputs","locktime","options","bip32derivs"} },
         { "wallet",             "walletlock",                   walletlock,                   {} },
         { "wallet",             "walletpassphrase",             walletpassphrase,             {"passphrase","timeout"} },
         { "wallet",             "walletpassphrasechange",       walletpassphrasechange,       {"oldpassphrase","newpassphrase"} },
         { "wallet",             "walletprocesspsbt",            walletprocesspsbt,            {"psbt","sign","sighashtype","bip32derivs"} },
     };
     // clang-format on
 
     return MakeSpan(commands);
 }
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index dc6c2d993..852d1bb4a 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5097 +1,5088 @@
 // Copyright (c) 2009-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 <wallet/wallet.h>
 
 #include <chain.h>
 #include <chainparams.h>
 #include <config.h>
 #include <consensus/consensus.h>
 #include <consensus/validation.h>
 #include <fs.h>
 #include <interfaces/wallet.h>
 #include <key.h>
 #include <key_io.h>
 #include <policy/mempool.h>
 #include <policy/policy.h>
 #include <primitives/transaction.h>
 #include <random.h>
 #include <script/descriptor.h>
 #include <script/script.h>
 #include <script/sighashtype.h>
 #include <script/sign.h>
 #include <script/signingprovider.h>
 #include <txmempool.h>
 #include <univalue.h>
 #include <util/bip32.h>
 #include <util/check.h>
 #include <util/error.h>
 #include <util/moneystr.h>
 #include <util/string.h>
 #include <util/translation.h>
 #include <wallet/coincontrol.h>
 #include <wallet/fees.h>
 
 #include <boost/algorithm/string/replace.hpp>
 
 using interfaces::FoundBlock;
 
 const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
     {WALLET_FLAG_AVOID_REUSE,
      "You need to rescan the blockchain in order to correctly mark used "
      "destinations in the past. Until this is done, some destinations may "
      "be considered unused, even if the opposite is the case."},
 };
 
 static RecursiveMutex cs_wallets;
 static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
 static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
 
 bool AddWalletSetting(interfaces::Chain &chain,
                       const std::string &wallet_name) {
     util::SettingsValue setting_value = chain.getRwSetting("wallet");
     if (!setting_value.isArray()) {
         setting_value.setArray();
     }
     for (const util::SettingsValue &value : setting_value.getValues()) {
         if (value.isStr() && value.get_str() == wallet_name) {
             return true;
         }
     }
     setting_value.push_back(wallet_name);
     return chain.updateRwSetting("wallet", setting_value);
 }
 
 bool RemoveWalletSetting(interfaces::Chain &chain,
                          const std::string &wallet_name) {
     util::SettingsValue setting_value = chain.getRwSetting("wallet");
     if (!setting_value.isArray()) {
         return true;
     }
     util::SettingsValue new_value(util::SettingsValue::VARR);
     for (const util::SettingsValue &value : setting_value.getValues()) {
         if (!value.isStr() || value.get_str() != wallet_name) {
             new_value.push_back(value);
         }
     }
     if (new_value.size() == setting_value.size()) {
         return true;
     }
     return chain.updateRwSetting("wallet", new_value);
 }
 
 static void UpdateWalletSetting(interfaces::Chain &chain,
                                 const std::string &wallet_name,
                                 std::optional<bool> load_on_startup,
                                 std::vector<bilingual_str> &warnings) {
     if (!load_on_startup) {
         return;
     }
     if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) {
         warnings.emplace_back(
             Untranslated("Wallet load on startup setting could not be updated, "
                          "so wallet may not be loaded next node startup."));
     } else if (!load_on_startup.value() &&
                !RemoveWalletSetting(chain, wallet_name)) {
         warnings.emplace_back(
             Untranslated("Wallet load on startup setting could not be updated, "
                          "so wallet may still be loaded next node startup."));
     }
 }
 
 bool AddWallet(const std::shared_ptr<CWallet> &wallet) {
     LOCK(cs_wallets);
     assert(wallet);
     std::vector<std::shared_ptr<CWallet>>::const_iterator i =
         std::find(vpwallets.begin(), vpwallets.end(), wallet);
     if (i != vpwallets.end()) {
         return false;
     }
     vpwallets.push_back(wallet);
     wallet->ConnectScriptPubKeyManNotifiers();
     return true;
 }
 
 bool RemoveWallet(const std::shared_ptr<CWallet> &wallet,
                   std::optional<bool> load_on_start,
                   std::vector<bilingual_str> &warnings) {
     assert(wallet);
 
     interfaces::Chain &chain = wallet->chain();
     std::string name = wallet->GetName();
 
     // Unregister with the validation interface which also drops shared ponters.
     wallet->m_chain_notifications_handler.reset();
     LOCK(cs_wallets);
     std::vector<std::shared_ptr<CWallet>>::iterator i =
         std::find(vpwallets.begin(), vpwallets.end(), wallet);
     if (i == vpwallets.end()) {
         return false;
     }
     vpwallets.erase(i);
 
     // Write the wallet setting
     UpdateWalletSetting(chain, name, load_on_start, warnings);
 
     return true;
 }
 
 bool RemoveWallet(const std::shared_ptr<CWallet> &wallet,
                   std::optional<bool> load_on_start) {
     std::vector<bilingual_str> warnings;
     return RemoveWallet(wallet, load_on_start, warnings);
 }
 
 std::vector<std::shared_ptr<CWallet>> GetWallets() {
     LOCK(cs_wallets);
     return vpwallets;
 }
 
 std::shared_ptr<CWallet> GetWallet(const std::string &name) {
     LOCK(cs_wallets);
     for (const std::shared_ptr<CWallet> &wallet : vpwallets) {
         if (wallet->GetName() == name) {
             return wallet;
         }
     }
     return nullptr;
 }
 
 std::unique_ptr<interfaces::Handler>
 HandleLoadWallet(LoadWalletFn load_wallet) {
     LOCK(cs_wallets);
     auto it = g_load_wallet_fns.emplace(g_load_wallet_fns.end(),
                                         std::move(load_wallet));
     return interfaces::MakeHandler([it] {
         LOCK(cs_wallets);
         g_load_wallet_fns.erase(it);
     });
 }
 
 static Mutex g_loading_wallet_mutex;
 static Mutex g_wallet_release_mutex;
 static std::condition_variable g_wallet_release_cv;
 static std::set<std::string>
     g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
 static std::set<std::string>
     g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
 
 // Custom deleter for shared_ptr<CWallet>.
 static void ReleaseWallet(CWallet *wallet) {
     const std::string name = wallet->GetName();
     wallet->WalletLogPrintf("Releasing wallet\n");
     wallet->Flush();
     delete wallet;
     // Wallet is now released, notify UnloadWallet, if any.
     {
         LOCK(g_wallet_release_mutex);
         if (g_unloading_wallet_set.erase(name) == 0) {
             // UnloadWallet was not called for this wallet, all done.
             return;
         }
     }
     g_wallet_release_cv.notify_all();
 }
 
 void UnloadWallet(std::shared_ptr<CWallet> &&wallet) {
     // Mark wallet for unloading.
     const std::string name = wallet->GetName();
     {
         LOCK(g_wallet_release_mutex);
         auto it = g_unloading_wallet_set.insert(name);
         assert(it.second);
     }
     // The wallet can be in use so it's not possible to explicitly unload here.
     // Notify the unload intent so that all remaining shared pointers are
     // released.
     wallet->NotifyUnload();
 
     // Time to ditch our shared_ptr and wait for ReleaseWallet call.
     wallet.reset();
     {
         WAIT_LOCK(g_wallet_release_mutex, lock);
         while (g_unloading_wallet_set.count(name) == 1) {
             g_wallet_release_cv.wait(lock);
         }
     }
 }
 
 static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
 
 namespace {
 std::shared_ptr<CWallet>
 LoadWalletInternal(interfaces::Chain &chain, const std::string &name,
                    std::optional<bool> load_on_start,
                    const DatabaseOptions &options, DatabaseStatus &status,
                    bilingual_str &error, std::vector<bilingual_str> &warnings) {
     try {
         std::unique_ptr<WalletDatabase> database =
             MakeWalletDatabase(name, options, status, error);
         if (!database) {
             error = Untranslated("Wallet file verification failed.") +
                     Untranslated(" ") + error;
             return nullptr;
         }
 
         std::shared_ptr<CWallet> wallet =
             CWallet::Create(chain, name, std::move(database),
                             options.create_flags, error, warnings);
         if (!wallet) {
             error = Untranslated("Wallet loading failed.") + Untranslated(" ") +
                     error;
             return nullptr;
         }
         AddWallet(wallet);
         wallet->postInitProcess();
 
         // Write the wallet setting
         UpdateWalletSetting(chain, name, load_on_start, warnings);
 
         return wallet;
     } catch (const std::runtime_error &e) {
         error = Untranslated(e.what());
         return nullptr;
     }
 }
 } // namespace
 
 std::shared_ptr<CWallet>
 LoadWallet(interfaces::Chain &chain, const std::string &name,
            std::optional<bool> load_on_start, const DatabaseOptions &options,
            DatabaseStatus &status, bilingual_str &error,
            std::vector<bilingual_str> &warnings) {
     auto result = WITH_LOCK(g_loading_wallet_mutex,
                             return g_loading_wallet_set.insert(name));
     if (!result.second) {
         error = Untranslated("Wallet already being loading.");
         return nullptr;
     }
     auto wallet = LoadWalletInternal(chain, name, load_on_start, options,
                                      status, error, warnings);
     WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first));
     return wallet;
 }
 
 std::shared_ptr<CWallet>
 CreateWallet(interfaces::Chain &chain, const std::string &name,
              std::optional<bool> load_on_start, const DatabaseOptions &options,
              DatabaseStatus &status, bilingual_str &error,
              std::vector<bilingual_str> &warnings) {
     uint64_t wallet_creation_flags = options.create_flags;
     const SecureString &passphrase = options.create_passphrase;
 
     // Indicate that the wallet is actually supposed to be blank and not just
     // blank to make it encrypted
     bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
 
     // Born encrypted wallets need to be created blank first.
     if (!passphrase.empty()) {
         wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
     }
 
-    // Check the wallet file location
-    if (fs::symlink_status(
-            fs::absolute(name.empty() ? "wallet.dat" : name, GetWalletDir()))
-            .type() != fs::file_not_found) {
-        error = strprintf(Untranslated("Wallet %s already exists."), name);
-        status = DatabaseStatus::FAILED_CREATE;
-        return nullptr;
-    }
-
     // Wallet::Verify will check if we're trying to create a wallet with a
     // duplicate name.
     std::unique_ptr<WalletDatabase> database =
         MakeWalletDatabase(name, options, status, error);
     if (!database) {
         error = Untranslated("Wallet file verification failed.") +
                 Untranslated(" ") + error;
         status = DatabaseStatus::FAILED_VERIFY;
         return nullptr;
     }
 
     // Do not allow a passphrase when private keys are disabled
     if (!passphrase.empty() &&
         (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
         error = Untranslated(
             "Passphrase provided but private keys are disabled. A passphrase "
             "is only used to encrypt private keys, so cannot be used for "
             "wallets with private keys disabled.");
         status = DatabaseStatus::FAILED_CREATE;
         return nullptr;
     }
 
     // Make the wallet
     std::shared_ptr<CWallet> wallet =
         CWallet::Create(chain, name, std::move(database), wallet_creation_flags,
                         error, warnings);
     if (!wallet) {
         error =
             Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
         status = DatabaseStatus::FAILED_CREATE;
         return nullptr;
     }
 
     // Encrypt the wallet
     if (!passphrase.empty() &&
         !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
         if (!wallet->EncryptWallet(passphrase)) {
             error =
                 Untranslated("Error: Wallet created but failed to encrypt.");
             status = DatabaseStatus::FAILED_ENCRYPT;
             return nullptr;
         }
         if (!create_blank) {
             // Unlock the wallet
             if (!wallet->Unlock(passphrase)) {
                 error = Untranslated(
                     "Error: Wallet was encrypted but could not be unlocked");
                 status = DatabaseStatus::FAILED_ENCRYPT;
                 return nullptr;
             }
 
             // Set a seed for the wallet
             {
                 LOCK(wallet->cs_wallet);
                 if (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
                     wallet->SetupDescriptorScriptPubKeyMans();
                 } else {
                     for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) {
                         if (!spk_man->SetupGeneration()) {
                             error = Untranslated(
                                 "Unable to generate initial keys");
                             status = DatabaseStatus::FAILED_CREATE;
                             return nullptr;
                         }
                     }
                 }
             }
 
             // Relock the wallet
             wallet->Lock();
         }
     }
     AddWallet(wallet);
     wallet->postInitProcess();
 
     // Write the wallet settings
     UpdateWalletSetting(chain, name, load_on_start, warnings);
 
     status = DatabaseStatus::SUCCESS;
     return wallet;
 }
 
 /** @defgroup mapWallet
  *
  * @{
  */
 
 std::string COutput::ToString() const {
     return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i,
                      nDepth, FormatMoney(tx->tx->vout[i].nValue));
 }
 
 const CChainParams &CWallet::GetChainParams() const {
     // Get CChainParams from interfaces::Chain, unless wallet doesn't have a
     // chain (i.e. bitcoin-wallet), in which case return global Params()
     return m_chain ? m_chain->params() : Params();
 }
 
 const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const {
     AssertLockHeld(cs_wallet);
     std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid);
     if (it == mapWallet.end()) {
         return nullptr;
     }
 
     return &(it->second);
 }
 
 void CWallet::UpgradeKeyMetadata() {
     if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
         return;
     }
 
     auto spk_man = GetLegacyScriptPubKeyMan();
     if (!spk_man) {
         return;
     }
 
     spk_man->UpgradeKeyMetadata();
     SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
 }
 
 bool CWallet::Unlock(const SecureString &strWalletPassphrase,
                      bool accept_no_keys) {
     CCrypter crypter;
     CKeyingMaterial _vMasterKey;
 
     {
         LOCK(cs_wallet);
         for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
             if (!crypter.SetKeyFromPassphrase(
                     strWalletPassphrase, pMasterKey.second.vchSalt,
                     pMasterKey.second.nDeriveIterations,
                     pMasterKey.second.nDerivationMethod)) {
                 return false;
             }
             if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey,
                                  _vMasterKey)) {
                 // try another master key
                 continue;
             }
             if (Unlock(_vMasterKey, accept_no_keys)) {
                 // Now that we've unlocked, upgrade the key metadata
                 UpgradeKeyMetadata();
                 return true;
             }
         }
     }
 
     return false;
 }
 
 bool CWallet::ChangeWalletPassphrase(
     const SecureString &strOldWalletPassphrase,
     const SecureString &strNewWalletPassphrase) {
     bool fWasLocked = IsLocked();
 
     LOCK(cs_wallet);
     Lock();
 
     CCrypter crypter;
     CKeyingMaterial _vMasterKey;
     for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
         if (!crypter.SetKeyFromPassphrase(
                 strOldWalletPassphrase, pMasterKey.second.vchSalt,
                 pMasterKey.second.nDeriveIterations,
                 pMasterKey.second.nDerivationMethod)) {
             return false;
         }
 
         if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) {
             return false;
         }
 
         if (Unlock(_vMasterKey)) {
             int64_t nStartTime = GetTimeMillis();
             crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
                                          pMasterKey.second.vchSalt,
                                          pMasterKey.second.nDeriveIterations,
                                          pMasterKey.second.nDerivationMethod);
             pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(
                 pMasterKey.second.nDeriveIterations *
                 (100 / ((double)(GetTimeMillis() - nStartTime))));
 
             nStartTime = GetTimeMillis();
             crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
                                          pMasterKey.second.vchSalt,
                                          pMasterKey.second.nDeriveIterations,
                                          pMasterKey.second.nDerivationMethod);
             pMasterKey.second.nDeriveIterations =
                 (pMasterKey.second.nDeriveIterations +
                  static_cast<unsigned int>(
                      pMasterKey.second.nDeriveIterations * 100 /
                      double(GetTimeMillis() - nStartTime))) /
                 2;
 
             if (pMasterKey.second.nDeriveIterations < 25000) {
                 pMasterKey.second.nDeriveIterations = 25000;
             }
 
             WalletLogPrintf(
                 "Wallet passphrase changed to an nDeriveIterations of %i\n",
                 pMasterKey.second.nDeriveIterations);
 
             if (!crypter.SetKeyFromPassphrase(
                     strNewWalletPassphrase, pMasterKey.second.vchSalt,
                     pMasterKey.second.nDeriveIterations,
                     pMasterKey.second.nDerivationMethod)) {
                 return false;
             }
 
             if (!crypter.Encrypt(_vMasterKey,
                                  pMasterKey.second.vchCryptedKey)) {
                 return false;
             }
 
             WalletBatch(*database).WriteMasterKey(pMasterKey.first,
                                                   pMasterKey.second);
             if (fWasLocked) {
                 Lock();
             }
 
             return true;
         }
     }
 
     return false;
 }
 
 void CWallet::chainStateFlushed(const CBlockLocator &loc) {
     WalletBatch batch(*database);
     batch.WriteBestBlock(loc);
 }
 
 void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch *batch_in,
                             bool fExplicit) {
     LOCK(cs_wallet);
     if (nWalletVersion >= nVersion) {
         return;
     }
 
     // When doing an explicit upgrade, if we pass the max version permitted,
     // upgrade all the way.
     if (fExplicit && nVersion > nWalletMaxVersion) {
         nVersion = FEATURE_LATEST;
     }
 
     nWalletVersion = nVersion;
 
     if (nVersion > nWalletMaxVersion) {
         nWalletMaxVersion = nVersion;
     }
 
     WalletBatch *batch = batch_in ? batch_in : new WalletBatch(*database);
     if (nWalletVersion > 40000) {
         batch->WriteMinVersion(nWalletVersion);
     }
     if (!batch_in) {
         delete batch;
     }
 }
 
 bool CWallet::SetMaxVersion(int nVersion) {
     LOCK(cs_wallet);
 
     // Cannot downgrade below current version
     if (nWalletVersion > nVersion) {
         return false;
     }
 
     nWalletMaxVersion = nVersion;
 
     return true;
 }
 
 std::set<TxId> CWallet::GetConflicts(const TxId &txid) const {
     std::set<TxId> result;
     AssertLockHeld(cs_wallet);
 
     std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid);
     if (it == mapWallet.end()) {
         return result;
     }
 
     const CWalletTx &wtx = it->second;
 
     std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
 
     for (const CTxIn &txin : wtx.tx->vin) {
         if (mapTxSpends.count(txin.prevout) <= 1) {
             // No conflict if zero or one spends.
             continue;
         }
 
         range = mapTxSpends.equal_range(txin.prevout);
         for (TxSpends::const_iterator _it = range.first; _it != range.second;
              ++_it) {
             result.insert(_it->second);
         }
     }
 
     return result;
 }
 
 bool CWallet::HasWalletSpend(const TxId &txid) const {
     AssertLockHeld(cs_wallet);
     auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
     return (iter != mapTxSpends.end() && iter->first.GetTxId() == txid);
 }
 
 void CWallet::Flush() {
     database->Flush();
 }
 
 void CWallet::Close() {
     database->Close();
 }
 
 void CWallet::SyncMetaData(
     std::pair<TxSpends::iterator, TxSpends::iterator> range) {
     // We want all the wallet transactions in range to have the same metadata as
     // the oldest (smallest nOrderPos).
     // So: find smallest nOrderPos:
 
     int nMinOrderPos = std::numeric_limits<int>::max();
     const CWalletTx *copyFrom = nullptr;
     for (TxSpends::iterator it = range.first; it != range.second; ++it) {
         const CWalletTx *wtx = &mapWallet.at(it->second);
         if (wtx->nOrderPos < nMinOrderPos) {
             nMinOrderPos = wtx->nOrderPos;
             copyFrom = wtx;
         }
     }
 
     if (!copyFrom) {
         return;
     }
 
     // Now copy data from copyFrom to rest:
     for (TxSpends::iterator it = range.first; it != range.second; ++it) {
         const TxId &txid = it->second;
         CWalletTx *copyTo = &mapWallet.at(txid);
         if (copyFrom == copyTo) {
             continue;
         }
 
         assert(
             copyFrom &&
             "Oldest wallet transaction in range assumed to have been found.");
 
         if (!copyFrom->IsEquivalentTo(*copyTo)) {
             continue;
         }
 
         copyTo->mapValue = copyFrom->mapValue;
         copyTo->vOrderForm = copyFrom->vOrderForm;
         // fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied
         // on purpose.
         copyTo->nTimeSmart = copyFrom->nTimeSmart;
         copyTo->fFromMe = copyFrom->fFromMe;
         // nOrderPos not copied on purpose cached members not copied on purpose.
     }
 }
 
 /**
  * Outpoint is spent if any non-conflicted transaction, spends it:
  */
 bool CWallet::IsSpent(const COutPoint &outpoint) const {
     std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range =
         mapTxSpends.equal_range(outpoint);
 
     for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
         const TxId &wtxid = it->second;
         std::map<TxId, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
         if (mit != mapWallet.end()) {
             int depth = mit->second.GetDepthInMainChain();
             if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) {
                 // Spent
                 return true;
             }
         }
     }
 
     return false;
 }
 
 void CWallet::AddToSpends(const COutPoint &outpoint, const TxId &wtxid) {
     mapTxSpends.insert(std::make_pair(outpoint, wtxid));
 
     setLockedCoins.erase(outpoint);
 
     std::pair<TxSpends::iterator, TxSpends::iterator> range;
     range = mapTxSpends.equal_range(outpoint);
     SyncMetaData(range);
 }
 
 void CWallet::AddToSpends(const TxId &wtxid) {
     auto it = mapWallet.find(wtxid);
     assert(it != mapWallet.end());
     CWalletTx &thisTx = it->second;
     // Coinbases don't spend anything!
     if (thisTx.IsCoinBase()) {
         return;
     }
 
     for (const CTxIn &txin : thisTx.tx->vin) {
         AddToSpends(txin.prevout, wtxid);
     }
 }
 
 bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) {
     if (IsCrypted()) {
         return false;
     }
 
     CKeyingMaterial _vMasterKey;
 
     _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
     GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
 
     CMasterKey kMasterKey;
 
     kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
     GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
 
     CCrypter crypter;
     int64_t nStartTime = GetTimeMillis();
     crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000,
                                  kMasterKey.nDerivationMethod);
     kMasterKey.nDeriveIterations = static_cast<unsigned int>(
         2500000 / double(GetTimeMillis() - nStartTime));
 
     nStartTime = GetTimeMillis();
     crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
                                  kMasterKey.nDeriveIterations,
                                  kMasterKey.nDerivationMethod);
     kMasterKey.nDeriveIterations =
         (kMasterKey.nDeriveIterations +
          static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 /
                                    double(GetTimeMillis() - nStartTime))) /
         2;
 
     if (kMasterKey.nDeriveIterations < 25000) {
         kMasterKey.nDeriveIterations = 25000;
     }
 
     WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n",
                     kMasterKey.nDeriveIterations);
 
     if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
                                       kMasterKey.nDeriveIterations,
                                       kMasterKey.nDerivationMethod)) {
         return false;
     }
 
     if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) {
         return false;
     }
 
     {
         LOCK(cs_wallet);
         mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
         WalletBatch *encrypted_batch = new WalletBatch(*database);
         if (!encrypted_batch->TxnBegin()) {
             delete encrypted_batch;
             encrypted_batch = nullptr;
             return false;
         }
         encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
 
         for (const auto &spk_man_pair : m_spk_managers) {
             auto spk_man = spk_man_pair.second.get();
             if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) {
                 encrypted_batch->TxnAbort();
                 delete encrypted_batch;
                 encrypted_batch = nullptr;
                 // We now probably have half of our keys encrypted in memory,
                 // and half not... die and let the user reload the unencrypted
                 // wallet.
                 assert(false);
             }
         }
 
         // Encryption was introduced in version 0.4.0
         SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true);
 
         if (!encrypted_batch->TxnCommit()) {
             delete encrypted_batch;
             encrypted_batch = nullptr;
             // We now have keys encrypted in memory, but not on disk...
             // die to avoid confusion and let the user reload the unencrypted
             // wallet.
             assert(false);
         }
 
         delete encrypted_batch;
         encrypted_batch = nullptr;
 
         Lock();
         Unlock(strWalletPassphrase);
 
         // If we are using descriptors, make new descriptors with a new seed
         if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) &&
             !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
             SetupDescriptorScriptPubKeyMans();
         } else if (auto spk_man = GetLegacyScriptPubKeyMan()) {
             // if we are using HD, replace the HD seed with a new one
             if (spk_man->IsHDEnabled()) {
                 if (!spk_man->SetupGeneration(true)) {
                     return false;
                 }
             }
         }
         Lock();
 
         // Need to completely rewrite the wallet file; if we don't, bdb might
         // keep bits of the unencrypted private key in slack space in the
         // database file.
         database->Rewrite();
 
         // BDB seems to have a bad habit of writing old data into
         // slack space in .dat files; that is bad if the old data is
         // unencrypted private keys. So:
         database->ReloadDbEnv();
     }
 
     NotifyStatusChanged(this);
     return true;
 }
 
 DBErrors CWallet::ReorderTransactions() {
     LOCK(cs_wallet);
     WalletBatch batch(*database);
 
     // Old wallets didn't have any defined order for transactions. Probably a
     // bad idea to change the output of this.
 
     // First: get all CWalletTx into a sorted-by-time
     // multimap.
     TxItems txByTime;
 
     for (auto &entry : mapWallet) {
         CWalletTx *wtx = &entry.second;
         txByTime.insert(std::make_pair(wtx->nTimeReceived, wtx));
     }
 
     nOrderPosNext = 0;
     std::vector<int64_t> nOrderPosOffsets;
     for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) {
         CWalletTx *const pwtx = (*it).second;
         int64_t &nOrderPos = pwtx->nOrderPos;
 
         if (nOrderPos == -1) {
             nOrderPos = nOrderPosNext++;
             nOrderPosOffsets.push_back(nOrderPos);
 
             if (!batch.WriteTx(*pwtx)) {
                 return DBErrors::LOAD_FAIL;
             }
         } else {
             int64_t nOrderPosOff = 0;
             for (const int64_t &nOffsetStart : nOrderPosOffsets) {
                 if (nOrderPos >= nOffsetStart) {
                     ++nOrderPosOff;
                 }
             }
 
             nOrderPos += nOrderPosOff;
             nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
 
             if (!nOrderPosOff) {
                 continue;
             }
 
             // Since we're changing the order, write it back.
             if (!batch.WriteTx(*pwtx)) {
                 return DBErrors::LOAD_FAIL;
             }
         }
     }
 
     batch.WriteOrderPosNext(nOrderPosNext);
 
     return DBErrors::LOAD_OK;
 }
 
 int64_t CWallet::IncOrderPosNext(WalletBatch *batch) {
     AssertLockHeld(cs_wallet);
     int64_t nRet = nOrderPosNext++;
     if (batch) {
         batch->WriteOrderPosNext(nOrderPosNext);
     } else {
         WalletBatch(*database).WriteOrderPosNext(nOrderPosNext);
     }
 
     return nRet;
 }
 
 void CWallet::MarkDirty() {
     LOCK(cs_wallet);
     for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
         item.second.MarkDirty();
     }
 }
 
 void CWallet::SetSpentKeyState(WalletBatch &batch, const TxId &txid,
                                unsigned int n, bool used,
                                std::set<CTxDestination> &tx_destinations) {
     AssertLockHeld(cs_wallet);
     const CWalletTx *srctx = GetWalletTx(txid);
     if (!srctx) {
         return;
     }
 
     CTxDestination dst;
     if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
         if (IsMine(dst)) {
             if (used && !GetDestData(dst, "used", nullptr)) {
                 // p for "present", opposite of absent (null)
                 if (AddDestData(batch, dst, "used", "p")) {
                     tx_destinations.insert(dst);
                 }
             } else if (!used && GetDestData(dst, "used", nullptr)) {
                 EraseDestData(batch, dst, "used");
             }
         }
     }
 }
 
 bool CWallet::IsSpentKey(const TxId &txid, unsigned int n) const {
     AssertLockHeld(cs_wallet);
     const CWalletTx *srctx = GetWalletTx(txid);
     if (srctx) {
         assert(srctx->tx->vout.size() > n);
         CTxDestination dest;
         if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
             return false;
         }
         if (GetDestData(dest, "used", nullptr)) {
             return true;
         }
         if (IsLegacy()) {
             LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan();
             assert(spk_man != nullptr);
             for (const auto &keyid :
                  GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
                 PKHash pkh_dest(keyid);
                 if (GetDestData(pkh_dest, "used", nullptr)) {
                     return true;
                 }
             }
         }
     }
     return false;
 }
 
 CWalletTx *CWallet::AddToWallet(CTransactionRef tx,
                                 const CWalletTx::Confirmation &confirm,
                                 const UpdateWalletTxFn &update_wtx,
                                 bool fFlushOnClose) {
     LOCK(cs_wallet);
 
     WalletBatch batch(*database, "r+", fFlushOnClose);
 
     const TxId &txid = tx->GetId();
 
     if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
         // Mark used destinations
         std::set<CTxDestination> tx_destinations;
 
         for (const CTxIn &txin : tx->vin) {
             const COutPoint &op = txin.prevout;
             SetSpentKeyState(batch, op.GetTxId(), op.GetN(), true,
                              tx_destinations);
         }
 
         MarkDestinationsDirty(tx_destinations);
     }
 
     // Inserts only if not already there, returns tx inserted or tx found.
     auto ret =
         mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid),
                           std::forward_as_tuple(this, tx));
     CWalletTx &wtx = (*ret.first).second;
     bool fInsertedNew = ret.second;
     bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew);
     if (fInsertedNew) {
         wtx.m_confirm = confirm;
         wtx.nTimeReceived = chain().getAdjustedTime();
         wtx.nOrderPos = IncOrderPosNext(&batch);
         wtx.m_it_wtxOrdered =
             wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
         wtx.nTimeSmart = ComputeTimeSmart(wtx);
         AddToSpends(txid);
     }
 
     if (!fInsertedNew) {
         if (confirm.status != wtx.m_confirm.status) {
             wtx.m_confirm.status = confirm.status;
             wtx.m_confirm.nIndex = confirm.nIndex;
             wtx.m_confirm.hashBlock = confirm.hashBlock;
             wtx.m_confirm.block_height = confirm.block_height;
             fUpdated = true;
         } else {
             assert(wtx.m_confirm.nIndex == confirm.nIndex);
             assert(wtx.m_confirm.hashBlock == confirm.hashBlock);
             assert(wtx.m_confirm.block_height == confirm.block_height);
         }
     }
 
     //// debug print
     WalletLogPrintf("AddToWallet %s  %s%s\n", txid.ToString(),
                     (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
 
     // Write to disk
     if ((fInsertedNew || fUpdated) && !batch.WriteTx(wtx)) {
         return nullptr;
     }
 
     // Break debit/credit balance caches:
     wtx.MarkDirty();
 
     // Notify UI of new or updated transaction.
     NotifyTransactionChanged(this, txid, fInsertedNew ? CT_NEW : CT_UPDATED);
 
 #if defined(HAVE_SYSTEM)
     // Notify an external script when a wallet transaction comes in or is
     // updated.
     std::string strCmd = gArgs.GetArg("-walletnotify", "");
 
     if (!strCmd.empty()) {
         boost::replace_all(strCmd, "%s", txid.GetHex());
 #ifndef WIN32
         // Substituting the wallet name isn't currently supported on windows
         // because windows shell escaping has not been implemented yet:
         // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875
         // A few ways it could be implemented in the future are described in:
         // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094
         boost::replace_all(strCmd, "%w", ShellEscape(GetName()));
 #endif
 
         std::thread t(runCommand, strCmd);
         // Thread runs free.
         t.detach();
     }
 #endif
 
     return &wtx;
 }
 
 bool CWallet::LoadToWallet(const TxId &txid, const UpdateWalletTxFn &fill_wtx) {
     const auto &ins =
         mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid),
                           std::forward_as_tuple(this, nullptr));
     CWalletTx &wtx = ins.first->second;
     if (!fill_wtx(wtx, ins.second)) {
         return false;
     }
     // If wallet doesn't have a chain (e.g wallet-tool), don't bother to update
     // txn.
     if (HaveChain()) {
         std::optional<int> block_height =
             chain().getBlockHeight(wtx.m_confirm.hashBlock);
         if (block_height) {
             // Update cached block height variable since it not stored in the
             // serialized transaction.
             wtx.m_confirm.block_height = *block_height;
         } else if (wtx.isConflicted() || wtx.isConfirmed()) {
             // If tx block (or conflicting block) was reorged out of chain
             // while the wallet was shutdown, change tx status to UNCONFIRMED
             // and reset block height, hash, and index. ABANDONED tx don't have
             // associated blocks and don't need to be updated. The case where a
             // transaction was reorged out while online and then reconfirmed
             // while offline is covered by the rescan logic.
             wtx.setUnconfirmed();
             wtx.m_confirm.hashBlock = BlockHash();
             wtx.m_confirm.block_height = 0;
             wtx.m_confirm.nIndex = 0;
         }
     }
     if (/* insertion took place */ ins.second) {
         wtx.m_it_wtxOrdered =
             wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
     }
     AddToSpends(txid);
     for (const CTxIn &txin : wtx.tx->vin) {
         auto it = mapWallet.find(txin.prevout.GetTxId());
         if (it != mapWallet.end()) {
             CWalletTx &prevtx = it->second;
             if (prevtx.isConflicted()) {
                 MarkConflicted(prevtx.m_confirm.hashBlock,
                                prevtx.m_confirm.block_height, wtx.GetId());
             }
         }
     }
     return true;
 }
 
 bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx,
                                        CWalletTx::Confirmation confirm,
                                        bool fUpdate) {
     const CTransaction &tx = *ptx;
     AssertLockHeld(cs_wallet);
 
     if (!confirm.hashBlock.IsNull()) {
         for (const CTxIn &txin : tx.vin) {
             std::pair<TxSpends::const_iterator, TxSpends::const_iterator>
                 range = mapTxSpends.equal_range(txin.prevout);
             while (range.first != range.second) {
                 if (range.first->second != tx.GetId()) {
                     WalletLogPrintf(
                         "Transaction %s (in block %s) conflicts with wallet "
                         "transaction %s (both spend %s:%i)\n",
                         tx.GetId().ToString(), confirm.hashBlock.ToString(),
                         range.first->second.ToString(),
                         range.first->first.GetTxId().ToString(),
                         range.first->first.GetN());
                     MarkConflicted(confirm.hashBlock, confirm.block_height,
                                    range.first->second);
                 }
                 range.first++;
             }
         }
     }
 
     bool fExisted = mapWallet.count(tx.GetId()) != 0;
     if (fExisted && !fUpdate) {
         return false;
     }
     if (fExisted || IsMine(tx) || IsFromMe(tx)) {
         /**
          * Check if any keys in the wallet keypool that were supposed to be
          * unused have appeared in a new transaction. If so, remove those keys
          * from the keypool. This can happen when restoring an old wallet backup
          * that does not contain the mostly recently created transactions from
          * newer versions of the wallet.
          */
 
         // loop though all outputs
         for (const CTxOut &txout : tx.vout) {
             for (const auto &spk_man_pair : m_spk_managers) {
                 spk_man_pair.second->MarkUnusedAddresses(txout.scriptPubKey);
             }
         }
 
         // Block disconnection override an abandoned tx as unconfirmed
         // which means user may have to call abandontransaction again
         return AddToWallet(MakeTransactionRef(tx), confirm,
                            /* update_wtx= */ nullptr,
                            /* fFlushOnClose= */ false);
     }
     return false;
 }
 
 bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const {
     LOCK(cs_wallet);
     const CWalletTx *wtx = GetWalletTx(txid);
     return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 &&
            !wtx->InMempool();
 }
 
 void CWallet::MarkInputsDirty(const CTransactionRef &tx) {
     for (const CTxIn &txin : tx->vin) {
         auto it = mapWallet.find(txin.prevout.GetTxId());
         if (it != mapWallet.end()) {
             it->second.MarkDirty();
         }
     }
 }
 
 bool CWallet::AbandonTransaction(const TxId &txid) {
     LOCK(cs_wallet);
 
     WalletBatch batch(*database, "r+");
 
     std::set<TxId> todo;
     std::set<TxId> done;
 
     // Can't mark abandoned if confirmed or in mempool
     auto it = mapWallet.find(txid);
     assert(it != mapWallet.end());
     CWalletTx &origtx = it->second;
     if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
         return false;
     }
 
     todo.insert(txid);
 
     while (!todo.empty()) {
         const TxId now = *todo.begin();
         todo.erase(now);
         done.insert(now);
         it = mapWallet.find(now);
         assert(it != mapWallet.end());
         CWalletTx &wtx = it->second;
         int currentconfirm = wtx.GetDepthInMainChain();
         // If the orig tx was not in block, none of its spends can be.
         assert(currentconfirm <= 0);
         // If (currentconfirm < 0) {Tx and spends are already conflicted, no
         // need to abandon}
         if (currentconfirm == 0 && !wtx.isAbandoned()) {
             // If the orig tx was not in block/mempool, none of its spends can
             // be in mempool.
             assert(!wtx.InMempool());
             wtx.setAbandoned();
             wtx.MarkDirty();
             batch.WriteTx(wtx);
             NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED);
             // Iterate over all its outputs, and mark transactions in the wallet
             // that spend them abandoned too.
             TxSpends::const_iterator iter =
                 mapTxSpends.lower_bound(COutPoint(now, 0));
             while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) {
                 if (!done.count(iter->second)) {
                     todo.insert(iter->second);
                 }
                 iter++;
             }
 
             // If a transaction changes 'conflicted' state, that changes the
             // balance available of the outputs it spends. So force those to be
             // recomputed.
             MarkInputsDirty(wtx.tx);
         }
     }
 
     return true;
 }
 
 void CWallet::MarkConflicted(const BlockHash &hashBlock, int conflicting_height,
                              const TxId &txid) {
     LOCK(cs_wallet);
 
     int conflictconfirms =
         (m_last_block_processed_height - conflicting_height + 1) * -1;
 
     // If number of conflict confirms cannot be determined, this means that the
     // block is still unknown or not yet part of the main chain, for example
     // when loading the wallet during a reindex. Do nothing in that case.
     if (conflictconfirms >= 0) {
         return;
     }
 
     // Do not flush the wallet here for performance reasons.
     WalletBatch batch(*database, "r+", false);
 
     std::set<TxId> todo;
     std::set<TxId> done;
 
     todo.insert(txid);
 
     while (!todo.empty()) {
         const TxId now = *todo.begin();
         todo.erase(now);
         done.insert(now);
         auto it = mapWallet.find(now);
         assert(it != mapWallet.end());
         CWalletTx &wtx = it->second;
         int currentconfirm = wtx.GetDepthInMainChain();
         if (conflictconfirms < currentconfirm) {
             // Block is 'more conflicted' than current confirm; update.
             // Mark transaction as conflicted with this block.
             wtx.m_confirm.nIndex = 0;
             wtx.m_confirm.hashBlock = hashBlock;
             wtx.m_confirm.block_height = conflicting_height;
             wtx.setConflicted();
             wtx.MarkDirty();
             batch.WriteTx(wtx);
             // Iterate over all its outputs, and mark transactions in the wallet
             // that spend them conflicted too.
             TxSpends::const_iterator iter =
                 mapTxSpends.lower_bound(COutPoint(now, 0));
             while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) {
                 if (!done.count(iter->second)) {
                     todo.insert(iter->second);
                 }
                 iter++;
             }
             // If a transaction changes 'conflicted' state, that changes the
             // balance available of the outputs it spends. So force those to be
             // recomputed.
             MarkInputsDirty(wtx.tx);
         }
     }
 }
 
 void CWallet::SyncTransaction(const CTransactionRef &ptx,
                               CWalletTx::Confirmation confirm, bool update_tx) {
     if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx)) {
         // Not one of ours
         return;
     }
 
     // If a transaction changes 'conflicted' state, that changes the balance
     // available of the outputs it spends. So force those to be
     // recomputed, also:
     MarkInputsDirty(ptx);
 }
 
 void CWallet::transactionAddedToMempool(const CTransactionRef &tx) {
     LOCK(cs_wallet);
 
     SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block_height */ 0,
                          BlockHash(), /* nIndex */ 0});
 
     auto it = mapWallet.find(tx->GetId());
     if (it != mapWallet.end()) {
         it->second.fInMempool = true;
     }
 }
 
 void CWallet::transactionRemovedFromMempool(const CTransactionRef &tx,
                                             MemPoolRemovalReason reason) {
     LOCK(cs_wallet);
     auto it = mapWallet.find(tx->GetId());
     if (it != mapWallet.end()) {
         it->second.fInMempool = false;
     }
     // Handle transactions that were removed from the mempool because they
     // conflict with transactions in a newly connected block.
     if (reason == MemPoolRemovalReason::CONFLICT) {
         // Call SyncNotifications, so external -walletnotify notifications will
         // be triggered for these transactions. Set Status::UNCONFIRMED instead
         // of Status::CONFLICTED for a few reasons:
         //
         // 1. The transactionRemovedFromMempool callback does not currently
         //    provide the conflicting block's hash and height, and for backwards
         //    compatibility reasons it may not be not safe to store conflicted
         //    wallet transactions with a null block hash. See
         //    https://github.com/bitcoin/bitcoin/pull/18600#discussion_r420195993.
         // 2. For most of these transactions, the wallet's internal conflict
         //    detection in the blockConnected handler will subsequently call
         //    MarkConflicted and update them with CONFLICTED status anyway. This
         //    applies to any wallet transaction that has inputs spent in the
         //    block, or that has ancestors in the wallet with inputs spent by
         //    the block.
         // 3. Longstanding behavior since the sync implementation in
         //    https://github.com/bitcoin/bitcoin/pull/9371 and the prior sync
         //    implementation before that was to mark these transactions
         //    unconfirmed rather than conflicted.
         //
         // Nothing described above should be seen as an unchangeable requirement
         // when improving this code in the future. The wallet's heuristics for
         // distinguishing between conflicted and unconfirmed transactions are
         // imperfect, and could be improved in general, see
         // https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
         SyncTransaction(tx,
                         {CWalletTx::Status::UNCONFIRMED, /* block height  */ 0,
                          BlockHash(), /* index */ 0});
     }
 }
 
 void CWallet::blockConnected(const CBlock &block, int height) {
     const BlockHash &block_hash = block.GetHash();
     LOCK(cs_wallet);
 
     m_last_block_processed_height = height;
     m_last_block_processed = block_hash;
     for (size_t index = 0; index < block.vtx.size(); index++) {
         SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height,
                                            block_hash, int(index)});
         transactionRemovedFromMempool(block.vtx[index],
                                       MemPoolRemovalReason::BLOCK);
     }
 }
 
 void CWallet::blockDisconnected(const CBlock &block, int height) {
     LOCK(cs_wallet);
 
     // At block disconnection, this will change an abandoned transaction to
     // be unconfirmed, whether or not the transaction is added back to the
     // mempool. User may have to call abandontransaction again. It may be
     // addressed in the future with a stickier abandoned state or even removing
     // abandontransaction call.
     m_last_block_processed_height = height - 1;
     m_last_block_processed = block.hashPrevBlock;
     for (const CTransactionRef &ptx : block.vtx) {
         SyncTransaction(ptx,
                         {CWalletTx::Status::UNCONFIRMED, /* block_height */ 0,
                          BlockHash(), /* nIndex */ 0});
     }
 }
 
 void CWallet::updatedBlockTip() {
     m_best_block_time = GetTime();
 }
 
 void CWallet::BlockUntilSyncedToCurrentChain() const {
     AssertLockNotHeld(cs_wallet);
     // Skip the queue-draining stuff if we know we're caught up with
     // chainActive.Tip(), otherwise put a callback in the validation interface
     // queue and wait for the queue to drain enough to execute it (indicating we
     // are caught up at least with the time we entered this function).
     const BlockHash last_block_hash =
         WITH_LOCK(cs_wallet, return m_last_block_processed);
     chain().waitForNotificationsIfTipChanged(last_block_hash);
 }
 
 isminetype CWallet::IsMine(const CTxIn &txin) const {
     AssertLockHeld(cs_wallet);
     std::map<TxId, CWalletTx>::const_iterator mi =
         mapWallet.find(txin.prevout.GetTxId());
     if (mi != mapWallet.end()) {
         const CWalletTx &prev = (*mi).second;
         if (txin.prevout.GetN() < prev.tx->vout.size()) {
             return IsMine(prev.tx->vout[txin.prevout.GetN()]);
         }
     }
 
     return ISMINE_NO;
 }
 
 // Note that this function doesn't distinguish between a 0-valued input, and a
 // not-"is mine" (according to the filter) input.
 Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const {
     LOCK(cs_wallet);
     std::map<TxId, CWalletTx>::const_iterator mi =
         mapWallet.find(txin.prevout.GetTxId());
     if (mi != mapWallet.end()) {
         const CWalletTx &prev = (*mi).second;
         if (txin.prevout.GetN() < prev.tx->vout.size()) {
             if (IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter) {
                 return prev.tx->vout[txin.prevout.GetN()].nValue;
             }
         }
     }
 
     return Amount::zero();
 }
 
 isminetype CWallet::IsMine(const CTxOut &txout) const {
     AssertLockHeld(cs_wallet);
     return IsMine(txout.scriptPubKey);
 }
 
 isminetype CWallet::IsMine(const CTxDestination &dest) const {
     AssertLockHeld(cs_wallet);
     return IsMine(GetScriptForDestination(dest));
 }
 
 isminetype CWallet::IsMine(const CScript &script) const {
     AssertLockHeld(cs_wallet);
     isminetype result = ISMINE_NO;
     for (const auto &spk_man_pair : m_spk_managers) {
         result = std::max(result, spk_man_pair.second->IsMine(script));
     }
     return result;
 }
 
 Amount CWallet::GetCredit(const CTxOut &txout,
                           const isminefilter &filter) const {
     if (!MoneyRange(txout.nValue)) {
         throw std::runtime_error(std::string(__func__) +
                                  ": value out of range");
     }
     LOCK(cs_wallet);
     return (IsMine(txout) & filter) ? txout.nValue : Amount::zero();
 }
 
 bool CWallet::IsChange(const CTxOut &txout) const {
     return IsChange(txout.scriptPubKey);
 }
 
 bool CWallet::IsChange(const CScript &script) const {
     // TODO: fix handling of 'change' outputs. The assumption is that any
     // payment to a script that is ours, but is not in the address book is
     // change. That assumption is likely to break when we implement
     // multisignature wallets that return change back into a
     // multi-signature-protected address; a better way of identifying which
     // outputs are 'the send' and which are 'the change' will need to be
     // implemented (maybe extend CWalletTx to remember which output, if any, was
     // change).
     AssertLockHeld(cs_wallet);
     if (IsMine(script)) {
         CTxDestination address;
         if (!ExtractDestination(script, address)) {
             return true;
         }
         if (!FindAddressBookEntry(address)) {
             return true;
         }
     }
 
     return false;
 }
 
 Amount CWallet::GetChange(const CTxOut &txout) const {
     AssertLockHeld(cs_wallet);
     if (!MoneyRange(txout.nValue)) {
         throw std::runtime_error(std::string(__func__) +
                                  ": value out of range");
     }
 
     return (IsChange(txout) ? txout.nValue : Amount::zero());
 }
 
 bool CWallet::IsMine(const CTransaction &tx) const {
     AssertLockHeld(cs_wallet);
     for (const CTxOut &txout : tx.vout) {
         if (IsMine(txout)) {
             return true;
         }
     }
 
     return false;
 }
 
 bool CWallet::IsFromMe(const CTransaction &tx) const {
     return GetDebit(tx, ISMINE_ALL) > Amount::zero();
 }
 
 Amount CWallet::GetDebit(const CTransaction &tx,
                          const isminefilter &filter) const {
     Amount nDebit = Amount::zero();
     for (const CTxIn &txin : tx.vin) {
         nDebit += GetDebit(txin, filter);
         if (!MoneyRange(nDebit)) {
             throw std::runtime_error(std::string(__func__) +
                                      ": value out of range");
         }
     }
 
     return nDebit;
 }
 
 bool CWallet::IsAllFromMe(const CTransaction &tx,
                           const isminefilter &filter) const {
     LOCK(cs_wallet);
 
     for (const CTxIn &txin : tx.vin) {
         auto mi = mapWallet.find(txin.prevout.GetTxId());
         if (mi == mapWallet.end()) {
             // Any unknown inputs can't be from us.
             return false;
         }
 
         const CWalletTx &prev = (*mi).second;
 
         if (txin.prevout.GetN() >= prev.tx->vout.size()) {
             // Invalid input!
             return false;
         }
 
         if (!(IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter)) {
             return false;
         }
     }
 
     return true;
 }
 
 Amount CWallet::GetCredit(const CTransaction &tx,
                           const isminefilter &filter) const {
     Amount nCredit = Amount::zero();
     for (const CTxOut &txout : tx.vout) {
         nCredit += GetCredit(txout, filter);
         if (!MoneyRange(nCredit)) {
             throw std::runtime_error(std::string(__func__) +
                                      ": value out of range");
         }
     }
 
     return nCredit;
 }
 
 Amount CWallet::GetChange(const CTransaction &tx) const {
     LOCK(cs_wallet);
     Amount nChange = Amount::zero();
     for (const CTxOut &txout : tx.vout) {
         nChange += GetChange(txout);
         if (!MoneyRange(nChange)) {
             throw std::runtime_error(std::string(__func__) +
                                      ": value out of range");
         }
     }
 
     return nChange;
 }
 
 bool CWallet::IsHDEnabled() const {
     // All Active ScriptPubKeyMans must be HD for this to be true
     bool result = true;
     for (const auto &spk_man : GetActiveScriptPubKeyMans()) {
         result &= spk_man->IsHDEnabled();
     }
     return result;
 }
 
 bool CWallet::CanGetAddresses(bool internal) const {
     LOCK(cs_wallet);
     if (m_spk_managers.empty()) {
         return false;
     }
     for (OutputType t : OUTPUT_TYPES) {
         auto spk_man = GetScriptPubKeyMan(t, internal);
         if (spk_man && spk_man->CanGetAddresses(internal)) {
             return true;
         }
     }
     return false;
 }
 
 void CWallet::SetWalletFlag(uint64_t flags) {
     LOCK(cs_wallet);
     m_wallet_flags |= flags;
     if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) {
         throw std::runtime_error(std::string(__func__) +
                                  ": writing wallet flags failed");
     }
 }
 
 void CWallet::UnsetWalletFlag(uint64_t flag) {
     WalletBatch batch(*database);
     UnsetWalletFlagWithDB(batch, flag);
 }
 
 void CWallet::UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag) {
     LOCK(cs_wallet);
     m_wallet_flags &= ~flag;
     if (!batch.WriteWalletFlags(m_wallet_flags)) {
         throw std::runtime_error(std::string(__func__) +
                                  ": writing wallet flags failed");
     }
 }
 
 void CWallet::UnsetBlankWalletFlag(WalletBatch &batch) {
     UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
 }
 
 bool CWallet::IsWalletFlagSet(uint64_t flag) const {
     return (m_wallet_flags & flag);
 }
 
 bool CWallet::LoadWalletFlags(uint64_t flags) {
     LOCK(cs_wallet);
     if (((flags & KNOWN_WALLET_FLAGS) >> 32) ^ (flags >> 32)) {
         // contains unknown non-tolerable wallet flags
         return false;
     }
     m_wallet_flags = flags;
 
     return true;
 }
 
 bool CWallet::AddWalletFlags(uint64_t flags) {
     LOCK(cs_wallet);
     // We should never be writing unknown non-tolerable wallet flags
     assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32));
     if (!WalletBatch(*database).WriteWalletFlags(flags)) {
         throw std::runtime_error(std::string(__func__) +
                                  ": writing wallet flags failed");
     }
 
     return LoadWalletFlags(flags);
 }
 
 int64_t CWalletTx::GetTxTime() const {
     int64_t n = nTimeSmart;
     return n ? n : nTimeReceived;
 }
 
 // Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
 // or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
 bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout,
                              bool use_max_sig) const {
     // Fill in dummy signatures for fee calculation.
     const CScript &scriptPubKey = txout.scriptPubKey;
     SignatureData sigdata;
 
     std::unique_ptr<SigningProvider> provider =
         GetSolvingProvider(scriptPubKey);
     if (!provider) {
         // We don't know about this scriptpbuKey;
         return false;
     }
 
     if (!ProduceSignature(*provider,
                           use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR
                                       : DUMMY_SIGNATURE_CREATOR,
                           scriptPubKey, sigdata)) {
         return false;
     }
 
     UpdateInput(tx_in, sigdata);
     return true;
 }
 
 // Helper for producing a bunch of max-sized low-S low-R signatures (eg 71
 // bytes)
 bool CWallet::DummySignTx(CMutableTransaction &txNew,
                           const std::vector<CTxOut> &txouts,
                           bool use_max_sig) const {
     // Fill in dummy signatures for fee calculation.
     int nIn = 0;
     for (const auto &txout : txouts) {
         if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) {
             return false;
         }
 
         nIn++;
     }
     return true;
 }
 
 bool CWallet::ImportScripts(const std::set<CScript> scripts,
                             int64_t timestamp) {
     auto spk_man = GetLegacyScriptPubKeyMan();
     if (!spk_man) {
         return false;
     }
     LOCK(spk_man->cs_KeyStore);
     return spk_man->ImportScripts(scripts, timestamp);
 }
 
 bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map,
                              const int64_t timestamp) {
     auto spk_man = GetLegacyScriptPubKeyMan();
     if (!spk_man) {
         return false;
     }
     LOCK(spk_man->cs_KeyStore);
     return spk_man->ImportPrivKeys(privkey_map, timestamp);
 }
 
 bool CWallet::ImportPubKeys(
     const std::vector<CKeyID> &ordered_pubkeys,
     const std::map<CKeyID, CPubKey> &pubkey_map,
     const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins,
     const bool add_keypool, const bool internal, const int64_t timestamp) {
     auto spk_man = GetLegacyScriptPubKeyMan();
     if (!spk_man) {
         return false;
     }
     LOCK(spk_man->cs_KeyStore);
     return spk_man->ImportPubKeys(ordered_pubkeys, pubkey_map, key_origins,
                                   add_keypool, internal, timestamp);
 }
 
 bool CWallet::ImportScriptPubKeys(const std::string &label,
                                   const std::set<CScript> &script_pub_keys,
                                   const bool have_solving_data,
                                   const bool apply_label,
                                   const int64_t timestamp) {
     auto spk_man = GetLegacyScriptPubKeyMan();
     if (!spk_man) {
         return false;
     }
     LOCK(spk_man->cs_KeyStore);
     if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data,
                                       timestamp)) {
         return false;
     }
     if (apply_label) {
         WalletBatch batch(*database);
         for (const CScript &script : script_pub_keys) {
             CTxDestination dest;
             ExtractDestination(script, dest);
             if (IsValidDestination(dest)) {
                 SetAddressBookWithDB(batch, dest, label, "receive");
             }
         }
     }
     return true;
 }
 
 int64_t CalculateMaximumSignedTxSize(const CTransaction &tx,
                                      const CWallet *wallet, bool use_max_sig) {
     std::vector<CTxOut> txouts;
     for (auto &input : tx.vin) {
         const auto mi = wallet->mapWallet.find(input.prevout.GetTxId());
         // Can not estimate size without knowing the input details
         if (mi == wallet->mapWallet.end()) {
             return -1;
         }
         assert(input.prevout.GetN() < mi->second.tx->vout.size());
         txouts.emplace_back(mi->second.tx->vout[input.prevout.GetN()]);
     }
     return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
 }
 
 // txouts needs to be in the order of tx.vin
 int64_t CalculateMaximumSignedTxSize(const CTransaction &tx,
                                      const CWallet *wallet,
                                      const std::vector<CTxOut> &txouts,
                                      bool use_max_sig) {
     CMutableTransaction txNew(tx);
     if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
         return -1;
     }
     return GetSerializeSize(txNew, PROTOCOL_VERSION);
 }
 
 int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *wallet,
                                     bool use_max_sig) {
     CMutableTransaction txn;
     txn.vin.push_back(CTxIn(COutPoint()));
     if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
         return -1;
     }
     return GetSerializeSize(txn.vin[0], PROTOCOL_VERSION);
 }
 
 void CWalletTx::GetAmounts(std::list<COutputEntry> &listReceived,
                            std::list<COutputEntry> &listSent, Amount &nFee,
                            const isminefilter &filter) const {
     nFee = Amount::zero();
     listReceived.clear();
     listSent.clear();
 
     // Compute fee:
     Amount nDebit = GetDebit(filter);
     // debit>0 means we signed/sent this transaction.
     if (nDebit > Amount::zero()) {
         Amount nValueOut = tx->GetValueOut();
         nFee = (nDebit - nValueOut);
     }
 
     LOCK(pwallet->cs_wallet);
     // Sent/received.
     for (unsigned int i = 0; i < tx->vout.size(); ++i) {
         const CTxOut &txout = tx->vout[i];
         isminetype fIsMine = pwallet->IsMine(txout);
         // Only need to handle txouts if AT LEAST one of these is true:
         //   1) they debit from us (sent)
         //   2) the output is to us (received)
         if (nDebit > Amount::zero()) {
             // Don't report 'change' txouts
             if (pwallet->IsChange(txout)) {
                 continue;
             }
         } else if (!(fIsMine & filter)) {
             continue;
         }
 
         // In either case, we need to get the destination address.
         CTxDestination address;
 
         if (!ExtractDestination(txout.scriptPubKey, address) &&
             !txout.scriptPubKey.IsUnspendable()) {
             pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown "
                                      "transaction type found, txid %s\n",
                                      this->GetId().ToString());
             address = CNoDestination();
         }
 
         COutputEntry output = {address, txout.nValue, (int)i};
 
         // If we are debited by the transaction, add the output as a "sent"
         // entry.
         if (nDebit > Amount::zero()) {
             listSent.push_back(output);
         }
 
         // If we are receiving the output, add it as a "received" entry.
         if (fIsMine & filter) {
             listReceived.push_back(output);
         }
     }
 }
 
 /**
  * Scan active chain for relevant transactions after importing keys. This should
  * be called whenever new keys are added to the wallet, with the oldest key
  * creation time.
  *
  * @return Earliest timestamp that could be successfully scanned from. Timestamp
  * returned will be higher than startTime if relevant blocks could not be read.
  */
 int64_t CWallet::RescanFromTime(int64_t startTime,
                                 const WalletRescanReserver &reserver,
                                 bool update) {
     // Find starting block. May be null if nCreateTime is greater than the
     // highest blockchain timestamp, in which case there is nothing that needs
     // to be scanned.
     int start_height = 0;
     BlockHash start_block;
     bool start = chain().findFirstBlockWithTimeAndHeight(
         startTime - TIMESTAMP_WINDOW, 0,
         FoundBlock().hash(start_block).height(start_height));
     WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__,
                     start ? WITH_LOCK(cs_wallet, return GetLastBlockHeight()) -
                                 start_height + 1
                           : 0);
 
     if (start) {
         // TODO: this should take into account failure by ScanResult::USER_ABORT
         ScanResult result = ScanForWalletTransactions(
             start_block, start_height, {} /* max_height */, reserver, update);
         if (result.status == ScanResult::FAILURE) {
             int64_t time_max;
             CHECK_NONFATAL(chain().findBlock(result.last_failed_block,
                                              FoundBlock().maxTime(time_max)));
             return time_max + TIMESTAMP_WINDOW + 1;
         }
     }
     return startTime;
 }
 
 /**
  * Scan the block chain (starting in start_block) for transactions from or to
  * us. If fUpdate is true, found transactions that already exist in the wallet
  * will be updated.
  *
  * @param[in] start_block Scan starting block. If block is not on the active
  *                        chain, the scan will return SUCCESS immediately.
  * @param[in] start_height Height of start_block
  * @param[in] max_height  Optional max scanning height. If unset there is
  *                        no maximum and scanning can continue to the tip
  *
  * @return ScanResult returning scan information and indicating success or
  *         failure. Return status will be set to SUCCESS if scan was
  *         successful. FAILURE if a complete rescan was not possible (due to
  *         pruning or corruption). USER_ABORT if the rescan was aborted before
  *         it could complete.
  *
  * @pre Caller needs to make sure start_block (and the optional stop_block) are
  * on the main chain after to the addition of any new keys you want to detect
  * transactions for.
  */
 CWallet::ScanResult CWallet::ScanForWalletTransactions(
     const BlockHash &start_block, int start_height,
     std::optional<int> max_height, const WalletRescanReserver &reserver,
     bool fUpdate) {
     int64_t nNow = GetTime();
     int64_t start_time = GetTimeMillis();
 
     assert(reserver.isReserved());
 
     BlockHash block_hash = start_block;
     ScanResult result;
 
     WalletLogPrintf("Rescan started from block %s...\n",
                     start_block.ToString());
 
     fAbortRescan = false;
     // Show rescan progress in GUI as dialog or on splashscreen, if -rescan on
     // startup.
     ShowProgress(
         strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0);
     BlockHash tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
     BlockHash end_hash = tip_hash;
     if (max_height) {
         chain().findAncestorByHeight(tip_hash, *max_height,
                                      FoundBlock().hash(end_hash));
     }
     double progress_begin = chain().guessVerificationProgress(block_hash);
     double progress_end = chain().guessVerificationProgress(end_hash);
     double progress_current = progress_begin;
     int block_height = start_height;
     while (!fAbortRescan && !chain().shutdownRequested()) {
         if (progress_end - progress_begin > 0.0) {
             m_scanning_progress = (progress_current - progress_begin) /
                                   (progress_end - progress_begin);
         } else {
             // avoid divide-by-zero for single block scan range (i.e. start and
             // stop hashes are equal)
             m_scanning_progress = 0;
         }
         if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
             ShowProgress(
                 strprintf("%s " + _("Rescanning...").translated,
                           GetDisplayName()),
                 std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
         }
         if (GetTime() >= nNow + 60) {
             nNow = GetTime();
             WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n",
                             block_height, progress_current);
         }
 
         CBlock block;
         bool next_block;
         BlockHash next_block_hash;
         bool reorg = false;
         if (chain().findBlock(block_hash, FoundBlock().data(block)) &&
             !block.IsNull()) {
             LOCK(cs_wallet);
             next_block = chain().findNextBlock(
                 block_hash, block_height, FoundBlock().hash(next_block_hash),
                 &reorg);
             if (reorg) {
                 // Abort scan if current block is no longer active, to prevent
                 // marking transactions as coming from the wrong block.
                 // TODO: This should return success instead of failure, see
                 // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518
                 result.last_failed_block = block_hash;
                 result.status = ScanResult::FAILURE;
                 break;
             }
             for (size_t posInBlock = 0; posInBlock < block.vtx.size();
                  ++posInBlock) {
                 CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED,
                                                 block_height, block_hash,
                                                 posInBlock);
                 SyncTransaction(block.vtx[posInBlock],
                                 {CWalletTx::Status::CONFIRMED, block_height,
                                  block_hash, int(posInBlock)},
                                 fUpdate);
             }
             // scan succeeded, record block as most recent successfully
             // scanned
             result.last_scanned_block = block_hash;
             result.last_scanned_height = block_height;
         } else {
             // could not scan block, keep scanning but record this block as
             // the most recent failure
             result.last_failed_block = block_hash;
             result.status = ScanResult::FAILURE;
             next_block = chain().findNextBlock(
                 block_hash, block_height, FoundBlock().hash(next_block_hash),
                 &reorg);
         }
         if (max_height && block_height >= *max_height) {
             break;
         }
         {
             if (!next_block || reorg) {
                 // break successfully when rescan has reached the tip, or
                 // previous block is no longer on the chain due to a reorg
                 break;
             }
 
             // increment block and verification progress
             block_hash = next_block_hash;
             ++block_height;
             progress_current = chain().guessVerificationProgress(block_hash);
 
             // handle updated tip hash
             const BlockHash prev_tip_hash = tip_hash;
             tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
             if (!max_height && prev_tip_hash != tip_hash) {
                 // in case the tip has changed, update progress max
                 progress_end = chain().guessVerificationProgress(tip_hash);
             }
         }
     }
 
     // Hide progress dialog in GUI.
     ShowProgress(
         strprintf("%s " + _("Rescanning...").translated, GetDisplayName()),
         100);
     if (block_height && fAbortRescan) {
         WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n",
                         block_height, progress_current);
         result.status = ScanResult::USER_ABORT;
     } else if (block_height && chain().shutdownRequested()) {
         WalletLogPrintf(
             "Rescan interrupted by shutdown request at block %d. Progress=%f\n",
             block_height, progress_current);
         result.status = ScanResult::USER_ABORT;
     } else {
         WalletLogPrintf("Rescan completed in %15dms\n",
                         GetTimeMillis() - start_time);
     }
     return result;
 }
 
 void CWallet::ReacceptWalletTransactions() {
     // If transactions aren't being broadcasted, don't let them into local
     // mempool either.
     if (!fBroadcastTransactions) {
         return;
     }
 
     std::map<int64_t, CWalletTx *> mapSorted;
 
     // Sort pending wallet transactions based on their initial wallet insertion
     // order.
     for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
         const TxId &wtxid = item.first;
         CWalletTx &wtx = item.second;
         assert(wtx.GetId() == wtxid);
 
         int nDepth = wtx.GetDepthInMainChain();
 
         if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
             mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
         }
     }
 
     // Try to add wallet transactions to memory pool.
     for (const std::pair<const int64_t, CWalletTx *> &item : mapSorted) {
         CWalletTx &wtx = *(item.second);
         std::string unused_err_string;
         wtx.SubmitMemoryPoolAndRelay(unused_err_string, false);
     }
 }
 
 bool CWalletTx::SubmitMemoryPoolAndRelay(std::string &err_string, bool relay) {
     // Can't relay if wallet is not broadcasting
     if (!pwallet->GetBroadcastTransactions()) {
         return false;
     }
     // Don't relay abandoned transactions
     if (isAbandoned()) {
         return false;
     }
     // Don't try to submit coinbase transactions. These would fail anyway but
     // would cause log spam.
     if (IsCoinBase()) {
         return false;
     }
     // Don't try to submit conflicted or confirmed transactions.
     if (GetDepthInMainChain() != 0) {
         return false;
     }
 
     // Submit transaction to mempool for relay
     pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n",
                              GetId().ToString());
     // We must set fInMempool here - while it will be re-set to true by the
     // entered-mempool callback, if we did not there would be a race where a
     // user could call sendmoney in a loop and hit spurious out of funds errors
     // because we think that this newly generated transaction's change is
     // unavailable as we're not yet aware that it is in the mempool.
     //
     // Irrespective of the failure reason, un-marking fInMempool
     // out-of-order is incorrect - it should be unmarked when
     // TransactionRemovedFromMempool fires.
     bool ret = pwallet->chain().broadcastTransaction(
         GetConfig(), tx, pwallet->m_default_max_tx_fee, relay, err_string);
     fInMempool |= ret;
     return ret;
 }
 
 std::set<TxId> CWalletTx::GetConflicts() const {
     std::set<TxId> result;
     if (pwallet != nullptr) {
         const TxId &txid = GetId();
         result = pwallet->GetConflicts(txid);
         result.erase(txid);
     }
 
     return result;
 }
 
 Amount CWalletTx::GetCachableAmount(AmountType type, const isminefilter &filter,
                                     bool recalculate) const {
     auto &amount = m_amounts[type];
     if (recalculate || !amount.m_cached[filter]) {
         amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter)
                                          : pwallet->GetCredit(*tx, filter));
         m_is_cache_empty = false;
     }
     return amount.m_value[filter];
 }
 
 Amount CWalletTx::GetDebit(const isminefilter &filter) const {
     if (tx->vin.empty()) {
         return Amount::zero();
     }
 
     Amount debit = Amount::zero();
     if (filter & ISMINE_SPENDABLE) {
         debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE);
     }
     if (filter & ISMINE_WATCH_ONLY) {
         debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY);
     }
 
     return debit;
 }
 
 Amount CWalletTx::GetCredit(const isminefilter &filter) const {
     // Must wait until coinbase is safely deep enough in the chain before
     // valuing it.
     if (IsImmatureCoinBase()) {
         return Amount::zero();
     }
 
     Amount credit = Amount::zero();
     if (filter & ISMINE_SPENDABLE) {
         // GetBalance can assume transactions in mapWallet won't change.
         credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE);
     }
 
     if (filter & ISMINE_WATCH_ONLY) {
         credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY);
     }
 
     return credit;
 }
 
 Amount CWalletTx::GetImmatureCredit(bool fUseCache) const {
     if (IsImmatureCoinBase() && IsInMainChain()) {
         return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
     }
 
     return Amount::zero();
 }
 
 Amount CWalletTx::GetAvailableCredit(bool fUseCache,
                                      const isminefilter &filter) const {
     if (pwallet == nullptr) {
         return Amount::zero();
     }
 
     // Avoid caching ismine for NO or ALL cases (could remove this check and
     // simplify in the future).
     bool allow_cache =
         (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
 
     // Must wait until coinbase is safely deep enough in the chain before
     // valuing it.
     if (IsImmatureCoinBase()) {
         return Amount::zero();
     }
 
     if (fUseCache && allow_cache &&
         m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
         return m_amounts[AVAILABLE_CREDIT].m_value[filter];
     }
 
     bool allow_used_addresses =
         (filter & ISMINE_USED) ||
         !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
     Amount nCredit = Amount::zero();
     const TxId &txid = GetId();
     for (uint32_t i = 0; i < tx->vout.size(); i++) {
         if (!pwallet->IsSpent(COutPoint(txid, i)) &&
             (allow_used_addresses || !pwallet->IsSpentKey(txid, i))) {
             const CTxOut &txout = tx->vout[i];
             nCredit += pwallet->GetCredit(txout, filter);
             if (!MoneyRange(nCredit)) {
                 throw std::runtime_error(std::string(__func__) +
                                          " : value out of range");
             }
         }
     }
 
     if (allow_cache) {
         m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit);
         m_is_cache_empty = false;
     }
 
     return nCredit;
 }
 
 Amount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const {
     if (IsImmatureCoinBase() && IsInMainChain()) {
         return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY,
                                  !fUseCache);
     }
 
     return Amount::zero();
 }
 
 Amount CWalletTx::GetChange() const {
     if (fChangeCached) {
         return nChangeCached;
     }
 
     nChangeCached = pwallet->GetChange(*tx);
     fChangeCached = true;
     return nChangeCached;
 }
 
 bool CWalletTx::InMempool() const {
     return fInMempool;
 }
 
 bool CWalletTx::IsTrusted() const {
     std::set<TxId> trusted_parents;
     LOCK(pwallet->cs_wallet);
     return pwallet->IsTrusted(*this, trusted_parents);
 }
 
 bool CWallet::IsTrusted(const CWalletTx &wtx,
                         std::set<TxId> &trusted_parents) const {
     AssertLockHeld(cs_wallet);
     // Quick answer in most cases
     TxValidationState state;
     if (!chain().contextualCheckTransactionForCurrentBlock(*wtx.tx, state)) {
         return false;
     }
 
     int nDepth = wtx.GetDepthInMainChain();
     if (nDepth >= 1) {
         return true;
     }
 
     if (nDepth < 0) {
         return false;
     }
 
     // using wtx's cached debit
     if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) {
         return false;
     }
 
     // Don't trust unconfirmed transactions from us unless they are in the
     // mempool.
     if (!wtx.InMempool()) {
         return false;
     }
 
     // Trusted if all inputs are from us and are in the mempool:
     for (const CTxIn &txin : wtx.tx->vin) {
         // Transactions not sent by us: not trusted
         const CWalletTx *parent = GetWalletTx(txin.prevout.GetTxId());
         if (parent == nullptr) {
             return false;
         }
 
         const CTxOut &parentOut = parent->tx->vout[txin.prevout.GetN()];
         // Check that this specific input being spent is trusted
         if (IsMine(parentOut) != ISMINE_SPENDABLE) {
             return false;
         }
         // If we've already trusted this parent, continue
         if (trusted_parents.count(parent->GetId())) {
             continue;
         }
         // Recurse to check that the parent is also trusted
         if (!IsTrusted(*parent, trusted_parents)) {
             return false;
         }
         trusted_parents.insert(parent->GetId());
     }
 
     return true;
 }
 
 bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const {
     CMutableTransaction tx1{*this->tx};
     CMutableTransaction tx2{*_tx.tx};
     for (auto &txin : tx1.vin) {
         txin.scriptSig = CScript();
     }
 
     for (auto &txin : tx2.vin) {
         txin.scriptSig = CScript();
     }
 
     return CTransaction(tx1) == CTransaction(tx2);
 }
 
 // Rebroadcast transactions from the wallet. We do this on a random timer
 // to slightly obfuscate which transactions come from our wallet.
 //
 // Ideally, we'd only resend transactions that we think should have been
 // mined in the most recent block. Any transaction that wasn't in the top
 // blockweight of transactions in the mempool shouldn't have been mined,
 // and so is probably just sitting in the mempool waiting to be confirmed.
 // Rebroadcasting does nothing to speed up confirmation and only damages
 // privacy.
 void CWallet::ResendWalletTransactions() {
     // During reindex, importing and IBD, old wallet transactions become
     // unconfirmed. Don't resend them as that would spam other nodes.
     if (!chain().isReadyToBroadcast()) {
         return;
     }
 
     // Do this infrequently and randomly to avoid giving away that these are our
     // transactions.
     if (GetTime() < nNextResend || !fBroadcastTransactions) {
         return;
     }
 
     bool fFirst = (nNextResend == 0);
     // resend 12-36 hours from now, ~1 day on average.
     nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
     if (fFirst) {
         return;
     }
 
     int submitted_tx_count = 0;
 
     { // cs_wallet scope
         LOCK(cs_wallet);
 
         // Relay transactions
         for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
             CWalletTx &wtx = item.second;
             // Attempt to rebroadcast all txes more than 5 minutes older than
             // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast
             // any confirmed or conflicting txs.
             if (wtx.nTimeReceived > m_best_block_time - 5 * 60) {
                 continue;
             }
             std::string unused_err_string;
             if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) {
                 ++submitted_tx_count;
             }
         }
     } // cs_wallet
 
     if (submitted_tx_count > 0) {
         WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__,
                         submitted_tx_count);
     }
 }
 
 /** @} */ // end of mapWallet
 
 void MaybeResendWalletTxs() {
     for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) {
         pwallet->ResendWalletTransactions();
     }
 }
 
 /**
  * @defgroup Actions
  *
  * @{
  */
 CWallet::Balance CWallet::GetBalance(const int min_depth,
                                      bool avoid_reuse) const {
     Balance ret;
     isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
     LOCK(cs_wallet);
     std::set<TxId> trusted_parents;
     for (const auto &entry : mapWallet) {
         const CWalletTx &wtx = entry.second;
         const bool is_trusted{IsTrusted(wtx, trusted_parents)};
         const int tx_depth{wtx.GetDepthInMainChain()};
         const Amount tx_credit_mine{wtx.GetAvailableCredit(
             /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
         const Amount tx_credit_watchonly{wtx.GetAvailableCredit(
             /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
         if (is_trusted && tx_depth >= min_depth) {
             ret.m_mine_trusted += tx_credit_mine;
             ret.m_watchonly_trusted += tx_credit_watchonly;
         }
         if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
             ret.m_mine_untrusted_pending += tx_credit_mine;
             ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
         }
         ret.m_mine_immature += wtx.GetImmatureCredit();
         ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
     }
     return ret;
 }
 
 Amount CWallet::GetAvailableBalance(const CCoinControl *coinControl) const {
     LOCK(cs_wallet);
 
     Amount balance = Amount::zero();
     std::vector<COutput> vCoins;
     AvailableCoins(vCoins, true, coinControl);
     for (const COutput &out : vCoins) {
         if (out.fSpendable) {
             balance += out.tx->tx->vout[out.i].nValue;
         }
     }
     return balance;
 }
 
 void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe,
                              const CCoinControl *coinControl,
                              const Amount nMinimumAmount,
                              const Amount nMaximumAmount,
                              const Amount nMinimumSumAmount,
                              const uint64_t nMaximumCount) const {
     AssertLockHeld(cs_wallet);
 
     vCoins.clear();
     Amount nTotal = Amount::zero();
     // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we
     // always allow), or we default to avoiding, and only in the case where a
     // coin control object is provided, and has the avoid address reuse flag set
     // to false, do we allow already used addresses
     bool allow_used_addresses =
         !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ||
         (coinControl && !coinControl->m_avoid_address_reuse);
     const int min_depth = {coinControl ? coinControl->m_min_depth
                                        : DEFAULT_MIN_DEPTH};
     const int max_depth = {coinControl ? coinControl->m_max_depth
                                        : DEFAULT_MAX_DEPTH};
 
     std::set<TxId> trusted_parents;
     for (const auto &entry : mapWallet) {
         const TxId &wtxid = entry.first;
         const CWalletTx &wtx = entry.second;
 
         TxValidationState state;
         if (!chain().contextualCheckTransactionForCurrentBlock(*wtx.tx,
                                                                state)) {
             continue;
         }
 
         if (wtx.IsImmatureCoinBase()) {
             continue;
         }
 
         int nDepth = wtx.GetDepthInMainChain();
         if (nDepth < 0) {
             continue;
         }
 
         // We should not consider coins which aren't at least in our mempool.
         // It's possible for these to be conflicted via ancestors which we may
         // never be able to detect.
         if (nDepth == 0 && !wtx.InMempool()) {
             continue;
         }
 
         bool safeTx = IsTrusted(wtx, trusted_parents);
 
         // Bitcoin-ABC: Removed check that prevents consideration of coins from
         // transactions that are replacing other transactions. This check based
         // on wtx.mapValue.count("replaces_txid") which was not being set
         // anywhere.
 
         // Similarly, we should not consider coins from transactions that have
         // been replaced. In the example above, we would want to prevent
         // creation of a transaction A' spending an output of A, because if
         // transaction B were initially confirmed, conflicting with A and A', we
         // wouldn't want to the user to create a transaction D intending to
         // replace A', but potentially resulting in a scenario where A, A', and
         // D could all be accepted (instead of just B and D, or just A and A'
         // like the user would want).
 
         // Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set
         // in the wallet code.
         if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
             safeTx = false;
         }
 
         if (fOnlySafe && !safeTx) {
             continue;
         }
 
         if (nDepth < min_depth || nDepth > max_depth) {
             continue;
         }
 
         for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) {
             // Only consider selected coins if add_inputs is false
             if (coinControl && !coinControl->m_add_inputs &&
                 !coinControl->IsSelected(COutPoint(entry.first, i))) {
                 continue;
             }
 
             if (wtx.tx->vout[i].nValue < nMinimumAmount ||
                 wtx.tx->vout[i].nValue > nMaximumAmount) {
                 continue;
             }
 
             const COutPoint outpoint(wtxid, i);
 
             if (coinControl && coinControl->HasSelected() &&
                 !coinControl->fAllowOtherInputs &&
                 !coinControl->IsSelected(outpoint)) {
                 continue;
             }
 
             if (IsLockedCoin(outpoint)) {
                 continue;
             }
 
             if (IsSpent(outpoint)) {
                 continue;
             }
 
             isminetype mine = IsMine(wtx.tx->vout[i]);
 
             if (mine == ISMINE_NO) {
                 continue;
             }
 
             if (!allow_used_addresses && IsSpentKey(wtxid, i)) {
                 continue;
             }
 
             std::unique_ptr<SigningProvider> provider =
                 GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
 
             bool solvable =
                 provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey)
                          : false;
             bool spendable =
                 ((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
                 (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) &&
                  (coinControl && coinControl->fAllowWatchOnly && solvable));
 
             vCoins.push_back(
                 COutput(&wtx, i, nDepth, spendable, solvable, safeTx,
                         (coinControl && coinControl->fAllowWatchOnly)));
 
             // Checks the sum amount of all UTXO's.
             if (nMinimumSumAmount != MAX_MONEY) {
                 nTotal += wtx.tx->vout[i].nValue;
 
                 if (nTotal >= nMinimumSumAmount) {
                     return;
                 }
             }
 
             // Checks the maximum number of UTXO's.
             if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
                 return;
             }
         }
     }
 }
 
 std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const {
     AssertLockHeld(cs_wallet);
 
     std::map<CTxDestination, std::vector<COutput>> result;
     std::vector<COutput> availableCoins;
 
     AvailableCoins(availableCoins);
 
     for (const auto &coin : availableCoins) {
         CTxDestination address;
         if ((coin.fSpendable ||
              (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
               coin.fSolvable)) &&
             ExtractDestination(
                 FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey,
                 address)) {
             result[address].emplace_back(std::move(coin));
         }
     }
 
     std::vector<COutPoint> lockedCoins;
     ListLockedCoins(lockedCoins);
     // Include watch-only for LegacyScriptPubKeyMan wallets without private keys
     const bool include_watch_only =
         GetLegacyScriptPubKeyMan() &&
         IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
     const isminetype is_mine_filter =
         include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
     for (const auto &output : lockedCoins) {
         auto it = mapWallet.find(output.GetTxId());
         if (it != mapWallet.end()) {
             int depth = it->second.GetDepthInMainChain();
             if (depth >= 0 && output.GetN() < it->second.tx->vout.size() &&
                 IsMine(it->second.tx->vout[output.GetN()]) == is_mine_filter) {
                 CTxDestination address;
                 if (ExtractDestination(
                         FindNonChangeParentOutput(*it->second.tx, output.GetN())
                             .scriptPubKey,
                         address)) {
                     result[address].emplace_back(
                         &it->second, output.GetN(), depth, true /* spendable */,
                         true /* solvable */, false /* safe */);
                 }
             }
         }
     }
 
     return result;
 }
 
 const CTxOut &CWallet::FindNonChangeParentOutput(const CTransaction &tx,
                                                  int output) const {
     AssertLockHeld(cs_wallet);
     const CTransaction *ptx = &tx;
     int n = output;
     while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
         const COutPoint &prevout = ptx->vin[0].prevout;
         auto it = mapWallet.find(prevout.GetTxId());
         if (it == mapWallet.end() ||
             it->second.tx->vout.size() <= prevout.GetN() ||
             !IsMine(it->second.tx->vout[prevout.GetN()])) {
             break;
         }
         ptx = it->second.tx.get();
         n = prevout.GetN();
     }
     return ptx->vout[n];
 }
 
 bool CWallet::SelectCoinsMinConf(
     const Amount nTargetValue, const CoinEligibilityFilter &eligibility_filter,
     std::vector<OutputGroup> groups, std::set<CInputCoin> &setCoinsRet,
     Amount &nValueRet, const CoinSelectionParams &coin_selection_params,
     bool &bnb_used) const {
     setCoinsRet.clear();
     nValueRet = Amount::zero();
 
     std::vector<OutputGroup> utxo_pool;
     if (coin_selection_params.use_bnb) {
         // Get long term estimate
         CCoinControl temp;
         temp.m_confirm_target = 1008;
         CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp);
 
         // Calculate cost of change
         Amount cost_of_change = chain().relayDustFee().GetFee(
                                     coin_selection_params.change_spend_size) +
                                 coin_selection_params.effective_fee.GetFee(
                                     coin_selection_params.change_output_size);
 
         // Filter by the min conf specs and add to utxo_pool and calculate
         // effective value
         for (OutputGroup &group : groups) {
             if (!group.EligibleForSpending(eligibility_filter)) {
                 continue;
             }
 
             if (coin_selection_params.m_subtract_fee_outputs) {
                 // Set the effective feerate to 0 as we don't want to use the
                 // effective value since the fees will be deducted from the
                 // output
                 group.SetFees(CFeeRate(Amount::zero()) /* effective_feerate */,
                               long_term_feerate);
             } else {
                 group.SetFees(coin_selection_params.effective_fee,
                               long_term_feerate);
             }
 
             OutputGroup pos_group = group.GetPositiveOnlyGroup();
             if (pos_group.effective_value > Amount::zero()) {
                 utxo_pool.push_back(pos_group);
             }
         }
         // Calculate the fees for things that aren't inputs
         Amount not_input_fees = coin_selection_params.effective_fee.GetFee(
             coin_selection_params.tx_noinputs_size);
         bnb_used = true;
         return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change,
                               setCoinsRet, nValueRet, not_input_fees);
     } else {
         // Filter by the min conf specs and add to utxo_pool
         for (const OutputGroup &group : groups) {
             if (!group.EligibleForSpending(eligibility_filter)) {
                 continue;
             }
             utxo_pool.push_back(group);
         }
         bnb_used = false;
         return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
     }
 }
 
 bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins,
                           const Amount nTargetValue,
                           std::set<CInputCoin> &setCoinsRet, Amount &nValueRet,
                           const CCoinControl &coin_control,
                           CoinSelectionParams &coin_selection_params,
                           bool &bnb_used) const {
     std::vector<COutput> vCoins(vAvailableCoins);
     Amount value_to_select = nTargetValue;
 
     // Default to bnb was not used. If we use it, we set it later
     bnb_used = false;
 
     // coin control -> return all selected outputs (we want all selected to go
     // into the transaction for sure)
     if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) {
         for (const COutput &out : vCoins) {
             if (!out.fSpendable) {
                 continue;
             }
 
             nValueRet += out.tx->tx->vout[out.i].nValue;
             setCoinsRet.insert(out.GetInputCoin());
         }
 
         return (nValueRet >= nTargetValue);
     }
 
     // Calculate value from preset inputs and store them.
     std::set<CInputCoin> setPresetCoins;
     Amount nValueFromPresetInputs = Amount::zero();
 
     std::vector<COutPoint> vPresetInputs;
     coin_control.ListSelected(vPresetInputs);
 
     for (const COutPoint &outpoint : vPresetInputs) {
         std::map<TxId, CWalletTx>::const_iterator it =
             mapWallet.find(outpoint.GetTxId());
         if (it != mapWallet.end()) {
             const CWalletTx &wtx = it->second;
             // Clearly invalid input, fail
             if (wtx.tx->vout.size() <= outpoint.GetN()) {
                 return false;
             }
             // Just to calculate the marginal byte size
             CInputCoin coin(wtx.tx, outpoint.GetN(),
                             wtx.GetSpendSize(outpoint.GetN(), false));
             nValueFromPresetInputs += coin.txout.nValue;
             if (coin.m_input_bytes <= 0) {
                 // Not solvable, can't estimate size for fee
                 return false;
             }
             coin.effective_value =
                 coin.txout.nValue -
                 coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
             if (coin_selection_params.use_bnb) {
                 value_to_select -= coin.effective_value;
             } else {
                 value_to_select -= coin.txout.nValue;
             }
             setPresetCoins.insert(coin);
         } else {
             return false; // TODO: Allow non-wallet inputs
         }
     }
 
     // Remove preset inputs from vCoins
     for (std::vector<COutput>::iterator it = vCoins.begin();
          it != vCoins.end() && coin_control.HasSelected();) {
         if (setPresetCoins.count(it->GetInputCoin())) {
             it = vCoins.erase(it);
         } else {
             ++it;
         }
     }
 
     size_t max_ancestors{0};
     size_t max_descendants{0};
     chain().getPackageLimits(max_ancestors, max_descendants);
     bool fRejectLongChains = gArgs.GetBoolArg(
         "-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
 
     // form groups from remaining coins; note that preset coins will not
     // automatically have their associated (same address) coins included
     if (coin_control.m_avoid_partial_spends &&
         vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
         // Cases where we have 11+ outputs all pointing to the same destination
         // may result in privacy leaks as they will potentially be
         // deterministically sorted. We solve that by explicitly shuffling the
         // outputs before processing
         Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
     }
 
     std::vector<OutputGroup> groups = GroupOutputs(
         vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
 
     bool res =
         value_to_select <= Amount::zero() ||
         SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0),
                            groups, setCoinsRet, nValueRet,
                            coin_selection_params, bnb_used) ||
         SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0),
                            groups, setCoinsRet, nValueRet,
                            coin_selection_params, bnb_used) ||
         (m_spend_zero_conf_change &&
          SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2),
                             groups, setCoinsRet, nValueRet,
                             coin_selection_params, bnb_used)) ||
         (m_spend_zero_conf_change &&
          SelectCoinsMinConf(
              value_to_select,
              CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors / 3),
                                    std::min((size_t)4, max_descendants / 3)),
              groups, setCoinsRet, nValueRet, coin_selection_params,
              bnb_used)) ||
         (m_spend_zero_conf_change &&
          SelectCoinsMinConf(value_to_select,
                             CoinEligibilityFilter(0, 1, max_ancestors / 2,
                                                   max_descendants / 2),
                             groups, setCoinsRet, nValueRet,
                             coin_selection_params, bnb_used)) ||
         (m_spend_zero_conf_change &&
          SelectCoinsMinConf(value_to_select,
                             CoinEligibilityFilter(0, 1, max_ancestors - 1,
                                                   max_descendants - 1),
                             groups, setCoinsRet, nValueRet,
                             coin_selection_params, bnb_used)) ||
         (m_spend_zero_conf_change && !fRejectLongChains &&
          SelectCoinsMinConf(
              value_to_select,
              CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()),
              groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
 
     // Because SelectCoinsMinConf clears the setCoinsRet, we now add the
     // possible inputs to the coinset.
     util::insert(setCoinsRet, setPresetCoins);
 
     // Add preset inputs to the total value selected.
     nValueRet += nValueFromPresetInputs;
 
     return res;
 }
 
 bool CWallet::SignTransaction(CMutableTransaction &tx) const {
     AssertLockHeld(cs_wallet);
 
     // Build coins map
     std::map<COutPoint, Coin> coins;
     for (auto &input : tx.vin) {
         auto mi = mapWallet.find(input.prevout.GetTxId());
         if (mi == mapWallet.end() ||
             input.prevout.GetN() >= mi->second.tx->vout.size()) {
             return false;
         }
         const CWalletTx &wtx = mi->second;
         coins[input.prevout] =
             Coin(wtx.tx->vout[input.prevout.GetN()], wtx.m_confirm.block_height,
                  wtx.IsCoinBase());
     }
     std::map<int, std::string> input_errors;
     return SignTransaction(tx, coins, SigHashType().withForkId(), input_errors);
 }
 
 bool CWallet::SignTransaction(CMutableTransaction &tx,
                               const std::map<COutPoint, Coin> &coins,
                               SigHashType sighash,
                               std::map<int, std::string> &input_errors) const {
     // Try to sign with all ScriptPubKeyMans
     for (ScriptPubKeyMan *spk_man : GetAllScriptPubKeyMans()) {
         // spk_man->SignTransaction will return true if the transaction is
         // complete, so we can exit early and return true if that happens
         if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
             return true;
         }
     }
 
     // At this point, one input was not fully signed otherwise we would have
     // exited already
 
     // When there are no available providers for the remaining inputs, use the
     // legacy provider so we can get proper error messages.
     auto legacy_spk_man = GetLegacyScriptPubKeyMan();
     if (legacy_spk_man &&
         legacy_spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
         return true;
     }
 
     return false;
 }
 
 TransactionError CWallet::FillPSBT(PartiallySignedTransaction &psbtx,
                                    bool &complete, SigHashType sighash_type,
                                    bool sign, bool bip32derivs) const {
     LOCK(cs_wallet);
     // Get all of the previous transactions
     for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) {
         const CTxIn &txin = psbtx.tx->vin[i];
         PSBTInput &input = psbtx.inputs.at(i);
 
         if (PSBTInputSigned(input)) {
             continue;
         }
 
         // If we have no utxo, grab it from the wallet.
         if (input.utxo.IsNull()) {
             const TxId &txid = txin.prevout.GetTxId();
             const auto it = mapWallet.find(txid);
             if (it != mapWallet.end()) {
                 const CWalletTx &wtx = it->second;
                 CTxOut utxo = wtx.tx->vout[txin.prevout.GetN()];
                 // Update UTXOs from the wallet.
                 input.utxo = utxo;
             }
         }
     }
 
     // Fill in information from ScriptPubKeyMans
     for (ScriptPubKeyMan *spk_man : GetAllScriptPubKeyMans()) {
         TransactionError res =
             spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs);
         if (res != TransactionError::OK) {
             return res;
         }
     }
 
     // Complete if every input is now signed
     complete = true;
     for (const auto &input : psbtx.inputs) {
         complete &= PSBTInputSigned(input);
     }
 
     return TransactionError::OK;
 }
 
 SigningResult CWallet::SignMessage(const std::string &message,
                                    const PKHash &pkhash,
                                    std::string &str_sig) const {
     SignatureData sigdata;
     CScript script_pub_key = GetScriptForDestination(pkhash);
     for (const auto &spk_man_pair : m_spk_managers) {
         if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) {
             return spk_man_pair.second->SignMessage(message, pkhash, str_sig);
         }
     }
     return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
 }
 
 bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet,
                               int &nChangePosInOut, bilingual_str &error,
                               bool lockUnspents,
                               const std::set<int> &setSubtractFeeFromOutputs,
                               CCoinControl coinControl) {
     std::vector<CRecipient> vecSend;
 
     // Turn the txout set into a CRecipient vector.
     for (size_t idx = 0; idx < tx.vout.size(); idx++) {
         const CTxOut &txOut = tx.vout[idx];
         CRecipient recipient = {txOut.scriptPubKey, txOut.nValue,
                                 setSubtractFeeFromOutputs.count(idx) == 1};
         vecSend.push_back(recipient);
     }
 
     coinControl.fAllowOtherInputs = true;
 
     for (const CTxIn &txin : tx.vin) {
         coinControl.Select(txin.prevout);
     }
 
     // Acquire the locks to prevent races to the new locked unspents between the
     // CreateTransaction call and LockCoin calls (when lockUnspents is true).
     LOCK(cs_wallet);
 
     CTransactionRef tx_new;
     if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error,
                            coinControl, false)) {
         return false;
     }
 
     if (nChangePosInOut != -1) {
         tx.vout.insert(tx.vout.begin() + nChangePosInOut,
                        tx_new->vout[nChangePosInOut]);
     }
 
     // Copy output sizes from new transaction; they may have had the fee
     // subtracted from them.
     for (size_t idx = 0; idx < tx.vout.size(); idx++) {
         tx.vout[idx].nValue = tx_new->vout[idx].nValue;
     }
 
     // Add new txins (keeping original txin scriptSig/order)
     for (const CTxIn &txin : tx_new->vin) {
         if (!coinControl.IsSelected(txin.prevout)) {
             tx.vin.push_back(txin);
         }
         if (lockUnspents) {
             LockCoin(txin.prevout);
         }
     }
 
     return true;
 }
 
 static bool IsCurrentForAntiFeeSniping(interfaces::Chain &chain,
                                        const BlockHash &block_hash) {
     if (chain.isInitialBlockDownload()) {
         return false;
     }
 
     // in seconds
     constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60;
     int64_t block_time;
     CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time)));
     if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
         return false;
     }
     return true;
 }
 
 /**
  * Return a height-based locktime for new transactions (uses the height of the
  * current chain tip unless we are not synced with the current chain
  */
 static uint32_t GetLocktimeForNewTransaction(interfaces::Chain &chain,
                                              const BlockHash &block_hash,
                                              int block_height) {
     uint32_t locktime;
     // Discourage fee sniping.
     //
     // For a large miner the value of the transactions in the best block and
     // the mempool can exceed the cost of deliberately attempting to mine two
     // blocks to orphan the current best block. By setting nLockTime such that
     // only the next block can include the transaction, we discourage this
     // practice as the height restricted and limited blocksize gives miners
     // considering fee sniping fewer options for pulling off this attack.
     //
     // A simple way to think about this is from the wallet's point of view we
     // always want the blockchain to move forward. By setting nLockTime this
     // way we're basically making the statement that we only want this
     // transaction to appear in the next block; we don't want to potentially
     // encourage reorgs by allowing transactions to appear at lower heights
     // than the next block in forks of the best chain.
     //
     // Of course, the subsidy is high enough, and transaction volume low
     // enough, that fee sniping isn't a problem yet, but by implementing a fix
     // now we ensure code won't be written that makes assumptions about
     // nLockTime that preclude a fix later.
     if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
         locktime = block_height;
 
         // Secondly occasionally randomly pick a nLockTime even further back, so
         // that transactions that are delayed after signing for whatever reason,
         // e.g. high-latency mix networks and some CoinJoin implementations,
         // have better privacy.
         if (GetRandInt(10) == 0) {
             locktime = std::max(0, int(locktime) - GetRandInt(100));
         }
     } else {
         // If our chain is lagging behind, we can't discourage fee sniping nor
         // help the privacy of high-latency transactions. To avoid leaking a
         // potentially unique "nLockTime fingerprint", set nLockTime to a
         // constant.
         locktime = 0;
     }
     assert(locktime < LOCKTIME_THRESHOLD);
     return locktime;
 }
 
 OutputType
 CWallet::TransactionChangeType(const std::optional<OutputType> &change_type,
                                const std::vector<CRecipient> &vecSend) {
     // If -changetype is specified, always use that change type.
     if (change_type) {
         return *change_type;
     }
 
     // if m_default_address_type is legacy, use legacy address as change.
     if (m_default_address_type == OutputType::LEGACY) {
         return OutputType::LEGACY;
     }
 
     // else use m_default_address_type for change
     return m_default_address_type;
 }
 
 bool CWallet::CreateTransactionInternal(const std::vector<CRecipient> &vecSend,
                                         CTransactionRef &tx, Amount &nFeeRet,
                                         int &nChangePosInOut,
                                         bilingual_str &error,
                                         const CCoinControl &coin_control,
                                         bool sign) {
     Amount nValue = Amount::zero();
     const OutputType change_type = TransactionChangeType(
         coin_control.m_change_type ? *coin_control.m_change_type
                                    : m_default_change_type,
         vecSend);
     ReserveDestination reservedest(this, change_type);
     int nChangePosRequest = nChangePosInOut;
     unsigned int nSubtractFeeFromAmount = 0;
     for (const auto &recipient : vecSend) {
         if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) {
             error = _("Transaction amounts must not be negative");
             return false;
         }
 
         nValue += recipient.nAmount;
 
         if (recipient.fSubtractFeeFromAmount) {
             nSubtractFeeFromAmount++;
         }
     }
 
     if (vecSend.empty()) {
         error = _("Transaction must have at least one recipient");
         return false;
     }
 
     CMutableTransaction txNew;
 
     {
         std::set<CInputCoin> setCoins;
         LOCK(cs_wallet);
         txNew.nLockTime = GetLocktimeForNewTransaction(
             chain(), GetLastBlockHash(), GetLastBlockHeight());
         std::vector<COutput> vAvailableCoins;
         AvailableCoins(vAvailableCoins, true, &coin_control);
         // Parameters for coin selection, init with dummy
         CoinSelectionParams coin_selection_params;
 
         // Create change script that will be used if we need change
         // TODO: pass in scriptChange instead of reservedest so
         // change transaction isn't always pay-to-bitcoin-address
         CScript scriptChange;
 
         // coin control: send change to custom address
         if (!boost::get<CNoDestination>(&coin_control.destChange)) {
             scriptChange = GetScriptForDestination(coin_control.destChange);
 
             // no coin control: send change to newly generated address
         } else {
             // Note: We use a new key here to keep it from being obvious
             // which side is the change.
             //  The drawback is that by not reusing a previous key, the
             //  change may be lost if a backup is restored, if the backup
             //  doesn't have the new private key for the change. If we
             //  reused the old key, it would be possible to add code to look
             //  for and rediscover unknown transactions that were written
             //  with keys of ours to recover post-backup change.
 
             // Reserve a new key pair from key pool. If it fails, provide a
             // dummy destination in case we don't need change.
             CTxDestination dest;
             if (!reservedest.GetReservedDestination(dest, true)) {
                 error = _("Transaction needs a change address, but we can't "
                           "generate it. Please call keypoolrefill first.");
             }
 
             scriptChange = GetScriptForDestination(dest);
             // A valid destination implies a change script (and
             // vice-versa). An empty change script will abort later, if the
             // change keypool ran out, but change is required.
             CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty());
         }
         CTxOut change_prototype_txout(Amount::zero(), scriptChange);
         coin_selection_params.change_output_size =
             GetSerializeSize(change_prototype_txout);
 
         // Get the fee rate to use effective values in coin selection
         CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control);
         // Do not, ever, assume that it's fine to change the fee rate if the
         // user has explicitly provided one
         if (coin_control.m_feerate &&
             nFeeRateNeeded > *coin_control.m_feerate) {
             error = strprintf(_("Fee rate (%s) is lower than the minimum fee "
                                 "rate setting (%s)"),
                               coin_control.m_feerate->ToString(),
                               nFeeRateNeeded.ToString());
             return false;
         }
 
         nFeeRet = Amount::zero();
         bool pick_new_inputs = true;
         Amount nValueIn = Amount::zero();
 
         // BnB selector is the only selector used when this is true.
         // That should only happen on the first pass through the loop.
         coin_selection_params.use_bnb = true;
         // If we are doing subtract fee from recipient, don't use effective
         // values
         coin_selection_params.m_subtract_fee_outputs =
             nSubtractFeeFromAmount != 0;
         // Start with no fee and loop until there is enough fee
         while (true) {
             nChangePosInOut = nChangePosRequest;
             txNew.vin.clear();
             txNew.vout.clear();
             bool fFirst = true;
 
             Amount nValueToSelect = nValue;
             if (nSubtractFeeFromAmount == 0) {
                 nValueToSelect += nFeeRet;
             }
 
             // vouts to the payees
             if (!coin_selection_params.m_subtract_fee_outputs) {
                 // Static size overhead + outputs vsize. 4 nVersion, 4
                 // nLocktime, 1 input count, 1 output count
                 coin_selection_params.tx_noinputs_size = 10;
             }
             // vouts to the payees
             for (const auto &recipient : vecSend) {
                 CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
 
                 if (recipient.fSubtractFeeFromAmount) {
                     assert(nSubtractFeeFromAmount != 0);
                     // Subtract fee equally from each selected recipient.
                     txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount);
 
                     // First receiver pays the remainder not divisible by output
                     // count.
                     if (fFirst) {
                         fFirst = false;
                         txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount);
                     }
                 }
 
                 // Include the fee cost for outputs. Note this is only used for
                 // BnB right now
                 if (!coin_selection_params.m_subtract_fee_outputs) {
                     coin_selection_params.tx_noinputs_size +=
                         ::GetSerializeSize(txout, PROTOCOL_VERSION);
                 }
 
                 if (IsDust(txout, chain().relayDustFee())) {
                     if (recipient.fSubtractFeeFromAmount &&
                         nFeeRet > Amount::zero()) {
                         if (txout.nValue < Amount::zero()) {
                             error = _("The transaction amount is too small to "
                                       "pay the fee");
                         } else {
                             error = _("The transaction amount is too small to "
                                       "send after the fee has been deducted");
                         }
                     } else {
                         error = _("Transaction amount too small");
                     }
 
                     return false;
                 }
 
                 txNew.vout.push_back(txout);
             }
 
             // Choose coins to use
             bool bnb_used = false;
             if (pick_new_inputs) {
                 nValueIn = Amount::zero();
                 setCoins.clear();
                 int change_spend_size = CalculateMaximumSignedInputSize(
                     change_prototype_txout, this);
                 // If the wallet doesn't know how to sign change output, assume
                 // p2pkh as lower-bound to allow BnB to do it's thing
                 if (change_spend_size == -1) {
                     coin_selection_params.change_spend_size =
                         DUMMY_P2PKH_INPUT_SIZE;
                 } else {
                     coin_selection_params.change_spend_size =
                         size_t(change_spend_size);
                 }
                 coin_selection_params.effective_fee = nFeeRateNeeded;
                 if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins,
                                  nValueIn, coin_control, coin_selection_params,
                                  bnb_used)) {
                     // If BnB was used, it was the first pass. No longer the
                     // first pass and continue loop with knapsack.
                     if (bnb_used) {
                         coin_selection_params.use_bnb = false;
                         continue;
                     } else {
                         error = _("Insufficient funds");
                         return false;
                     }
                 }
             } else {
                 bnb_used = false;
             }
 
             const Amount nChange = nValueIn - nValueToSelect;
             if (nChange > Amount::zero()) {
                 // Fill a vout to ourself.
                 CTxOut newTxOut(nChange, scriptChange);
 
                 // Never create dust outputs; if we would, just add the dust to
                 // the fee.
                 // The nChange when BnB is used is always going to go to fees.
                 if (IsDust(newTxOut, chain().relayDustFee()) || bnb_used) {
                     nChangePosInOut = -1;
                     nFeeRet += nChange;
                 } else {
                     if (nChangePosInOut == -1) {
                         // Insert change txn at random position:
                         nChangePosInOut = GetRandInt(txNew.vout.size() + 1);
                     } else if ((unsigned int)nChangePosInOut >
                                txNew.vout.size()) {
                         error = _("Change index out of range");
                         return false;
                     }
 
                     std::vector<CTxOut>::iterator position =
                         txNew.vout.begin() + nChangePosInOut;
                     txNew.vout.insert(position, newTxOut);
                 }
             } else {
                 nChangePosInOut = -1;
             }
 
             // Dummy fill vin for maximum size estimation
             //
             for (const auto &coin : setCoins) {
                 txNew.vin.push_back(CTxIn(coin.outpoint, CScript()));
             }
 
             CTransaction txNewConst(txNew);
             int nBytes = CalculateMaximumSignedTxSize(
                 txNewConst, this, coin_control.fAllowWatchOnly);
             if (nBytes < 0) {
                 error = _("Signing transaction failed");
                 return false;
             }
 
             Amount nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control);
 
             if (nFeeRet >= nFeeNeeded) {
                 // Reduce fee to only the needed amount if possible. This
                 // prevents potential overpayment in fees if the coins selected
                 // to meet nFeeNeeded result in a transaction that requires less
                 // fee than the prior iteration.
 
                 // If we have no change and a big enough excess fee, then try to
                 // construct transaction again only without picking new inputs.
                 // We now know we only need the smaller fee (because of reduced
                 // tx size) and so we should add a change output. Only try this
                 // once.
                 if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 &&
                     pick_new_inputs) {
                     // Add 2 as a buffer in case increasing # of outputs changes
                     // compact size
                     unsigned int tx_size_with_change =
                         nBytes + coin_selection_params.change_output_size + 2;
                     Amount fee_needed_with_change =
                         GetMinimumFee(*this, tx_size_with_change, coin_control);
                     Amount minimum_value_for_change = GetDustThreshold(
                         change_prototype_txout, chain().relayDustFee());
                     if (nFeeRet >=
                         fee_needed_with_change + minimum_value_for_change) {
                         pick_new_inputs = false;
                         nFeeRet = fee_needed_with_change;
                         continue;
                     }
                 }
 
                 // If we have change output already, just increase it
                 if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 &&
                     nSubtractFeeFromAmount == 0) {
                     Amount extraFeePaid = nFeeRet - nFeeNeeded;
                     std::vector<CTxOut>::iterator change_position =
                         txNew.vout.begin() + nChangePosInOut;
                     change_position->nValue += extraFeePaid;
                     nFeeRet -= extraFeePaid;
                 }
 
                 // Done, enough fee included.
                 break;
             } else if (!pick_new_inputs) {
                 // This shouldn't happen, we should have had enough excess fee
                 // to pay for the new output and still meet nFeeNeeded.
                 // Or we should have just subtracted fee from recipients and
                 // nFeeNeeded should not have changed.
                 error = _("Transaction fee and change calculation failed");
                 return false;
             }
 
             // Try to reduce change to include necessary fee.
             if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
                 Amount additionalFeeNeeded = nFeeNeeded - nFeeRet;
                 std::vector<CTxOut>::iterator change_position =
                     txNew.vout.begin() + nChangePosInOut;
                 // Only reduce change if remaining amount is still a large
                 // enough output.
                 if (change_position->nValue >=
                     MIN_FINAL_CHANGE + additionalFeeNeeded) {
                     change_position->nValue -= additionalFeeNeeded;
                     nFeeRet += additionalFeeNeeded;
                     // Done, able to increase fee from change.
                     break;
                 }
             }
 
             // If subtracting fee from recipients, we now know what fee we
             // need to subtract, we have no reason to reselect inputs.
             if (nSubtractFeeFromAmount > 0) {
                 pick_new_inputs = false;
             }
 
             // Include more fee and try again.
             nFeeRet = nFeeNeeded;
             coin_selection_params.use_bnb = false;
             continue;
         }
 
         // Give up if change keypool ran out and change is required
         if (scriptChange.empty() && nChangePosInOut != -1) {
             return false;
         }
 
         // Shuffle selected coins and fill in final vin
         txNew.vin.clear();
         std::vector<CInputCoin> selected_coins(setCoins.begin(),
                                                setCoins.end());
         Shuffle(selected_coins.begin(), selected_coins.end(),
                 FastRandomContext());
 
         // Note how the sequence number is set to non-maxint so that
         // the nLockTime set above actually works.
         for (const auto &coin : selected_coins) {
             txNew.vin.push_back(
                 CTxIn(coin.outpoint, CScript(),
                       std::numeric_limits<uint32_t>::max() - 1));
         }
 
         if (sign && !SignTransaction(txNew)) {
             error = _("Signing transaction failed");
             return false;
         }
 
         // Return the constructed transaction data.
         tx = MakeTransactionRef(std::move(txNew));
 
         // Limit size.
         if (tx->GetTotalSize() > MAX_STANDARD_TX_SIZE) {
             error = _("Transaction too large");
             return false;
         }
     }
 
     if (nFeeRet > m_default_max_tx_fee) {
         error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
         return false;
     }
 
     if (gArgs.GetBoolArg("-walletrejectlongchains",
                          DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
         // Lastly, ensure this tx will pass the mempool's chain limits
         if (!chain().checkChainLimits(tx)) {
             error = _("Transaction has too long of a mempool chain");
             return false;
         }
     }
 
     // Before we return success, we assume any change key will be used to
     // prevent accidental re-use.
     reservedest.KeepDestination();
 
     return true;
 }
 
 bool CWallet::CreateTransaction(const std::vector<CRecipient> &vecSend,
                                 CTransactionRef &tx, Amount &nFeeRet,
                                 int &nChangePosInOut, bilingual_str &error,
                                 const CCoinControl &coin_control, bool sign) {
     int nChangePosIn = nChangePosInOut;
     CTransactionRef tx2 = tx;
     bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut,
                                          error, coin_control, sign);
     // try with avoidpartialspends unless it's enabled already
     if (res &&
         nFeeRet >
             Amount::zero() /* 0 means non-functional fee rate estimation */
         && m_max_aps_fee > (-1 * SATOSHI) &&
         !coin_control.m_avoid_partial_spends) {
         CCoinControl tmp_cc = coin_control;
         tmp_cc.m_avoid_partial_spends = true;
         Amount nFeeRet2;
         int nChangePosInOut2 = nChangePosIn;
         // fired and forgotten; if an error occurs, we discard the results
         bilingual_str error2;
         if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2,
                                       error2, tmp_cc, sign)) {
             // if fee of this alternative one is within the range of the max
             // fee, we use this one
             const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
             WalletLogPrintf(
                 "Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet,
                 nFeeRet2, use_aps ? "grouped" : "non-grouped");
             if (use_aps) {
                 tx = tx2;
                 nFeeRet = nFeeRet2;
                 nChangePosInOut = nChangePosInOut2;
             }
         }
     }
     return res;
 }
 
 void CWallet::CommitTransaction(
     CTransactionRef tx, mapValue_t mapValue,
     std::vector<std::pair<std::string, std::string>> orderForm) {
     LOCK(cs_wallet);
 
     WalletLogPrintfToBeContinued("CommitTransaction:\n%s", tx->ToString());
 
     // Add tx to wallet, because if it has change it's also ours, otherwise just
     // for transaction history.
     AddToWallet(tx, {}, [&](CWalletTx &wtx, bool new_tx) {
         CHECK_NONFATAL(wtx.mapValue.empty());
         CHECK_NONFATAL(wtx.vOrderForm.empty());
         wtx.mapValue = std::move(mapValue);
         wtx.vOrderForm = std::move(orderForm);
         wtx.fTimeReceivedIsTxTime = true;
         wtx.fFromMe = true;
         return true;
     });
 
     // Notify that old coins are spent.
     for (const CTxIn &txin : tx->vin) {
         CWalletTx &coin = mapWallet.at(txin.prevout.GetTxId());
         coin.MarkDirty();
         NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED);
     }
 
     // Get the inserted-CWalletTx from mapWallet so that the
     // fInMempool flag is cached properly
     CWalletTx &wtx = mapWallet.at(tx->GetId());
 
     if (!fBroadcastTransactions) {
         // Don't submit tx to the mempool
         return;
     }
 
     std::string err_string;
     if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) {
         WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast "
                         "immediately, %s\n",
                         err_string);
         // TODO: if we expect the failure to be long term or permanent, instead
         // delete wtx from the wallet and return failure.
     }
 }
 
 DBErrors CWallet::LoadWallet(bool &fFirstRunRet) {
     LOCK(cs_wallet);
 
     fFirstRunRet = false;
     DBErrors nLoadWalletRet = WalletBatch(*database, "cr+").LoadWallet(this);
     if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
         if (database->Rewrite("\x04pool")) {
             for (const auto &spk_man_pair : m_spk_managers) {
                 spk_man_pair.second->RewriteDB();
             }
         }
     }
 
     // This wallet is in its first run if there are no ScriptPubKeyMans and it
     // isn't blank or no privkeys
     fFirstRunRet = m_spk_managers.empty() &&
                    !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
                    !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
     if (fFirstRunRet) {
         assert(m_external_spk_managers.empty());
         assert(m_internal_spk_managers.empty());
     }
 
     if (nLoadWalletRet != DBErrors::LOAD_OK) {
         return nLoadWalletRet;
     }
 
     return DBErrors::LOAD_OK;
 }
 
 DBErrors CWallet::ZapSelectTx(std::vector<TxId> &txIdsIn,
                               std::vector<TxId> &txIdsOut) {
     AssertLockHeld(cs_wallet);
     DBErrors nZapSelectTxRet =
         WalletBatch(*database, "cr+").ZapSelectTx(txIdsIn, txIdsOut);
     for (const TxId &txid : txIdsOut) {
         const auto &it = mapWallet.find(txid);
         wtxOrdered.erase(it->second.m_it_wtxOrdered);
         for (const auto &txin : it->second.tx->vin) {
             mapTxSpends.erase(txin.prevout);
         }
         mapWallet.erase(it);
         NotifyTransactionChanged(this, txid, CT_DELETED);
     }
 
     if (nZapSelectTxRet == DBErrors::NEED_REWRITE) {
         if (database->Rewrite("\x04pool")) {
             for (const auto &spk_man_pair : m_spk_managers) {
                 spk_man_pair.second->RewriteDB();
             }
         }
     }
 
     if (nZapSelectTxRet != DBErrors::LOAD_OK) {
         return nZapSelectTxRet;
     }
 
     MarkDirty();
 
     return DBErrors::LOAD_OK;
 }
 
 bool CWallet::SetAddressBookWithDB(WalletBatch &batch,
                                    const CTxDestination &address,
                                    const std::string &strName,
                                    const std::string &strPurpose) {
     bool fUpdated = false;
     bool is_mine;
     {
         LOCK(cs_wallet);
         std::map<CTxDestination, CAddressBookData>::iterator mi =
             m_address_book.find(address);
         fUpdated = (mi != m_address_book.end() && !mi->second.IsChange());
         m_address_book[address].SetLabel(strName);
         // Update purpose only if requested.
         if (!strPurpose.empty()) {
             m_address_book[address].purpose = strPurpose;
         }
         is_mine = IsMine(address) != ISMINE_NO;
     }
 
     NotifyAddressBookChanged(this, address, strName, is_mine, strPurpose,
                              (fUpdated ? CT_UPDATED : CT_NEW));
     if (!strPurpose.empty() && !batch.WritePurpose(address, strPurpose)) {
         return false;
     }
     return batch.WriteName(address, strName);
 }
 
 bool CWallet::SetAddressBook(const CTxDestination &address,
                              const std::string &strName,
                              const std::string &strPurpose) {
     WalletBatch batch(*database);
     return SetAddressBookWithDB(batch, address, strName, strPurpose);
 }
 
 bool CWallet::DelAddressBook(const CTxDestination &address) {
     bool is_mine;
     WalletBatch batch(*database);
     {
         LOCK(cs_wallet);
         // If we want to delete receiving addresses, we need to take care that
         // DestData "used" (and possibly newer DestData) gets preserved (and the
         // "deleted" address transformed into a change entry instead of actually
         // being deleted)
         // NOTE: This isn't a problem for sending addresses because they never
         // have any DestData yet! When adding new DestData, it should be
         // considered here whether to retain or delete it (or move it?).
         if (IsMine(address)) {
             WalletLogPrintf(
                 "%s called with IsMine address, NOT SUPPORTED. Please "
                 "report this bug! %s\n",
                 __func__, PACKAGE_BUGREPORT);
             return false;
         }
         // Delete destdata tuples associated with address
         for (const std::pair<const std::string, std::string> &item :
              m_address_book[address].destdata) {
             batch.EraseDestData(address, item.first);
         }
         m_address_book.erase(address);
         is_mine = IsMine(address) != ISMINE_NO;
     }
 
     NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED);
 
     batch.ErasePurpose(address);
     return batch.EraseName(address);
 }
 
 size_t CWallet::KeypoolCountExternalKeys() const {
     AssertLockHeld(cs_wallet);
 
     unsigned int count = 0;
     for (auto spk_man : GetActiveScriptPubKeyMans()) {
         count += spk_man->KeypoolCountExternalKeys();
     }
 
     return count;
 }
 
 unsigned int CWallet::GetKeyPoolSize() const {
     AssertLockHeld(cs_wallet);
 
     unsigned int count = 0;
     for (auto spk_man : GetActiveScriptPubKeyMans()) {
         count += spk_man->GetKeyPoolSize();
     }
     return count;
 }
 
 bool CWallet::TopUpKeyPool(unsigned int kpSize) {
     LOCK(cs_wallet);
     bool res = true;
     for (auto spk_man : GetActiveScriptPubKeyMans()) {
         res &= spk_man->TopUp(kpSize);
     }
     return res;
 }
 
 bool CWallet::GetNewDestination(const OutputType type, const std::string label,
                                 CTxDestination &dest, std::string &error) {
     LOCK(cs_wallet);
     error.clear();
     bool result = false;
     auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
     if (spk_man) {
         spk_man->TopUp();
         result = spk_man->GetNewDestination(type, dest, error);
     } else {
         error = strprintf("Error: No %s addresses available.",
                           FormatOutputType(type));
     }
     if (result) {
         SetAddressBook(dest, label, "receive");
     }
 
     return result;
 }
 
 bool CWallet::GetNewChangeDestination(const OutputType type,
                                       CTxDestination &dest,
                                       std::string &error) {
     LOCK(cs_wallet);
     error.clear();
 
     ReserveDestination reservedest(this, type);
     if (!reservedest.GetReservedDestination(dest, true)) {
         error = _("Error: Keypool ran out, please call keypoolrefill first")
                     .translated;
         return false;
     }
 
     reservedest.KeepDestination();
     return true;
 }
 
 int64_t CWallet::GetOldestKeyPoolTime() const {
     LOCK(cs_wallet);
     int64_t oldestKey = std::numeric_limits<int64_t>::max();
     for (const auto &spk_man_pair : m_spk_managers) {
         oldestKey =
             std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime());
     }
     return oldestKey;
 }
 
 void CWallet::MarkDestinationsDirty(
     const std::set<CTxDestination> &destinations) {
     for (auto &entry : mapWallet) {
         CWalletTx &wtx = entry.second;
         if (wtx.m_is_cache_empty) {
             continue;
         }
 
         for (size_t i = 0; i < wtx.tx->vout.size(); i++) {
             CTxDestination dst;
 
             if (ExtractDestination(wtx.tx->vout[i].scriptPubKey, dst) &&
                 destinations.count(dst)) {
                 wtx.MarkDirty();
                 break;
             }
         }
     }
 }
 
 std::map<CTxDestination, Amount> CWallet::GetAddressBalances() const {
     std::map<CTxDestination, Amount> balances;
 
     LOCK(cs_wallet);
     std::set<TxId> trusted_parents;
     for (const auto &walletEntry : mapWallet) {
         const CWalletTx &wtx = walletEntry.second;
 
         if (!IsTrusted(wtx, trusted_parents)) {
             continue;
         }
 
         if (wtx.IsImmatureCoinBase()) {
             continue;
         }
 
         int nDepth = wtx.GetDepthInMainChain();
         if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) {
             continue;
         }
 
         for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) {
             CTxDestination addr;
             if (!IsMine(wtx.tx->vout[i])) {
                 continue;
             }
 
             if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) {
                 continue;
             }
 
             Amount n = IsSpent(COutPoint(walletEntry.first, i))
                            ? Amount::zero()
                            : wtx.tx->vout[i].nValue;
             balances[addr] += n;
         }
     }
 
     return balances;
 }
 
 std::set<std::set<CTxDestination>> CWallet::GetAddressGroupings() const {
     AssertLockHeld(cs_wallet);
     std::set<std::set<CTxDestination>> groupings;
     std::set<CTxDestination> grouping;
 
     for (const auto &walletEntry : mapWallet) {
         const CWalletTx &wtx = walletEntry.second;
 
         if (wtx.tx->vin.size() > 0) {
             bool any_mine = false;
             // Group all input addresses with each other.
             for (const auto &txin : wtx.tx->vin) {
                 CTxDestination address;
                 // If this input isn't mine, ignore it.
                 if (!IsMine(txin)) {
                     continue;
                 }
 
                 if (!ExtractDestination(mapWallet.at(txin.prevout.GetTxId())
                                             .tx->vout[txin.prevout.GetN()]
                                             .scriptPubKey,
                                         address)) {
                     continue;
                 }
 
                 grouping.insert(address);
                 any_mine = true;
             }
 
             // Group change with input addresses.
             if (any_mine) {
                 for (const auto &txout : wtx.tx->vout) {
                     if (IsChange(txout)) {
                         CTxDestination txoutAddr;
                         if (!ExtractDestination(txout.scriptPubKey,
                                                 txoutAddr)) {
                             continue;
                         }
 
                         grouping.insert(txoutAddr);
                     }
                 }
             }
 
             if (grouping.size() > 0) {
                 groupings.insert(grouping);
                 grouping.clear();
             }
         }
 
         // Group lone addrs by themselves.
         for (const auto &txout : wtx.tx->vout) {
             if (IsMine(txout)) {
                 CTxDestination address;
                 if (!ExtractDestination(txout.scriptPubKey, address)) {
                     continue;
                 }
 
                 grouping.insert(address);
                 groupings.insert(grouping);
                 grouping.clear();
             }
         }
     }
 
     // A set of pointers to groups of addresses.
     std::set<std::set<CTxDestination> *> uniqueGroupings;
     // Map addresses to the unique group containing it.
     std::map<CTxDestination, std::set<CTxDestination> *> setmap;
     for (std::set<CTxDestination> _grouping : groupings) {
         // Make a set of all the groups hit by this new group.
         std::set<std::set<CTxDestination> *> hits;
         std::map<CTxDestination, std::set<CTxDestination> *>::iterator it;
         for (const CTxDestination &address : _grouping) {
             if ((it = setmap.find(address)) != setmap.end()) {
                 hits.insert((*it).second);
             }
         }
 
         // Merge all hit groups into a new single group and delete old groups.
         std::set<CTxDestination> *merged =
             new std::set<CTxDestination>(_grouping);
         for (std::set<CTxDestination> *hit : hits) {
             merged->insert(hit->begin(), hit->end());
             uniqueGroupings.erase(hit);
             delete hit;
         }
         uniqueGroupings.insert(merged);
 
         // Update setmap.
         for (const CTxDestination &element : *merged) {
             setmap[element] = merged;
         }
     }
 
     std::set<std::set<CTxDestination>> ret;
     for (const std::set<CTxDestination> *uniqueGrouping : uniqueGroupings) {
         ret.insert(*uniqueGrouping);
         delete uniqueGrouping;
     }
 
     return ret;
 }
 
 std::set<CTxDestination>
 CWallet::GetLabelAddresses(const std::string &label) const {
     LOCK(cs_wallet);
     std::set<CTxDestination> result;
     for (const std::pair<const CTxDestination, CAddressBookData> &item :
          m_address_book) {
         if (item.second.IsChange()) {
             continue;
         }
         const CTxDestination &address = item.first;
         const std::string &strName = item.second.GetLabel();
         if (strName == label) {
             result.insert(address);
         }
     }
 
     return result;
 }
 
 bool ReserveDestination::GetReservedDestination(CTxDestination &dest,
                                                 bool internal) {
     m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
     if (!m_spk_man) {
         return false;
     }
 
     if (nIndex == -1) {
         m_spk_man->TopUp();
 
         CKeyPool keypool;
         if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex,
                                                keypool)) {
             return false;
         }
         fInternal = keypool.fInternal;
     }
     dest = address;
     return true;
 }
 
 void ReserveDestination::KeepDestination() {
     if (nIndex != -1) {
         m_spk_man->KeepDestination(nIndex, type);
     }
 
     nIndex = -1;
     address = CNoDestination();
 }
 
 void ReserveDestination::ReturnDestination() {
     if (nIndex != -1) {
         m_spk_man->ReturnDestination(nIndex, fInternal, address);
     }
     nIndex = -1;
     address = CNoDestination();
 }
 
 void CWallet::LockCoin(const COutPoint &output) {
     AssertLockHeld(cs_wallet);
     setLockedCoins.insert(output);
 }
 
 void CWallet::UnlockCoin(const COutPoint &output) {
     AssertLockHeld(cs_wallet);
     setLockedCoins.erase(output);
 }
 
 void CWallet::UnlockAllCoins() {
     AssertLockHeld(cs_wallet);
     setLockedCoins.clear();
 }
 
 bool CWallet::IsLockedCoin(const COutPoint &outpoint) const {
     AssertLockHeld(cs_wallet);
 
     return setLockedCoins.count(outpoint) > 0;
 }
 
 void CWallet::ListLockedCoins(std::vector<COutPoint> &vOutpts) const {
     AssertLockHeld(cs_wallet);
     for (COutPoint outpoint : setLockedCoins) {
         vOutpts.push_back(outpoint);
     }
 }
 
 /** @} */ // end of Actions
 
 void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
     AssertLockHeld(cs_wallet);
     mapKeyBirth.clear();
 
     LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan();
     assert(spk_man != nullptr);
     LOCK(spk_man->cs_KeyStore);
 
     // Get birth times for keys with metadata.
     for (const auto &entry : spk_man->mapKeyMetadata) {
         if (entry.second.nCreateTime) {
             mapKeyBirth[entry.first] = entry.second.nCreateTime;
         }
     }
 
     // map in which we'll infer heights of other keys
     std::map<CKeyID, const CWalletTx::Confirmation *> mapKeyFirstBlock;
     CWalletTx::Confirmation max_confirm;
     // the tip can be reorganized; use a 144-block safety margin
     max_confirm.block_height =
         GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0;
     CHECK_NONFATAL(chain().findAncestorByHeight(
         GetLastBlockHash(), max_confirm.block_height,
         FoundBlock().hash(max_confirm.hashBlock)));
     for (const CKeyID &keyid : spk_man->GetKeys()) {
         if (mapKeyBirth.count(keyid) == 0) {
             mapKeyFirstBlock[keyid] = &max_confirm;
         }
     }
 
     // If there are no such keys, we're done.
     if (mapKeyFirstBlock.empty()) {
         return;
     }
 
     // Find first block that affects those keys, if there are any left.
     for (const auto &entry : mapWallet) {
         // iterate over all wallet transactions...
         const CWalletTx &wtx = entry.second;
         if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
             // ... which are already in a block
             for (const CTxOut &txout : wtx.tx->vout) {
                 // Iterate over all their outputs...
                 for (const auto &keyid :
                      GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
                     // ... and all their affected keys.
                     auto rit = mapKeyFirstBlock.find(keyid);
                     if (rit != mapKeyFirstBlock.end() &&
                         wtx.m_confirm.block_height <
                             rit->second->block_height) {
                         rit->second = &wtx.m_confirm;
                     }
                 }
             }
         }
     }
 
     // Extract block timestamps for those keys.
     for (const auto &entry : mapKeyFirstBlock) {
         int64_t block_time;
         CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock,
                                          FoundBlock().time(block_time)));
         // block times can be 2h off
         mapKeyBirth[entry.first] = block_time - TIMESTAMP_WINDOW;
     }
 }
 
 /**
  * Compute smart timestamp for a transaction being added to the wallet.
  *
  * Logic:
  * - If sending a transaction, assign its timestamp to the current time.
  * - If receiving a transaction outside a block, assign its timestamp to the
  *   current time.
  * - If receiving a block with a future timestamp, assign all its (not already
  *   known) transactions' timestamps to the current time.
  * - If receiving a block with a past timestamp, before the most recent known
  *   transaction (that we care about), assign all its (not already known)
  *   transactions' timestamps to the same timestamp as that most-recent-known
  *   transaction.
  * - If receiving a block with a past timestamp, but after the most recent known
  *   transaction, assign all its (not already known) transactions' timestamps to
  *   the block time.
  *
  * For more information see CWalletTx::nTimeSmart,
  * https://bitcointalk.org/?topic=54527, or
  * https://github.com/bitcoin/bitcoin/pull/1393.
  */
 unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const {
     unsigned int nTimeSmart = wtx.nTimeReceived;
     if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
         int64_t blocktime;
         if (chain().findBlock(wtx.m_confirm.hashBlock,
                               FoundBlock().time(blocktime))) {
             int64_t latestNow = wtx.nTimeReceived;
             int64_t latestEntry = 0;
 
             // Tolerate times up to the last timestamp in the wallet not more
             // than 5 minutes into the future
             int64_t latestTolerated = latestNow + 300;
             const TxItems &txOrdered = wtxOrdered;
             for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
                 CWalletTx *const pwtx = it->second;
                 if (pwtx == &wtx) {
                     continue;
                 }
                 int64_t nSmartTime;
                 nSmartTime = pwtx->nTimeSmart;
                 if (!nSmartTime) {
                     nSmartTime = pwtx->nTimeReceived;
                 }
                 if (nSmartTime <= latestTolerated) {
                     latestEntry = nSmartTime;
                     if (nSmartTime > latestNow) {
                         latestNow = nSmartTime;
                     }
                     break;
                 }
             }
 
             nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
         } else {
             WalletLogPrintf("%s: found %s in block %s not in index\n", __func__,
                             wtx.GetId().ToString(),
                             wtx.m_confirm.hashBlock.ToString());
         }
     }
     return nTimeSmart;
 }
 
 bool CWallet::AddDestData(WalletBatch &batch, const CTxDestination &dest,
                           const std::string &key, const std::string &value) {
     if (boost::get<CNoDestination>(&dest)) {
         return false;
     }
 
     m_address_book[dest].destdata.insert(std::make_pair(key, value));
     return batch.WriteDestData(dest, key, value);
 }
 
 bool CWallet::EraseDestData(WalletBatch &batch, const CTxDestination &dest,
                             const std::string &key) {
     if (!m_address_book[dest].destdata.erase(key)) {
         return false;
     }
 
     return batch.EraseDestData(dest, key);
 }
 
 void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key,
                            const std::string &value) {
     m_address_book[dest].destdata.insert(std::make_pair(key, value));
 }
 
 bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key,
                           std::string *value) const {
     std::map<CTxDestination, CAddressBookData>::const_iterator i =
         m_address_book.find(dest);
     if (i != m_address_book.end()) {
         CAddressBookData::StringMap::const_iterator j =
             i->second.destdata.find(key);
         if (j != i->second.destdata.end()) {
             if (value) {
                 *value = j->second;
             }
 
             return true;
         }
     }
     return false;
 }
 
 std::vector<std::string>
 CWallet::GetDestValues(const std::string &prefix) const {
     std::vector<std::string> values;
     for (const auto &address : m_address_book) {
         for (const auto &data : address.second.destdata) {
             if (!data.first.compare(0, prefix.size(), prefix)) {
                 values.emplace_back(data.second);
             }
         }
     }
     return values;
 }
 
 std::unique_ptr<WalletDatabase>
 MakeWalletDatabase(const std::string &name, const DatabaseOptions &options,
                    DatabaseStatus &status, bilingual_str &error_string) {
     // Do some checking on wallet path. It should be either a:
     //
     // 1. Path where a directory can be created.
     // 2. Path to an existing directory.
     // 3. Path to a symlink to a directory.
     // 4. For backwards compatibility, the name of a data file in -walletdir.
     const fs::path &wallet_path = fs::absolute(name, GetWalletDir());
     fs::file_type path_type = fs::symlink_status(wallet_path).type();
     if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
           (path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
           (path_type == fs::regular_file &&
            fs::path(name).filename() == name))) {
         error_string = Untranslated(
             strprintf("Invalid -wallet path '%s'. -wallet path should point to "
                       "a directory where wallet.dat and "
                       "database/log.?????????? files can be stored, a location "
                       "where such a directory could be created, "
                       "or (for backwards compatibility) the name of an "
                       "existing data file in -walletdir (%s)",
                       name, GetWalletDir()));
         status = DatabaseStatus::FAILED_BAD_PATH;
         return nullptr;
     }
     return MakeDatabase(wallet_path, options, status, error_string);
 }
 
 std::shared_ptr<CWallet>
 CWallet::Create(interfaces::Chain &chain, const std::string &name,
                 std::unique_ptr<WalletDatabase> database,
                 uint64_t wallet_creation_flags, bilingual_str &error,
                 std::vector<bilingual_str> &warnings) {
     const std::string &walletFile = database->Filename();
 
     chain.initMessage(_("Loading wallet...").translated);
 
     int64_t nStart = GetTimeMillis();
     bool fFirstRun = true;
     // TODO: Can't use std::make_shared because we need a custom deleter but
     // should be possible to use std::allocate_shared.
     std::shared_ptr<CWallet> walletInstance(
         new CWallet(&chain, name, std::move(database)), ReleaseWallet);
     DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
     if (nLoadWalletRet != DBErrors::LOAD_OK) {
         if (nLoadWalletRet == DBErrors::CORRUPT) {
             error =
                 strprintf(_("Error loading %s: Wallet corrupted"), walletFile);
             return nullptr;
         }
 
         if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) {
             warnings.push_back(
                 strprintf(_("Error reading %s! All keys read correctly, but "
                             "transaction data or address book entries might be "
                             "missing or incorrect."),
                           walletFile));
         } else if (nLoadWalletRet == DBErrors::TOO_NEW) {
             error = strprintf(
                 _("Error loading %s: Wallet requires newer version of %s"),
                 walletFile, PACKAGE_NAME);
             return nullptr;
         } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
             error = strprintf(
                 _("Wallet needed to be rewritten: restart %s to complete"),
                 PACKAGE_NAME);
             return nullptr;
         } else {
             error = strprintf(_("Error loading %s"), walletFile);
             return nullptr;
         }
     }
 
     if (fFirstRun) {
         // Ensure this wallet.dat can only be opened by clients supporting
         // HD with chain split and expects no default key.
         walletInstance->SetMinVersion(FEATURE_LATEST);
 
         walletInstance->AddWalletFlags(wallet_creation_flags);
 
         // Only create LegacyScriptPubKeyMan when not descriptor wallet
         if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
             walletInstance->SetupLegacyScriptPubKeyMan();
         }
 
         if (!(wallet_creation_flags &
               (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
             LOCK(walletInstance->cs_wallet);
             if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
                 walletInstance->SetupDescriptorScriptPubKeyMans();
                 // SetupDescriptorScriptPubKeyMans already calls SetupGeneration
                 // for us so we don't need to call SetupGeneration separately
             } else {
                 // Legacy wallets need SetupGeneration here.
                 for (auto spk_man :
                      walletInstance->GetActiveScriptPubKeyMans()) {
                     if (!spk_man->SetupGeneration()) {
                         error = _("Unable to generate initial keys");
                         return nullptr;
                     }
                 }
             }
         }
 
         walletInstance->chainStateFlushed(chain.getTipLocator());
     } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
         // Make it impossible to disable private keys after creation
         error = strprintf(_("Error loading %s: Private keys can only be "
                             "disabled during creation"),
                           walletFile);
         return nullptr;
     } else if (walletInstance->IsWalletFlagSet(
                    WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
         for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
             if (spk_man->HavePrivateKeys()) {
                 warnings.push_back(
                     strprintf(_("Warning: Private keys detected in wallet {%s} "
                                 "with disabled private keys"),
                               walletFile));
             }
         }
     }
 
     if (gArgs.IsArgSet("-mintxfee")) {
         Amount n = Amount::zero();
         if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) ||
             n == Amount::zero()) {
             error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""));
             return nullptr;
         }
         if (n > HIGH_TX_FEE_PER_KB) {
             warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") +
                                _("This is the minimum transaction fee you pay "
                                  "on every transaction."));
         }
         walletInstance->m_min_fee = CFeeRate(n);
     }
 
     if (gArgs.IsArgSet("-maxapsfee")) {
         const std::string max_aps_fee{gArgs.GetArg("-maxapsfee", "")};
         Amount n = Amount::zero();
         if (max_aps_fee == "-1") {
             n = -1 * SATOSHI;
         } else if (!ParseMoney(max_aps_fee, n)) {
             error = AmountErrMsg("maxapsfee", max_aps_fee);
             return nullptr;
         }
         if (n > HIGH_APS_FEE) {
             warnings.push_back(
                 AmountHighWarn("-maxapsfee") + Untranslated(" ") +
                 _("This is the maximum transaction fee you pay (in addition to"
                   " the normal fee) to prioritize partial spend avoidance over"
                   " regular coin selection."));
         }
         walletInstance->m_max_aps_fee = n;
     }
 
     if (gArgs.IsArgSet("-fallbackfee")) {
         Amount nFeePerK = Amount::zero();
         if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
             error =
                 strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"),
                           gArgs.GetArg("-fallbackfee", ""));
             return nullptr;
         }
         if (nFeePerK > HIGH_TX_FEE_PER_KB) {
             warnings.push_back(AmountHighWarn("-fallbackfee") +
                                Untranslated(" ") +
                                _("This is the transaction fee you may pay when "
                                  "fee estimates are not available."));
         }
         walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
     }
     // Disable fallback fee in case value was set to 0, enable if non-null value
     walletInstance->m_allow_fallback_fee =
         walletInstance->m_fallback_fee.GetFeePerK() != Amount::zero();
 
     if (gArgs.IsArgSet("-paytxfee")) {
         Amount nFeePerK = Amount::zero();
         if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
             error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""));
             return nullptr;
         }
         if (nFeePerK > HIGH_TX_FEE_PER_KB) {
             warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") +
                                _("This is the transaction fee you will pay if "
                                  "you send a transaction."));
         }
         walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
         if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) {
             error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' "
                                 "(must be at least %s)"),
                               gArgs.GetArg("-paytxfee", ""),
                               chain.relayMinFee().ToString());
             return nullptr;
         }
     }
 
     if (gArgs.IsArgSet("-maxtxfee")) {
         Amount nMaxFee = Amount::zero();
         if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
             error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""));
             return nullptr;
         }
         if (nMaxFee > HIGH_MAX_TX_FEE) {
             warnings.push_back(_("-maxtxfee is set very high! Fees this large "
                                  "could be paid on a single transaction."));
         }
         if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
             error = strprintf(
                 _("Invalid amount for -maxtxfee=<amount>: '%s' (must be at "
                   "least the minrelay fee of %s to prevent stuck "
                   "transactions)"),
                 gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString());
             return nullptr;
         }
         walletInstance->m_default_max_tx_fee = nMaxFee;
     }
 
     if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
         warnings.push_back(
             AmountHighWarn("-minrelaytxfee") + Untranslated(" ") +
             _("The wallet will avoid paying less than the minimum relay fee."));
     }
 
     walletInstance->m_spend_zero_conf_change =
         gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
 
     walletInstance->m_default_address_type = DEFAULT_ADDRESS_TYPE;
 
     walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n",
                                     GetTimeMillis() - nStart);
 
     // Try to top up keypool. No-op if the wallet is locked.
     walletInstance->TopUpKeyPool();
 
     LOCK(walletInstance->cs_wallet);
 
     // Register wallet with validationinterface. It's done before rescan to
     // avoid missing block connections between end of rescan and validation
     // subscribing. Because of wallet lock being hold, block connection
     // notifications are going to be pending on the validation-side until lock
     // release. It's likely to have block processing duplicata (if rescan block
     // range overlaps with notification one) but we guarantee at least than
     // wallet state is correct after notifications delivery. This is temporary
     // until rescan and notifications delivery are unified under same interface.
     walletInstance->m_chain_notifications_handler =
         walletInstance->chain().handleNotifications(walletInstance);
 
     int rescan_height = 0;
     if (!gArgs.GetBoolArg("-rescan", false)) {
         WalletBatch batch(*walletInstance->database);
         CBlockLocator locator;
         if (batch.ReadBestBlock(locator)) {
             if (const std::optional<int> fork_height =
                     chain.findLocatorFork(locator)) {
                 rescan_height = *fork_height;
             }
         }
     }
 
     const std::optional<int> tip_height = chain.getHeight();
     if (tip_height) {
         walletInstance->m_last_block_processed =
             chain.getBlockHash(*tip_height);
         walletInstance->m_last_block_processed_height = *tip_height;
     } else {
         walletInstance->m_last_block_processed.SetNull();
         walletInstance->m_last_block_processed_height = -1;
     }
 
     if (tip_height && *tip_height != rescan_height) {
         // We can't rescan beyond non-pruned blocks, stop and throw an error.
         // This might happen if a user uses an old wallet within a pruned node
         // or if they ran -disablewallet for a longer time, then decided to
         // re-enable
         if (chain.havePruned()) {
             // Exit early and print an error.
             // If a block is pruned after this check, we will load the wallet,
             // but fail the rescan with a generic error.
             int block_height = *tip_height;
             while (block_height > 0 &&
                    chain.haveBlockOnDisk(block_height - 1) &&
                    rescan_height != block_height) {
                 --block_height;
             }
 
             if (rescan_height != block_height) {
                 error = _("Prune: last wallet synchronisation goes beyond "
                           "pruned data. You need to -reindex (download the "
                           "whole blockchain again in case of pruned node)");
                 return nullptr;
             }
         }
 
         chain.initMessage(_("Rescanning...").translated);
         walletInstance->WalletLogPrintf(
             "Rescanning last %i blocks (from block %i)...\n",
             *tip_height - rescan_height, rescan_height);
 
         // No need to read and scan block if block was created before our wallet
         // birthday (as adjusted for block time variability)
         std::optional<int64_t> time_first_key;
         for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
             int64_t time = spk_man->GetTimeFirstKey();
             if (!time_first_key || time < *time_first_key) {
                 time_first_key = time;
             }
         }
         if (time_first_key) {
             if (std::optional<int> first_block =
                     chain.findFirstBlockWithTimeAndHeight(
                         *time_first_key - TIMESTAMP_WINDOW, rescan_height,
                         nullptr)) {
                 rescan_height = *first_block;
             }
         }
 
         {
             WalletRescanReserver reserver(*walletInstance);
             if (!reserver.reserve() ||
                 (ScanResult::SUCCESS !=
                  walletInstance
                      ->ScanForWalletTransactions(
                          chain.getBlockHash(rescan_height), rescan_height,
                          {} /* max height */, reserver, true /* update */)
                      .status)) {
                 error = _("Failed to rescan the wallet during initialization");
                 return nullptr;
             }
         }
         walletInstance->chainStateFlushed(chain.getTipLocator());
         walletInstance->database->IncrementUpdateCounter();
     }
 
     {
         LOCK(cs_wallets);
         for (auto &load_wallet : g_load_wallet_fns) {
             load_wallet(interfaces::MakeWallet(walletInstance));
         }
     }
 
     walletInstance->SetBroadcastTransactions(
         gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
 
     walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n",
                                     walletInstance->GetKeyPoolSize());
     walletInstance->WalletLogPrintf("mapWallet.size() = %u\n",
                                     walletInstance->mapWallet.size());
     walletInstance->WalletLogPrintf("m_address_book.size() = %u\n",
                                     walletInstance->m_address_book.size());
 
     return walletInstance;
 }
 
 const CAddressBookData *
 CWallet::FindAddressBookEntry(const CTxDestination &dest,
                               bool allow_change) const {
     const auto &address_book_it = m_address_book.find(dest);
     if (address_book_it == m_address_book.end()) {
         return nullptr;
     }
     if ((!allow_change) && address_book_it->second.IsChange()) {
         return nullptr;
     }
     return &address_book_it->second;
 }
 
 bool CWallet::UpgradeWallet(int version, bilingual_str &error,
                             std::vector<bilingual_str> &warnings) {
     int prev_version = GetVersion();
     int nMaxVersion = version;
     // The -upgradewallet without argument case
     if (nMaxVersion == 0) {
         WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
         nMaxVersion = FEATURE_LATEST;
         // permanently upgrade the wallet immediately
         SetMinVersion(FEATURE_LATEST);
     } else {
         WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
     }
 
     if (nMaxVersion < GetVersion()) {
         error = _("Cannot downgrade wallet");
         return false;
     }
 
     SetMaxVersion(nMaxVersion);
 
     LOCK(cs_wallet);
 
     // Do not upgrade versions to any version between HD_SPLIT and
     // FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT
     int max_version = GetVersion();
     if (!CanSupportFeature(FEATURE_HD_SPLIT) &&
         max_version >= FEATURE_HD_SPLIT &&
         max_version < FEATURE_PRE_SPLIT_KEYPOOL) {
         error = _("Cannot upgrade a non HD split wallet without upgrading to "
                   "support pre split keypool. Please use version 200300 or no "
                   "version specified.");
         return false;
     }
 
     for (auto spk_man : GetActiveScriptPubKeyMans()) {
         if (!spk_man->Upgrade(prev_version, error)) {
             return false;
         }
     }
 
     return true;
 }
 
 void CWallet::postInitProcess() {
     LOCK(cs_wallet);
 
     // Add wallet transactions that aren't already in a block to mempool.
     // Do this here as mempool requires genesis block to be loaded.
     ReacceptWalletTransactions();
 
     // Update wallet transactions with current mempool transactions.
     chain().requestMempoolTransactions(*this);
 }
 
 bool CWallet::BackupWallet(const std::string &strDest) const {
     return database->Backup(strDest);
 }
 
 CKeyPool::CKeyPool() {
     nTime = GetTime();
     fInternal = false;
     m_pre_split = false;
 }
 
 CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) {
     nTime = GetTime();
     vchPubKey = vchPubKeyIn;
     fInternal = internalIn;
     m_pre_split = false;
 }
 
 int CWalletTx::GetDepthInMainChain() const {
     assert(pwallet != nullptr);
     AssertLockHeld(pwallet->cs_wallet);
     if (isUnconfirmed() || isAbandoned()) {
         return 0;
     }
 
     return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) *
            (isConflicted() ? -1 : 1);
 }
 
 int CWalletTx::GetBlocksToMaturity() const {
     if (!IsCoinBase()) {
         return 0;
     }
 
     int chain_depth = GetDepthInMainChain();
     // coinbase tx should not be conflicted
     assert(chain_depth >= 0);
     return std::max(0, (COINBASE_MATURITY + 1) - chain_depth);
 }
 
 bool CWalletTx::IsImmatureCoinBase() const {
     // note GetBlocksToMaturity is 0 for non-coinbase tx
     return GetBlocksToMaturity() > 0;
 }
 
 std::vector<OutputGroup>
 CWallet::GroupOutputs(const std::vector<COutput> &outputs, bool single_coin,
                       const size_t max_ancestors) const {
     std::vector<OutputGroup> groups;
     std::map<CTxDestination, OutputGroup> gmap;
     std::set<CTxDestination> full_groups;
 
     for (const auto &output : outputs) {
         if (output.fSpendable) {
             CTxDestination dst;
             CInputCoin input_coin = output.GetInputCoin();
 
             size_t ancestors, descendants;
             chain().getTransactionAncestry(output.tx->GetId(), ancestors,
                                            descendants);
             if (!single_coin &&
                 ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey,
                                    dst)) {
                 auto it = gmap.find(dst);
                 if (it != gmap.end()) {
                     // Limit output groups to no more than
                     // OUTPUT_GROUP_MAX_ENTRIES number of entries, to protect
                     // against inadvertently creating a too-large transaction
                     // when using -avoidpartialspends to prevent breaking
                     // consensus or surprising users with a very high amount of
                     // fees.
                     if (it->second.m_outputs.size() >=
                         OUTPUT_GROUP_MAX_ENTRIES) {
                         groups.push_back(it->second);
                         it->second = OutputGroup{};
                         full_groups.insert(dst);
                     }
                     it->second.Insert(input_coin, output.nDepth,
                                       output.tx->IsFromMe(ISMINE_ALL),
                                       ancestors, descendants);
                 } else {
                     gmap[dst].Insert(input_coin, output.nDepth,
                                      output.tx->IsFromMe(ISMINE_ALL), ancestors,
                                      descendants);
                 }
             } else {
                 groups.emplace_back(input_coin, output.nDepth,
                                     output.tx->IsFromMe(ISMINE_ALL), ancestors,
                                     descendants);
             }
         }
     }
     if (!single_coin) {
         for (auto &it : gmap) {
             auto &group = it.second;
             if (full_groups.count(it.first) > 0) {
                 // Make this unattractive as we want coin selection to avoid it
                 // if possible
                 group.m_ancestors = max_ancestors - 1;
             }
             groups.push_back(group);
         }
     }
     return groups;
 }
 
 bool CWallet::IsCrypted() const {
     return HasEncryptionKeys();
 }
 
 bool CWallet::IsLocked() const {
     if (!IsCrypted()) {
         return false;
     }
     LOCK(cs_wallet);
     return vMasterKey.empty();
 }
 
 bool CWallet::Lock() {
     if (!IsCrypted()) {
         return false;
     }
 
     {
         LOCK(cs_wallet);
         vMasterKey.clear();
     }
 
     NotifyStatusChanged(this);
     return true;
 }
 
 bool CWallet::Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys) {
     {
         LOCK(cs_wallet);
         for (const auto &spk_man_pair : m_spk_managers) {
             if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn,
                                                          accept_no_keys)) {
                 return false;
             }
         }
         vMasterKey = vMasterKeyIn;
     }
     NotifyStatusChanged(this);
     return true;
 }
 
 std::set<ScriptPubKeyMan *> CWallet::GetActiveScriptPubKeyMans() const {
     std::set<ScriptPubKeyMan *> spk_mans;
     for (bool internal : {false, true}) {
         for (OutputType t : OUTPUT_TYPES) {
             auto spk_man = GetScriptPubKeyMan(t, internal);
             if (spk_man) {
                 spk_mans.insert(spk_man);
             }
         }
     }
     return spk_mans;
 }
 
 std::set<ScriptPubKeyMan *> CWallet::GetAllScriptPubKeyMans() const {
     std::set<ScriptPubKeyMan *> spk_mans;
     for (const auto &spk_man_pair : m_spk_managers) {
         spk_mans.insert(spk_man_pair.second.get());
     }
     return spk_mans;
 }
 
 ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const OutputType &type,
                                              bool internal) const {
     const std::map<OutputType, ScriptPubKeyMan *> &spk_managers =
         internal ? m_internal_spk_managers : m_external_spk_managers;
     std::map<OutputType, ScriptPubKeyMan *>::const_iterator it =
         spk_managers.find(type);
     if (it == spk_managers.end()) {
         WalletLogPrintf(
             "%s scriptPubKey Manager for output type %d does not exist\n",
             internal ? "Internal" : "External", static_cast<int>(type));
         return nullptr;
     }
     return it->second;
 }
 
 std::set<ScriptPubKeyMan *>
 CWallet::GetScriptPubKeyMans(const CScript &script,
                              SignatureData &sigdata) const {
     std::set<ScriptPubKeyMan *> spk_mans;
     for (const auto &spk_man_pair : m_spk_managers) {
         if (spk_man_pair.second->CanProvide(script, sigdata)) {
             spk_mans.insert(spk_man_pair.second.get());
         }
     }
     return spk_mans;
 }
 
 ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const CScript &script) const {
     SignatureData sigdata;
     for (const auto &spk_man_pair : m_spk_managers) {
         if (spk_man_pair.second->CanProvide(script, sigdata)) {
             return spk_man_pair.second.get();
         }
     }
     return nullptr;
 }
 
 ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const uint256 &id) const {
     if (m_spk_managers.count(id) > 0) {
         return m_spk_managers.at(id).get();
     }
     return nullptr;
 }
 
 std::unique_ptr<SigningProvider>
 CWallet::GetSolvingProvider(const CScript &script) const {
     SignatureData sigdata;
     return GetSolvingProvider(script, sigdata);
 }
 
 std::unique_ptr<SigningProvider>
 CWallet::GetSolvingProvider(const CScript &script,
                             SignatureData &sigdata) const {
     for (const auto &spk_man_pair : m_spk_managers) {
         if (spk_man_pair.second->CanProvide(script, sigdata)) {
             return spk_man_pair.second->GetSolvingProvider(script);
         }
     }
     return nullptr;
 }
 
 LegacyScriptPubKeyMan *CWallet::GetLegacyScriptPubKeyMan() const {
     if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
         return nullptr;
     }
     // Legacy wallets only have one ScriptPubKeyMan which is a
     // LegacyScriptPubKeyMan. Everything in m_internal_spk_managers and
     // m_external_spk_managers point to the same legacyScriptPubKeyMan.
     auto it = m_internal_spk_managers.find(OutputType::LEGACY);
     if (it == m_internal_spk_managers.end()) {
         return nullptr;
     }
     return dynamic_cast<LegacyScriptPubKeyMan *>(it->second);
 }
 
 LegacyScriptPubKeyMan *CWallet::GetOrCreateLegacyScriptPubKeyMan() {
     SetupLegacyScriptPubKeyMan();
     return GetLegacyScriptPubKeyMan();
 }
 
 void CWallet::SetupLegacyScriptPubKeyMan() {
     if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() ||
         !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
         return;
     }
 
     auto spk_manager =
         std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this));
     for (const auto &type : OUTPUT_TYPES) {
         m_internal_spk_managers[type] = spk_manager.get();
         m_external_spk_managers[type] = spk_manager.get();
     }
     m_spk_managers[spk_manager->GetID()] = std::move(spk_manager);
 }
 
 const CKeyingMaterial &CWallet::GetEncryptionKey() const {
     return vMasterKey;
 }
 
 bool CWallet::HasEncryptionKeys() const {
     return !mapMasterKeys.empty();
 }
 
 void CWallet::ConnectScriptPubKeyManNotifiers() {
     for (const auto &spk_man : GetActiveScriptPubKeyMans()) {
         spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
         spk_man->NotifyCanGetAddressesChanged.connect(
             NotifyCanGetAddressesChanged);
     }
 }
 
 void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id,
                                             WalletDescriptor &desc) {
     auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(
         new DescriptorScriptPubKeyMan(*this, desc));
     m_spk_managers[id] = std::move(spk_manager);
 }
 
 void CWallet::SetupDescriptorScriptPubKeyMans() {
     AssertLockHeld(cs_wallet);
 
     // Make a seed
     CKey seed_key;
     seed_key.MakeNewKey(true);
     CPubKey seed = seed_key.GetPubKey();
     assert(seed_key.VerifyPubKey(seed));
 
     // Get the extended key
     CExtKey master_key;
     master_key.SetSeed(seed_key.begin(), seed_key.size());
 
     for (bool internal : {false, true}) {
         for (OutputType t : OUTPUT_TYPES) {
             auto spk_manager =
                 std::make_unique<DescriptorScriptPubKeyMan>(*this, internal);
             if (IsCrypted()) {
                 if (IsLocked()) {
                     throw std::runtime_error(
                         std::string(__func__) +
                         ": Wallet is locked, cannot setup new descriptors");
                 }
                 if (!spk_manager->CheckDecryptionKey(vMasterKey) &&
                     !spk_manager->Encrypt(vMasterKey, nullptr)) {
                     throw std::runtime_error(
                         std::string(__func__) +
                         ": Could not encrypt new descriptors");
                 }
             }
             spk_manager->SetupDescriptorGeneration(master_key, t);
             uint256 id = spk_manager->GetID();
             m_spk_managers[id] = std::move(spk_manager);
             AddActiveScriptPubKeyMan(id, t, internal);
         }
     }
 }
 
 void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type,
                                        bool internal) {
     WalletBatch batch(*database);
     if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id,
                                           internal)) {
         throw std::runtime_error(std::string(__func__) +
                                  ": writing active ScriptPubKeyMan id failed");
     }
     LoadActiveScriptPubKeyMan(id, type, internal);
 }
 
 void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type,
                                         bool internal) {
     WalletLogPrintf(
         "Setting spkMan to active: id = %s, type = %d, internal = %d\n",
         id.ToString(), static_cast<int>(type), static_cast<int>(internal));
     auto &spk_mans =
         internal ? m_internal_spk_managers : m_external_spk_managers;
     auto spk_man = m_spk_managers.at(id).get();
     spk_man->SetInternal(internal);
     spk_mans[type] = spk_man;
 
     NotifyCanGetAddressesChanged();
 }
 
 bool CWallet::IsLegacy() const {
     if (m_internal_spk_managers.count(OutputType::LEGACY) == 0) {
         return false;
     }
     auto spk_man = dynamic_cast<LegacyScriptPubKeyMan *>(
         m_internal_spk_managers.at(OutputType::LEGACY));
     return spk_man != nullptr;
 }
 
 DescriptorScriptPubKeyMan *
 CWallet::GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const {
     for (auto &spk_man_pair : m_spk_managers) {
         // Try to downcast to DescriptorScriptPubKeyMan then check if the
         // descriptors match
         DescriptorScriptPubKeyMan *spk_manager =
             dynamic_cast<DescriptorScriptPubKeyMan *>(
                 spk_man_pair.second.get());
         if (spk_manager != nullptr && spk_manager->HasWalletDescriptor(desc)) {
             return spk_manager;
         }
     }
 
     return nullptr;
 }
 
 ScriptPubKeyMan *
 CWallet::AddWalletDescriptor(WalletDescriptor &desc,
                              const FlatSigningProvider &signing_provider,
                              const std::string &label) {
     if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
         WalletLogPrintf(
             "Cannot add WalletDescriptor to a non-descriptor wallet\n");
         return nullptr;
     }
 
     LOCK(cs_wallet);
     auto new_spk_man = std::make_unique<DescriptorScriptPubKeyMan>(*this, desc);
 
     // If we already have this descriptor, remove it from the maps but add the
     // existing cache to desc
     auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
     if (old_spk_man) {
         WalletLogPrintf("Update existing descriptor: %s\n",
                         desc.descriptor->ToString());
 
         {
             LOCK(old_spk_man->cs_desc_man);
             new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
         }
 
         // Remove from maps of active spkMans
         auto old_spk_man_id = old_spk_man->GetID();
         for (bool internal : {false, true}) {
             for (OutputType t : OUTPUT_TYPES) {
                 auto active_spk_man = GetScriptPubKeyMan(t, internal);
                 if (active_spk_man &&
                     active_spk_man->GetID() == old_spk_man_id) {
                     if (internal) {
                         m_internal_spk_managers.erase(t);
                     } else {
                         m_external_spk_managers.erase(t);
                     }
                     break;
                 }
             }
         }
         m_spk_managers.erase(old_spk_man_id);
     }
 
     // Add the private keys to the descriptor
     for (const auto &entry : signing_provider.keys) {
         const CKey &key = entry.second;
         new_spk_man->AddDescriptorKey(key, key.GetPubKey());
     }
 
     // Top up key pool, the manager will generate new scriptPubKeys internally
     new_spk_man->TopUp();
 
     // Apply the label if necessary
     // Note: we disable labels for ranged descriptors
     if (!desc.descriptor->IsRange()) {
         auto script_pub_keys = new_spk_man->GetScriptPubKeys();
         if (script_pub_keys.empty()) {
             WalletLogPrintf(
                 "Could not generate scriptPubKeys (cache is empty)\n");
             return nullptr;
         }
 
         CTxDestination dest;
         if (ExtractDestination(script_pub_keys.at(0), dest)) {
             SetAddressBook(dest, label, "receive");
         }
     }
 
     // Save the descriptor to memory
     auto ret = new_spk_man.get();
     m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
 
     // Save the descriptor to DB
     ret->WriteDescriptor();
 
     return ret;
 }
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 79ad4d885..ff6639f7f 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -1,441 +1,445 @@
 #!/usr/bin/env python3
 # Copyright (c) 2017-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.
 """Test multiwallet.
 
 Verify that a bitcoind node can load multiple wallet files
 """
 import os
 import shutil
 import time
 from decimal import Decimal
 from threading import Thread
 
 from test_framework.authproxy import JSONRPCException
 from test_framework.test_framework import BitcoinTestFramework
 from test_framework.test_node import ErrorMatch
 from test_framework.util import (
     assert_equal,
     assert_raises_rpc_error,
     get_rpc_proxy,
 )
 
 FEATURE_LATEST = 200300
 
 got_loading_error = False
 
 
 def test_load_unload(node, name):
     global got_loading_error
     for _ in range(10):
         if got_loading_error:
             return
         try:
             node.loadwallet(name)
             node.unloadwallet(name)
         except JSONRPCException as e:
             if e.error['code'] == - \
                     4 and 'Wallet already being loading' in e.error['message']:
                 got_loading_error = True
                 return
 
 
 class MultiWalletTest(BitcoinTestFramework):
     def set_test_params(self):
         self.setup_clean_chain = True
         self.num_nodes = 2
 
     def skip_test_if_missing_module(self):
         self.skip_if_no_wallet()
 
     def add_options(self, parser):
         parser.add_argument(
             '--data_wallets_dir',
             default=os.path.join(
                 os.path.dirname(
                     os.path.realpath(__file__)),
                 'data/wallets/'),
             help='Test data with wallet directories (default: %(default)s)',
         )
 
     def run_test(self):
         node = self.nodes[0]
 
         def data_dir(*p): return os.path.join(node.datadir, self.chain, *p)
 
         def wallet_dir(*p): return data_dir('wallets', *p)
 
         def wallet(name): return node.get_wallet_rpc(name)
 
         def wallet_file(name):
             if os.path.isdir(wallet_dir(name)):
                 return wallet_dir(name, "wallet.dat")
             return wallet_dir(name)
 
         assert_equal(self.nodes[0].listwalletdir(),
                      {'wallets': [{'name': ''}]})
 
         # check wallet.dat is created
         self.stop_nodes()
         assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True)
 
         # create symlink to verify wallet directory path can be referenced
         # through symlink
         if os.name != 'nt':
             os.mkdir(wallet_dir('w7'))
             os.symlink('w7', wallet_dir('w7_symlink'))
 
         # rename wallet.dat to make sure plain wallet file paths (as opposed to
         # directory paths) can be loaded
         os.rename(wallet_dir("wallet.dat"), wallet_dir("w8"))
 
         # create another dummy wallet for use in testing backups later
         self.start_node(0, [])
         self.stop_nodes()
         empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat')
         os.rename(wallet_dir("wallet.dat"), empty_wallet)
 
         # restart node with a mix of wallet names:
         #   w1, w2, w3 - to verify new wallets created when non-existing paths specified
         #   w          - to verify wallet name matching works when one wallet path is prefix of another
         #   sub/w5     - to verify relative wallet path is created correctly
         #   extern/w6  - to verify absolute wallet path is created correctly
         #   w7_symlink - to verify symlinked wallet path is initialized correctly
         #   w8         - to verify existing wallet file is loaded correctly
         #   ''         - to verify default wallet file is created correctly
         wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5',
                         os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', '']
         if os.name == 'nt':
             wallet_names.remove('w7_symlink')
         extra_args = ['-wallet={}'.format(n) for n in wallet_names]
         self.start_node(0, extra_args)
         assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [
                      '', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8'])
 
         assert_equal(set(node.listwallets()), set(wallet_names))
 
         # check that all requested wallets were created
         self.stop_node(0)
         for wallet_name in wallet_names:
             assert_equal(os.path.isfile(wallet_file(wallet_name)), True)
 
         # should not initialize if wallet path can't be created
         exp_stderr = "boost::filesystem::create_directory:"
         self.nodes[0].assert_start_raises_init_error(
             ['-wallet=wallet.dat/bad'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
 
         self.nodes[0].assert_start_raises_init_error(
             ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
         self.nodes[0].assert_start_raises_init_error(
             ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
         self.nodes[0].assert_start_raises_init_error(
             ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir())
 
         # should not initialize if there are duplicate wallets
         self.nodes[0].assert_start_raises_init_error(
             ['-wallet=w1', '-wallet=w1'], 'Error: Error loading wallet w1. Duplicate -wallet filename specified.')
 
         # should not initialize if one wallet is a copy of another
         shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
         exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
         self.nodes[0].assert_start_raises_init_error(
             ['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
 
         # should not initialize if wallet file is a symlink
         if os.name != 'nt':
             os.symlink('w8', wallet_dir('w8_symlink'))
             self.nodes[0].assert_start_raises_init_error(
                 ['-wallet=w8_symlink'], r'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX)
 
         # should not initialize if the specified walletdir does not exist
         self.nodes[0].assert_start_raises_init_error(
             ['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist')
         # should not initialize if the specified walletdir is not a directory
         not_a_dir = wallet_dir('notadir')
         open(not_a_dir, 'a', encoding="utf8").close()
         self.nodes[0].assert_start_raises_init_error(
             ['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory')
 
         # if wallets/ doesn't exist, datadir should be the default wallet dir
         wallet_dir2 = data_dir('walletdir')
         os.rename(wallet_dir(), wallet_dir2)
         self.start_node(0, ['-wallet=w4', '-wallet=w5'])
         assert_equal(set(node.listwallets()), {"w4", "w5"})
         w5 = wallet("w5")
         node.generatetoaddress(nblocks=1, address=w5.getnewaddress())
 
         # now if wallets/ exists again, but the rootdir is specified as the
         # walletdir, w4 and w5 should still be loaded
         os.rename(wallet_dir2, wallet_dir())
         self.restart_node(0, ['-wallet=w4', '-wallet=w5',
                               '-walletdir=' + data_dir()])
         assert_equal(set(node.listwallets()), {"w4", "w5"})
         w5 = wallet("w5")
         w5_info = w5.getwalletinfo()
         assert_equal(w5_info['immature_balance'], 50000000)
 
         competing_wallet_dir = os.path.join(
             self.options.tmpdir, 'competing_walletdir')
         os.mkdir(competing_wallet_dir)
         self.restart_node(0, ['-walletdir=' + competing_wallet_dir])
         exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!"
         self.nodes[1].assert_start_raises_init_error(
             ['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
 
         self.restart_node(0, extra_args)
 
         assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [
                      '', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy'])
 
         wallets = [wallet(w) for w in wallet_names]
         wallet_bad = wallet("bad")
 
         # check wallet names and balances
         node.generatetoaddress(nblocks=1, address=wallets[0].getnewaddress())
         for wallet_name, wallet in zip(wallet_names, wallets):
             info = wallet.getwalletinfo()
             assert_equal(info['immature_balance'],
                          50000000 if wallet is wallets[0] else 0)
             assert_equal(info['walletname'], wallet_name)
 
         # accessing invalid wallet fails
         assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
                                 wallet_bad.getwalletinfo)
 
         # accessing wallet RPC without using wallet endpoint fails
         assert_raises_rpc_error(-19, "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path).",
                                 node.getwalletinfo)
 
         w1, w2, w3, w4, *_ = wallets
         node.generatetoaddress(nblocks=101, address=w1.getnewaddress())
         assert_equal(w1.getbalance(), 100000000)
         assert_equal(w2.getbalance(), 0)
         assert_equal(w3.getbalance(), 0)
         assert_equal(w4.getbalance(), 0)
 
         w1.sendtoaddress(w2.getnewaddress(), 1000000)
         w1.sendtoaddress(w3.getnewaddress(), 2000000)
         w1.sendtoaddress(w4.getnewaddress(), 3000000)
         node.generatetoaddress(nblocks=1, address=w1.getnewaddress())
         assert_equal(w2.getbalance(), 1000000)
         assert_equal(w3.getbalance(), 2000000)
         assert_equal(w4.getbalance(), 3000000)
 
         batch = w1.batch([w1.getblockchaininfo.get_request(),
                           w1.getwalletinfo.get_request()])
         assert_equal(batch[0]["result"]["chain"], self.chain)
         assert_equal(batch[1]["result"]["walletname"], "w1")
 
         self.log.info('Check for per-wallet settxfee call')
         assert_equal(w1.getwalletinfo()['paytxfee'], 0)
         assert_equal(w2.getwalletinfo()['paytxfee'], 0)
         w2.settxfee(1000)
         assert_equal(w1.getwalletinfo()['paytxfee'], 0)
         assert_equal(w2.getwalletinfo()['paytxfee'], Decimal('1000.00'))
 
         self.log.info("Test dynamic wallet loading")
 
         self.restart_node(0, ['-nowallet'])
         assert_equal(node.listwallets(), [])
         assert_raises_rpc_error(-32601, "Method not found", node.getwalletinfo)
 
         self.log.info("Load first wallet")
         loadwallet_name = node.loadwallet(wallet_names[0])
         assert_equal(loadwallet_name['name'], wallet_names[0])
         assert_equal(node.listwallets(), wallet_names[0:1])
         node.getwalletinfo()
         w1 = node.get_wallet_rpc(wallet_names[0])
         w1.getwalletinfo()
 
         self.log.info("Load second wallet")
         loadwallet_name = node.loadwallet(wallet_names[1])
         assert_equal(loadwallet_name['name'], wallet_names[1])
         assert_equal(node.listwallets(), wallet_names[0:2])
         assert_raises_rpc_error(-19,
                                 "Wallet file not specified", node.getwalletinfo)
         w2 = node.get_wallet_rpc(wallet_names[1])
         w2.getwalletinfo()
 
         self.log.info("Concurrent wallet loading")
         threads = []
         for _ in range(3):
             n = node.cli if self.options.usecli else get_rpc_proxy(
                 node.url, 1, timeout=600, coveragedir=node.coverage_dir)
             t = Thread(target=test_load_unload, args=(n, wallet_names[2], ))
             t.start()
             threads.append(t)
         for t in threads:
             t.join()
         global got_loading_error
         assert_equal(got_loading_error, True)
 
         self.log.info("Load remaining wallets")
         for wallet_name in wallet_names[2:]:
             loadwallet_name = self.nodes[0].loadwallet(wallet_name)
             assert_equal(loadwallet_name['name'], wallet_name)
 
         assert_equal(set(self.nodes[0].listwallets()), set(wallet_names))
 
         # Fail to load if wallet doesn't exist
         path = os.path.join(self.options.tmpdir, "node0", "regtest",
                             "wallets", "wallets")
         assert_raises_rpc_error(
             -18,
             "Wallet file verification failed. Failed to load database path "
             "'{}'. Path does not exist.".format(path),
             self.nodes[0].loadwallet, 'wallets')
 
         # Fail to load duplicate wallets
         path = os.path.join(
             self.options.tmpdir,
             "node0",
             "regtest",
             "wallets",
             "w1",
             "wallet.dat")
         assert_raises_rpc_error(
             -4,
             "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(
                 path),
             self.nodes[0].loadwallet,
             wallet_names[0])
 
         # Fail to load duplicate wallets by different ways (directory and
         # filepath)
         path = os.path.join(
             self.options.tmpdir,
             "node0",
             "regtest",
             "wallets",
             "wallet.dat")
         assert_raises_rpc_error(
             -4,
             "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(
                 path),
             self.nodes[0].loadwallet,
             'wallet.dat')
 
         # Fail to load if one wallet is a copy of another
         assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid",
                                 self.nodes[0].loadwallet, 'w8_copy')
 
         # Fail to load if one wallet is a copy of another.
         # Test this twice to make sure that we don't re-introduce
         # https://github.com/bitcoin/bitcoin/issues/14304
         assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid",
                                 self.nodes[0].loadwallet, 'w8_copy')
 
         # Fail to load if wallet file is a symlink
         if os.name != 'nt':
             assert_raises_rpc_error(
                 -4,
                 "Wallet file verification failed. Invalid -wallet path 'w8_symlink'",
                 self.nodes[0].loadwallet,
                 'w8_symlink')
 
         # Fail to load if a directory is specified that doesn't contain a
         # wallet
         os.mkdir(wallet_dir('empty_wallet_dir'))
         path = os.path.join(self.options.tmpdir, "node0", "regtest",
                             "wallets", "empty_wallet_dir")
         assert_raises_rpc_error(
             -18,
             "Wallet file verification failed. Failed to load database "
             "path '{}'. Data is not in recognized format.".format(path),
             self.nodes[0].loadwallet, 'empty_wallet_dir')
 
         self.log.info("Test dynamic wallet creation.")
 
         # Fail to create a wallet if it already exists.
-        assert_raises_rpc_error(-4, "Wallet w2 already exists.",
-                                self.nodes[0].createwallet, 'w2')
+        path = os.path.join(self.options.tmpdir, "node0", "regtest",
+                            "wallets", "w2")
+        assert_raises_rpc_error(
+            -4,
+            f"Failed to create database path '{path}'. Database already exists.",
+            self.nodes[0].createwallet, 'w2')
 
         # Successfully create a wallet with a new name
         loadwallet_name = self.nodes[0].createwallet('w9')
         assert_equal(loadwallet_name['name'], 'w9')
         w9 = node.get_wallet_rpc('w9')
         assert_equal(w9.getwalletinfo()['walletname'], 'w9')
 
         assert 'w9' in self.nodes[0].listwallets()
 
         # Successfully create a wallet using a full path
         new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir')
         new_wallet_name = os.path.join(new_wallet_dir, 'w10')
         loadwallet_name = self.nodes[0].createwallet(new_wallet_name)
         assert_equal(loadwallet_name['name'], new_wallet_name)
         w10 = node.get_wallet_rpc(new_wallet_name)
         assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name)
 
         assert new_wallet_name in self.nodes[0].listwallets()
 
         self.log.info("Test dynamic wallet unloading")
 
         # Test `unloadwallet` errors
         assert_raises_rpc_error(-1, "JSON value is not a string as expected",
                                 self.nodes[0].unloadwallet)
         assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
                                 self.nodes[0].unloadwallet, "dummy")
         assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
                                 node.get_wallet_rpc("dummy").unloadwallet)
         assert_raises_rpc_error(-8, "Cannot unload the requested wallet",
                                 w1.unloadwallet, "w2"),
 
         # Successfully unload the specified wallet name
         self.nodes[0].unloadwallet("w1")
         assert 'w1' not in self.nodes[0].listwallets()
 
         # Successfully unload the wallet referenced by the request endpoint
         # Also ensure unload works during walletpassphrase timeout
         w2.encryptwallet('test')
         w2.walletpassphrase('test', 1)
         w2.unloadwallet()
         time.sleep(1.1)
         assert 'w2' not in self.nodes[0].listwallets()
 
         # Successfully unload all wallets
         for wallet_name in self.nodes[0].listwallets():
             self.nodes[0].unloadwallet(wallet_name)
         assert_equal(self.nodes[0].listwallets(), [])
         assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)",
                                 self.nodes[0].getwalletinfo)
 
         # Successfully load a previously unloaded wallet
         self.nodes[0].loadwallet('w1')
         assert_equal(self.nodes[0].listwallets(), ['w1'])
         assert_equal(w1.getwalletinfo()['walletname'], 'w1')
 
         assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [
                      '', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9'])
 
         # Test backing up and restoring wallets
         self.log.info("Test wallet backup")
         self.restart_node(0, ['-nowallet'])
         for wallet_name in wallet_names:
             self.nodes[0].loadwallet(wallet_name)
         for wallet_name in wallet_names:
             rpc = self.nodes[0].get_wallet_rpc(wallet_name)
             addr = rpc.getnewaddress()
             backup = os.path.join(self.options.tmpdir, 'backup.dat')
             rpc.backupwallet(backup)
             self.nodes[0].unloadwallet(wallet_name)
             shutil.copyfile(empty_wallet, wallet_file(wallet_name))
             self.nodes[0].loadwallet(wallet_name)
             assert_equal(rpc.getaddressinfo(addr)['ismine'], False)
             self.nodes[0].unloadwallet(wallet_name)
             shutil.copyfile(backup, wallet_file(wallet_name))
             self.nodes[0].loadwallet(wallet_name)
             assert_equal(rpc.getaddressinfo(addr)['ismine'], True)
 
         # Test .walletlock file is closed
         self.start_node(1)
         wallet = os.path.join(self.options.tmpdir, 'my_wallet')
         self.nodes[0].createwallet(wallet)
         assert_raises_rpc_error(-4, "Error initializing wallet database environment",
                                 self.nodes[1].loadwallet, wallet)
         self.nodes[0].unloadwallet(wallet)
         self.nodes[1].loadwallet(wallet)
 
 
 if __name__ == '__main__':
     MultiWalletTest().main()