diff --git a/src/outputtype.cpp b/src/outputtype.cpp
index 8c2b76494bd..30dd0e075ad 100644
--- a/src/outputtype.cpp
+++ b/src/outputtype.cpp
@@ -122,3 +122,21 @@ std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest)
     }
     return std::nullopt;
 }
+
+std::vector<std::string> GetOutputTypes()
+{
+    std::vector<std::string> v;
+    for (auto t : OUTPUT_TYPES) {
+        v.emplace_back("\"" + FormatOutputType(t) + "\"");
+    }
+    return v;
+}
+
+std::vector<std::string> GetLegacyOutputTypes()
+{
+    std::vector<std::string> v;
+    for (auto t : LEGACY_TYPES) {
+        v.emplace_back("\"" + FormatOutputType(t) + "\"");
+    }
+    return v;
+}
diff --git a/src/outputtype.h b/src/outputtype.h
index feef7991a60..b47dee6a8ec 100644
--- a/src/outputtype.h
+++ b/src/outputtype.h
@@ -12,6 +12,7 @@
 #include <array>
 #include <optional>
 #include <string>
+#include <unordered_set>
 #include <vector>
 
 enum class OutputType {
@@ -29,6 +30,16 @@ static constexpr auto OUTPUT_TYPES = std::array{
     OutputType::BECH32M,
 };
 
+/** OutputTypes supported by the LegacyScriptPubKeyMan */
+static constexpr auto LEGACY_TYPES = std::array{
+    OutputType::LEGACY,
+    OutputType::P2SH_SEGWIT,
+    OutputType::BECH32,
+};
+static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
+    LEGACY_TYPES.begin(), LEGACY_TYPES.end()
+};
+
 std::optional<OutputType> ParseOutputType(const std::string& str);
 const std::string& FormatOutputType(OutputType type);
 
@@ -51,4 +62,7 @@ CTxDestination AddAndGetDestinationForScript(FlatSigningProvider& keystore, cons
 /** Get the OutputType for a CTxDestination */
 std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest);
 
+std::vector<std::string> GetOutputTypes();
+std::vector<std::string> GetLegacyOutputTypes();
+
 #endif // BITCOIN_OUTPUTTYPE_H
diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp
index 5de2847be99..223786eaa3a 100644
--- a/src/rpc/output_script.cpp
+++ b/src/rpc/output_script.cpp
@@ -25,6 +25,8 @@
 #include <tuple>
 #include <vector>
 
