diff --git a/pkg/innerring/contracts.go b/pkg/innerring/contracts.go
new file mode 100644
index 0000000000..dcc59ed7f0
--- /dev/null
+++ b/pkg/innerring/contracts.go
@@ -0,0 +1,112 @@
+package innerring
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/nspcc-dev/neo-go/pkg/util"
+	"github.com/nspcc-dev/neofs-node/pkg/morph/client"
+	"github.com/spf13/viper"
+)
+
+type contracts struct {
+	neofs      util.Uint160 // in mainnet
+	netmap     util.Uint160 // in morph
+	balance    util.Uint160 // in morph
+	container  util.Uint160 // in morph
+	audit      util.Uint160 // in morph
+	proxy      util.Uint160 // in morph
+	processing util.Uint160 // in mainnet
+	reputation util.Uint160 // in morph
+	neofsID    util.Uint160 // in morph
+
+	alphabet alphabetContracts // in morph
+}
+
+func parseContracts(cfg *viper.Viper, morph *client.Client, withoutMainNet, withoutMainNotary, withoutSideNotary bool) (*contracts, error) {
+	var (
+		result = new(contracts)
+		err    error
+	)
+
+	if !withoutMainNet {
+		result.neofs, err = util.Uint160DecodeStringLE(cfg.GetString("contracts.neofs"))
+		if err != nil {
+			return nil, fmt.Errorf("can't get neofs script hash: %w", err)
+		}
+
+		if !withoutMainNotary {
+			result.processing, err = util.Uint160DecodeStringLE(cfg.GetString("contracts.processing"))
+			if err != nil {
+				return nil, fmt.Errorf("can't get processing script hash: %w", err)
+			}
+		}
+	}
+
+	if !withoutSideNotary {
+		result.proxy, err = parseContract(cfg, morph, "contracts.proxy", client.NNSProxyContractName)
+		if err != nil {
+			return nil, fmt.Errorf("can't get proxy script hash: %w", err)
+		}
+	}
+
+	targets := [...]struct {
+		cfgName string
+		nnsName string
+		dest    *util.Uint160
+	}{
+		{"contracts.netmap", client.NNSNetmapContractName, &result.netmap},
+		{"contracts.balance", client.NNSBalanceContractName, &result.balance},
+		{"contracts.container", client.NNSContainerContractName, &result.container},
+		{"contracts.audit", client.NNSAuditContractName, &result.audit},
+		{"contracts.reputation", client.NNSReputationContractName, &result.reputation},
+		{"contracts.neofsid", client.NNSNeoFSIDContractName, &result.neofsID},
+	}
+
+	for _, t := range targets {
+		*t.dest, err = parseContract(cfg, morph, t.cfgName, t.nnsName)
+		if err != nil {
+			name := strings.TrimPrefix(t.cfgName, "contracts.")
+			return nil, fmt.Errorf("can't get %s script hash: %w", name, err)
+		}
+	}
+
+	result.alphabet, err = parseAlphabetContracts(cfg, morph)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func parseAlphabetContracts(cfg *viper.Viper, morph *client.Client) (alphabetContracts, error) {
+	num := GlagoliticLetter(cfg.GetUint("contracts.alphabet.amount"))
+	alpha := newAlphabetContracts()
+
+	if num > lastLetterNum {
+		return nil, fmt.Errorf("amount of alphabet contracts overflows glagolitsa %d > %d", num, lastLetterNum)
+	}
+
+	for letter := az; letter < num; letter++ {
+		contractHash, err := parseContract(cfg, morph,
+			"contracts.alphabet."+letter.String(),
+			client.NNSAlphabetContractName(int(letter)),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("invalid alphabet %s contract: %w", letter, err)
+		}
+
+		alpha.set(letter, contractHash)
+	}
+
+	return alpha, nil
+}
+
+func parseContract(cfg *viper.Viper, morph *client.Client, cfgName, nnsName string) (res util.Uint160, err error) {
+	contractStr := cfg.GetString(cfgName)
+	if len(contractStr) == 0 {
+		return morph.NNSContractAddress(nnsName)
+	}
+
+	return util.Uint160DecodeStringLE(contractStr)
+}
diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go
index 7ee8f43565..f24560dde0 100644
--- a/pkg/innerring/innerring.go
+++ b/pkg/innerring/innerring.go
@@ -120,20 +120,6 @@ type (
 		runners []func(chan<- error)
 	}
 
-	contracts struct {
-		neofs      util.Uint160 // in mainnet
-		netmap     util.Uint160 // in morph
-		balance    util.Uint160 // in morph
-		container  util.Uint160 // in morph
-		audit      util.Uint160 // in morph
-		proxy      util.Uint160 // in morph
-		processing util.Uint160 // in mainnet
-		reputation util.Uint160 // in morph
-		neofsID    util.Uint160 // in morph
-
-		alphabet alphabetContracts // in morph
-	}
-
 	chainParams struct {
 		log  *zap.Logger
 		cfg  *viper.Viper
@@ -418,6 +404,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error
 	// get all script hashes of contracts
 	server.contracts, err = parseContracts(
 		cfg,
+		server.morphClient,
 		server.withoutMainNet,
 		server.mainNotaryConfig.disabled,
 		server.sideNotaryConfig.disabled,
@@ -931,78 +918,6 @@ func createClient(ctx context.Context, p *chainParams) (*client.Client, error) {
 	)
 }
 
-func parseContracts(cfg *viper.Viper, withoutMainNet, withoutMainNotary, withoutSideNotary bool) (*contracts, error) {
-	var (
-		result = new(contracts)
-		err    error
-	)
-
-	if !withoutMainNet {
-		result.neofs, err = util.Uint160DecodeStringLE(cfg.GetString("contracts.neofs"))
-		if err != nil {
-			return nil, fmt.Errorf("ir: can't read neofs script-hash: %w", err)
-		}
-
-		if !withoutMainNotary {
-			result.processing, err = util.Uint160DecodeStringLE(cfg.GetString("contracts.processing"))
-			if err != nil {
-				return nil, fmt.Errorf("ir: can't read processing script-hash: %w", err)
-			}
-		}
-	}
-
-	if !withoutSideNotary {
-		result.proxy, err = util.Uint160DecodeStringLE(cfg.GetString("contracts.proxy"))
-		if err != nil {
-			return nil, fmt.Errorf("ir: can't read proxy script-hash: %w", err)
-		}
-	}
-
-	netmapContractStr := cfg.GetString("contracts.netmap")
-	balanceContractStr := cfg.GetString("contracts.balance")
-	containerContractStr := cfg.GetString("contracts.container")
-	auditContractStr := cfg.GetString("contracts.audit")
-	reputationContractStr := cfg.GetString("contracts.reputation")
-	neofsIDContractStr := cfg.GetString("contracts.neofsid")
-
-	result.netmap, err = util.Uint160DecodeStringLE(netmapContractStr)
-	if err != nil {
-		return nil, fmt.Errorf("ir: can't read netmap script-hash: %w", err)
-	}
-
-	result.balance, err = util.Uint160DecodeStringLE(balanceContractStr)
-	if err != nil {
-		return nil, fmt.Errorf("ir: can't read balance script-hash: %w", err)
-	}
-
-	result.container, err = util.Uint160DecodeStringLE(containerContractStr)
-	if err != nil {
-		return nil, fmt.Errorf("ir: can't read container script-hash: %w", err)
-	}
-
-	result.audit, err = util.Uint160DecodeStringLE(auditContractStr)
-	if err != nil {
-		return nil, fmt.Errorf("ir: can't read audit script-hash: %w", err)
-	}
-
-	result.reputation, err = util.Uint160DecodeStringLE(reputationContractStr)
-	if err != nil {
-		return nil, fmt.Errorf("ir: can't read reputation script-hash: %w", err)
-	}
-
-	result.neofsID, err = util.Uint160DecodeStringLE(neofsIDContractStr)
-	if err != nil {
-		return nil, fmt.Errorf("ir: can't read NeoFS ID script-hash: %w", err)
-	}
-
-	result.alphabet, err = parseAlphabetContracts(cfg)
-	if err != nil {
-		return nil, err
-	}
-
-	return result, nil
-}
-
 func parsePredefinedValidators(cfg *viper.Viper) (keys.PublicKeys, error) {
 	publicKeyStrings := cfg.GetStringSlice("morph.validators")
 
@@ -1026,28 +941,6 @@ func ParsePublicKeysFromStrings(pubKeys []string) (keys.PublicKeys, error) {
 	return publicKeys, nil
 }
 
-func parseAlphabetContracts(cfg *viper.Viper) (alphabetContracts, error) {
-	num := GlagoliticLetter(cfg.GetUint("contracts.alphabet.amount"))
-	alpha := newAlphabetContracts()
-
-	if num > lastLetterNum {
-		return nil, fmt.Errorf("amount of alphabet contracts overflows glagolitsa %d > %d", num, lastLetterNum)
-	}
-
-	for letter := az; letter < num; letter++ {
-		contractStr := cfg.GetString("contracts.alphabet." + letter.String())
-
-		contractHash, err := util.Uint160DecodeStringLE(contractStr)
-		if err != nil {
-			return nil, fmt.Errorf("invalid alphabet %s contract: %s: %w", letter.String(), contractStr, err)
-		}
-
-		alpha.set(letter, contractHash)
-	}
-
-	return alpha, nil
-}
-
 func (s *Server) initConfigFromBlockchain() error {
 	// get current epoch
 	epoch, err := s.netmapClient.Epoch()