diff --git a/CHANGELOG.md b/CHANGELOG.md
index 792f7436d8..0bc0ea21dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ Changelog for FrostFS Node
 - Doc for extended headers (#2128)
 - New `frostfs_node_object_container_size` metric for tracking size of reqular objects in a container (#2116)
 - New `frostfs_node_object_payload_size` metric for tracking size of reqular objects on a single shard (#1794)
+- Add command `frostfs-adm morph netmap-candidates` (#1889)
 
 ### Changed
 - `common.PrintVerbose` prints via `cobra.Command.Printf` (#1962)
diff --git a/cmd/frostfs-adm/internal/commonflags/flags.go b/cmd/frostfs-adm/internal/commonflags/flags.go
index 081023642a..b31b13255a 100644
--- a/cmd/frostfs-adm/internal/commonflags/flags.go
+++ b/cmd/frostfs-adm/internal/commonflags/flags.go
@@ -4,4 +4,8 @@ const (
 	ConfigFlag          = "config"
 	ConfigFlagShorthand = "c"
 	ConfigFlagUsage     = "Config file"
+
+	Verbose          = "verbose"
+	VerboseShorthand = "v"
+	VerboseUsage     = "Verbose output"
 )
diff --git a/cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go b/cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go
new file mode 100644
index 0000000000..241e274a49
--- /dev/null
+++ b/cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go
@@ -0,0 +1,29 @@
+package morph
+
+import (
+	"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
+	commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
+	"github.com/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
+	"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+)
+
+func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
+	c, err := getN3Client(viper.GetViper())
+	commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
+
+	inv := invoker.New(c, nil)
+
+	cs, err := c.GetContractStateByID(1)
+	commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
+
+	nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
+	commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
+
+	res, err := inv.Call(nmHash, "netmapCandidates")
+	commonCmd.ExitOnErr(cmd, "can't fetch list of network config keys from the netmap contract", err)
+	nm, err := netmap.DecodeNetMap(res.Stack)
+	commonCmd.ExitOnErr(cmd, "unable to decode netmap: %w", err)
+	commonCmd.PrettyPrintNetMap(cmd, *nm, !viper.GetBool(commonflags.Verbose))
+}
diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go
index 603ea23ac1..c1d815607e 100644
--- a/cmd/frostfs-adm/internal/modules/morph/root.go
+++ b/cmd/frostfs-adm/internal/modules/morph/root.go
@@ -226,6 +226,16 @@ var (
 		},
 		RunE: depositNotary,
 	}
+
+	netmapCandidatesCmd = &cobra.Command{
+		Use:   "netmap-candidates",
+		Short: "List netmap candidates nodes",
+		PreRun: func(cmd *cobra.Command, _ []string) {
+			_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
+			_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
+		},
+		Run: listNetmapCandidatesNodes,
+	}
 )
 
 func init() {
@@ -323,4 +333,7 @@ func init() {
 	depositNotaryCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
 	depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit")
 	depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
+
+	RootCmd.AddCommand(netmapCandidatesCmd)
+	netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
 }
diff --git a/cmd/frostfs-adm/internal/modules/root.go b/cmd/frostfs-adm/internal/modules/root.go
index 9e1739867e..4ac9a9b20c 100644
--- a/cmd/frostfs-adm/internal/modules/root.go
+++ b/cmd/frostfs-adm/internal/modules/root.go
@@ -34,6 +34,8 @@ func init() {
 	rootCmd.SetOut(os.Stdout)
 
 	rootCmd.PersistentFlags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
+	rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, commonflags.VerboseUsage)
+	_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
 	rootCmd.Flags().Bool("version", false, "Application version")
 
 	rootCmd.AddCommand(config.RootCmd)
diff --git a/pkg/morph/client/netmap/netmap.go b/pkg/morph/client/netmap/netmap.go
index 071154313a..3943e8bc70 100644
--- a/pkg/morph/client/netmap/netmap.go
+++ b/pkg/morph/client/netmap/netmap.go
@@ -22,7 +22,7 @@ func (c *Client) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) {
 			epochSnapshotMethod, err)
 	}
 
-	nm, err := decodeNetMap(res)
+	nm, err := DecodeNetMap(res)
 	if err != nil {
 		return nil, err
 	}
@@ -61,10 +61,10 @@ func (c *Client) NetMap() (*netmap.NetMap, error) {
 			netMapMethod, err)
 	}
 
-	return decodeNetMap(res)
+	return DecodeNetMap(res)
 }
 
-func decodeNetMap(resStack []stackitem.Item) (*netmap.NetMap, error) {
+func DecodeNetMap(resStack []stackitem.Item) (*netmap.NetMap, error) {
 	var nm netmap.NetMap
 
 	if len(resStack) > 0 {
diff --git a/pkg/morph/client/netmap/snapshot.go b/pkg/morph/client/netmap/snapshot.go
index b11cfd39f5..fc13a8eff3 100644
--- a/pkg/morph/client/netmap/snapshot.go
+++ b/pkg/morph/client/netmap/snapshot.go
@@ -16,5 +16,5 @@ func (c *Client) GetNetMap(diff uint64) (*netmap.NetMap, error) {
 		return nil, err
 	}
 
-	return decodeNetMap(res)
+	return DecodeNetMap(res)
 }