+using util::Join;
+
 static RPCHelpMan validateaddress()
 {
     return RPCHelpMan{
@@ -96,7 +98,7 @@ static RPCHelpMan createmultisig()
                 {
                     {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
                 }},
-            {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
+            {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are " + Join(GetLegacyOutputTypes(), ", ") + "."},
         },
         RPCResult{
             RPCResult::Type::OBJ, "", "",
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index cfd09a2e101..3c7bcbc7321 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -26,6 +26,7 @@
 #include <walletinitinterface.h>
 
 using node::NodeContext;
+using util::Join;
 
 namespace wallet {
 class WalletInit : public WalletInitInterface
@@ -46,11 +47,10 @@ public:
 
 void WalletInit::AddWalletOptions(ArgsManager& argsman) const
 {
-    argsman.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", \"bech32\", or \"bech32m\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+    argsman.AddArg("-addresstype", strprintf("What type of addresses to use (%s), default: \"%s\")", Join(GetOutputTypes(), ", "), FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
     argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting many (possibly all) or none, instead of selecting on a per-output basis. Privacy is improved as addresses are mostly swept with fewer transactions and outputs are aggregated in clean change addresses. It may result in higher fees due to less optimal coin selection caused by this added limitation and possibly a larger-than-necessary number of inputs being used. Always enabled for wallets with \"avoid_reuse\" enabled, otherwise default: %u.", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
     argsman.AddArg("-changetype",
-                   "What type of change to use (\"legacy\", \"p2sh-segwit\", \"bech32\", or \"bech32m\"). Default is \"legacy\" when "
-                   "-addresstype=legacy, else it is an implementation detail.",
+                   strprintf("What type of change to use (%s). Default is \"legacy\" when -addresstype=legacy, otherwise it is an implementation detail.", Join(GetOutputTypes(), ", ")),
                    ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
     argsman.AddArg("-consolidatefeerate=<amt>", strprintf("The maximum feerate (in %s/kvB) at which transaction building may use more inputs than strictly necessary so that the wallet's UTXO pool can be reduced (default: %s).", CURRENCY_UNIT, FormatMoney(DEFAULT_CONSOLIDATE_FEERATE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
     argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index 1c2951deeec..4575c77dacf 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -17,6 +17,8 @@
 
 #include <univalue.h>
 
+using util::Join;
+
 namespace wallet {
 RPCHelpMan getnewaddress()
 {
@@ -26,7 +28,7 @@ RPCHelpMan getnewaddress()
                 "so payments received with the address will be associated with 'label'.\n",
                 {
                     {"label", RPCArg::Type::STR, RPCArg::Default{""}, "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."},
-                    {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+                    {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are " + Join(GetOutputTypes(), ", ") + "."},
                 },
                 RPCResult{
                     RPCResult::Type::STR, "address", "The new bitcoin address"
@@ -76,7 +78,7 @@ RPCHelpMan getrawchangeaddress()
                 "\nReturns a new Bitcoin address, for receiving change.\n"
                 "This is for use with raw transactions, NOT normal use.\n",
                 {
-                    {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+                    {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The address type to use. Options are " + Join(GetOutputTypes(), ", ") + "."},
                 },
                 RPCResult{
                     RPCResult::Type::STR, "address", "The address"
@@ -232,7 +234,7 @@ RPCHelpMan addmultisigaddress()
                         },
                         },
                     {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A label to assign the addresses to."},
-                    {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
+                    {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are " + Join(GetLegacyOutputTypes(), ", ") + "."},
                 },
                 RPCResult{
                     RPCResult::Type::OBJ, "", "",
@@ -529,6 +531,7 @@ RPCHelpMan getaddressinfo()
                     RPCResult::Type::OBJ, "", "",
                     {
                         {RPCResult::Type::STR, "address", "The bitcoin address validated."},
+                        {RPCResult::Type::STR, "address_type", "The type of bitcoin address; will be one of " + Join(GetOutputTypes(), ", ") + "."},
                         {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address."},
                         {RPCResult::Type::BOOL, "ismine", "If the address is yours."},
                         {RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."},
@@ -591,8 +594,8 @@ RPCHelpMan getaddressinfo()
 
     UniValue ret(UniValue::VOBJ);
 
-    std::string currentAddress = EncodeDestination(dest);
-    ret.pushKV("address", currentAddress);
+    ret.pushKV("address", EncodeDestination(dest));
+    ret.pushKV("address_type", FormatOutputType(OutputTypeFromDestination(dest).value()));
 
     CScript scriptPubKey = GetScriptForDestination(dest);
     ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 0cba830e2ab..39874016a17 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -29,6 +29,7 @@ using common::InvalidEstimateModeErrorMessage;
 using common::StringForFeeReason;
 using common::TransactionErrorString;
 using node::TransactionError;
+using util::Join;
 
 namespace wallet {
 std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
@@ -763,7 +764,7 @@ RPCHelpMan fundrawtransaction()
                             {"maxconf", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If add_inputs is specified, require inputs with at most this many confirmations."},
                             {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
                             {"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
-                            {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+                            {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are: " + Join(GetOutputTypes(), ", ") + "."},
                             {"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
                                                           "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
                                                           "e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
@@ -1221,7 +1222,7 @@ RPCHelpMan send()
                     {"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
                     {"change_address", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
                     {"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
-                    {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\" and \"bech32m\"."},
+                    {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are: " + Join(GetOutputTypes(), ", ") + "."},
                     {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB.", RPCArgOptions{.also_positional = true}},
                     {"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
                                           "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
@@ -1700,7 +1701,7 @@ RPCHelpMan walletcreatefundedpsbt()
                             {"maxconf", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If add_inputs is specified, require inputs with at most this many confirmations."},
                             {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
                             {"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
-                            {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+                            {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are: " + Join(GetOutputTypes(), ", ") + "."},
                             {"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only"},
                             {"lockUnspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"},
                             {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 5140ac8c059..2243413da58 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -21,6 +21,7 @@
 
 #include <univalue.h>
 
+using util::Join;
 
 namespace wallet {
 
@@ -930,7 +931,7 @@ static RPCHelpMan createwalletdescriptor()
         "The address type must be one that the wallet does not already have a descriptor for."
         + HELP_REQUIRING_PASSPHRASE,
         {
-            {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+            {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + Join(GetOutputTypes(), ", ") + "."},
             {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
                 {"internal", RPCArg::Type::BOOL, RPCArg::DefaultHint{"Both external and internal will be generated unless this parameter is specified"}, "Whether to only make one descriptor that is internal (if parameter is true) or external (if parameter is false)"},
                 {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"The HD key used by all other active descriptors"}, "The HD key that the wallet knows the private key of, listed using 'gethdkeys', to use for this descriptor's key"},
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index b620c6beb02..d6e0f425d1a 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -22,6 +22,7 @@
 #include <optional>
 
 using common::PSBTError;
+using util::Join;
 using util::ToString;
 
 namespace wallet {
@@ -31,7 +32,7 @@ const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
 util::Result<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type)
 {
     if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
-        return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")};
+        return util::Error{strprintf(_("Error: Legacy wallets only support the %s address types"), Join(GetLegacyOutputTypes(), ", "))};
     }
     assert(type != OutputType::BECH32M);
 
@@ -305,7 +306,7 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
 util::Result<CTxDestination> LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool)
 {
     if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
-        return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")};
+        return util::Error{strprintf(_("Error: Legacy wallets only support the %s address types"), Join(GetLegacyOutputTypes(), ", "))};
     }
     assert(type != OutputType::BECH32M);
 
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 0d19eb92229..f468bdf7c22 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -270,13 +270,6 @@ public:
     boost::signals2::signal<void (const ScriptPubKeyMan* spkm, int64_t new_birth_time)> NotifyFirstKeyTimeChanged;
 };
 
-/** OutputTypes supported by the LegacyScriptPubKeyMan */
-static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
-    OutputType::LEGACY,
-    OutputType::P2SH_SEGWIT,
-    OutputType::BECH32,
-};
-
 class DescriptorScriptPubKeyMan;
 
 // Manages the data for a LegacyScriptPubKeyMan.
diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py
index 4ce24ecb67d..4612530231b 100755
--- a/test/functional/rpc_help.py
+++ b/test/functional/rpc_help.py
@@ -126,7 +126,11 @@ class HelpRpcTest(BitcoinTestFramework):
                 f.write(self.nodes[0].help(call))
 
     def wallet_help(self):
-        assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')
+        assert '"legacy", "p2sh-segwit", "bech32", "bech32m".' in self.nodes[0].help('getaddressinfo')
+        assert '"legacy", "p2sh-segwit", "bech32".' in self.nodes[0].help('addmultisigaddress')
+        ret = self.nodes[0].help('getnewaddress')
+        assert '"legacy", "p2sh-segwit", "bech32", "bech32m".' in ret
+        assert 'getnewaddress ( "label" "address_type" )' in ret
         self.restart_node(0, extra_args=['-nowallet=1'])
         assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')
 
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index f2e1850e9db..98fad077857 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -104,6 +104,7 @@ class AddressTypeTest(BitcoinTestFramework):
         info = self.nodes[node].getaddressinfo(address)
         assert self.nodes[node].validateaddress(address)['isvalid']
         assert_equal(info.get('solvable'), True)
+        assert_equal(info['address_type'], "legacy" if typ == "p2sh-segwit" else typ)
 
         if not multisig and typ == 'legacy':
             # P2PKH
@@ -163,6 +164,7 @@ class AddressTypeTest(BitcoinTestFramework):
         info = self.nodes[node].getaddressinfo(address)
         assert 'desc' in info
         assert_equal(info['desc'], utxo['desc'])
+        assert_equal(info['address_type'], "legacy" if typ == "p2sh-segwit" else typ)
         assert self.nodes[node].validateaddress(address)['isvalid']
 
         # Use a ridiculously roundabout way to find the key origin info through