Repository: skalenetwork/IMA (language: python)

Issue:
Enhancement/merge conflict from beta



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a22aa484f..abc42208e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -120,7 +120,7 @@ jobs:
     
     - name: Test upgrade
       run: bash ./proxy/scripts/test_upgrade.sh
-    
+
     - name: Run tests
       working-directory: ${{env.working-directory}}
       run: |
diff --git a/agent/cli.js b/agent/cli.js
index c5806feb6..404ab9743 100644
--- a/agent/cli.js
+++ b/agent/cli.js
@@ -1396,6 +1396,8 @@ function ima_common_init() {
             "deposit_box_erc721_address",
             "deposit_box_erc1155_abi",
             "deposit_box_erc1155_address",
+            "deposit_box_erc721_with_metadata_abi",
+            "deposit_box_erc721_with_metadata_address",
             "community_pool_abi",
             "community_pool_address"
         ] );
@@ -1410,6 +1412,8 @@ function ima_common_init() {
             "token_manager_erc721_address",
             "token_manager_erc1155_abi",
             "token_manager_erc1155_address",
+            "token_manager_erc721_with_metadata_abi",
+            "token_manager_erc721_with_metadata_address",
             "message_proxy_chain_abi",
             "message_proxy_chain_address",
             "token_manager_linker_abi",
@@ -1428,6 +1432,8 @@ function ima_common_init() {
             "token_manager_erc721_address",
             "token_manager_erc1155_abi",
             "token_manager_erc1155_address",
+            "token_manager_erc721_with_metadata_abi",
+            "token_manager_erc721_with_metadata_address",
             "message_proxy_chain_abi",
             "message_proxy_chain_address",
             "token_manager_linker_abi",
@@ -1437,18 +1443,20 @@ function ima_common_init() {
         ] );
     }
 
-    // deposit_box_eth_address       --> deposit_box_eth_abi
-    // deposit_box_erc20_address     --> deposit_box_erc20_abi
-    // deposit_box_erc721_address    --> deposit_box_erc721_abi
-    // deposit_box_erc1155_address   --> deposit_box_erc1155_abi
-    // linker_address                --> linker_abi
-    // token_manager_eth_address     --> token_manager_eth_abi
-    // token_manager_erc20_address   --> token_manager_erc20_abi
-    // token_manager_erc721_address  --> token_manager_erc721_abi
-    // token_manager_erc1155_address --> token_manager_erc1155_abi
-    // token_manager_linker_address  --> token_manager_linker_abi
-    // message_proxy_mainnet_address --> message_proxy_mainnet_abi
-    // message_proxy_chain_address   --> message_proxy_chain_abi
+    // deposit_box_eth_address                    --> deposit_box_eth_abi
+    // deposit_box_erc20_address                  --> deposit_box_erc20_abi
+    // deposit_box_erc721_address                 --> deposit_box_erc721_abi
+    // deposit_box_erc1155_address                --> deposit_box_erc1155_abi
+    // deposit_box_erc721_with_metadata_address   --> deposit_box_erc721_with_metadata_abi
+    // linker_address                             --> linker_abi
+    // token_manager_eth_address                  --> token_manager_eth_abi
+    // token_manager_erc20_address                --> token_manager_erc20_abi
+    // token_manager_erc721_address               --> token_manager_erc721_abi
+    // token_manager_erc1155_address              --> token_manager_erc1155_abi
+    // token_manager_erc721_with_metadata_address --> token_manager_erc721_with_metdata_abi
+    // token_manager_linker_address               --> token_manager_linker_abi
+    // message_proxy_mainnet_address              --> message_proxy_mainnet_abi
+    // message_proxy_chain_address                --> message_proxy_chain_abi
 
     //
     if( imaState.strURL_main_net && typeof imaState.strURL_main_net == "string" && imaState.strURL_main_net.length > 0 ) {
@@ -1492,6 +1500,7 @@ function ima_common_init() {
         imaState.jo_deposit_box_erc20 = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.deposit_box_erc20_abi, imaState.joAbiPublishResult_main_net.deposit_box_erc20_address ); // only main net
         imaState.jo_deposit_box_erc721 = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.deposit_box_erc721_abi, imaState.joAbiPublishResult_main_net.deposit_box_erc721_address ); // only main net
         imaState.jo_deposit_box_erc1155 = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.deposit_box_erc1155_abi, imaState.joAbiPublishResult_main_net.deposit_box_erc1155_address ); // only main net
+        imaState.jo_deposit_box_erc721_with_metadata = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.deposit_box_erc721_with_metadata_abi, imaState.joAbiPublishResult_main_net.deposit_box_erc721_with_metadata_address ); // only main net
         imaState.jo_community_pool = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.community_pool_abi, imaState.joAbiPublishResult_main_net.community_pool_address ); // only main net
         imaState.jo_linker = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.linker_abi, imaState.joAbiPublishResult_main_net.linker_address ); // only main net
         imaState.jo_message_proxy_main_net = new imaState.w3_main_net.eth.Contract( imaState.joAbiPublishResult_main_net.message_proxy_mainnet_abi, imaState.joAbiPublishResult_main_net.message_proxy_mainnet_address );
@@ -1501,6 +1510,7 @@ function ima_common_init() {
         imaState.jo_token_manager_erc20 = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.token_manager_erc20_abi, imaState.joAbiPublishResult_s_chain.token_manager_erc20_address ); // only s-chain
         imaState.jo_token_manager_erc721 = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.token_manager_erc721_abi, imaState.joAbiPublishResult_s_chain.token_manager_erc721_address ); // only s-chain
         imaState.jo_token_manager_erc1155 = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.token_manager_erc1155_abi, imaState.joAbiPublishResult_s_chain.token_manager_erc1155_address ); // only s-chain
+        imaState.jo_token_manager_erc721_with_metadata = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.token_manager_erc721_with_metadata_abi, imaState.joAbiPublishResult_s_chain.token_manager_erc721_with_metadata_address ); // only s-chain
         imaState.jo_community_locker = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.community_locker_abi, imaState.joAbiPublishResult_s_chain.community_locker_address ); // only s-chain
         imaState.jo_message_proxy_s_chain = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.message_proxy_chain_abi, imaState.joAbiPublishResult_s_chain.message_proxy_chain_address );
         imaState.jo_token_manager_linker = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_s_chain.token_manager_linker_abi, imaState.joAbiPublishResult_s_chain.token_manager_linker_address );
@@ -1513,6 +1523,7 @@ function ima_common_init() {
         imaState.jo_token_manager_erc20_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.token_manager_erc20_abi, imaState.joAbiPublishResult_t_chain.token_manager_erc20_address ); // only s-chain
         imaState.jo_token_manager_erc721_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.token_manager_erc721_abi, imaState.joAbiPublishResult_t_chain.token_manager_erc721_address ); // only s-chain
         imaState.jo_token_manager_erc1155_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.token_manager_erc1155_abi, imaState.joAbiPublishResult_t_chain.token_manager_erc1155_address ); // only s-chain
+        imaState.jo_token_manager_erc721_with_metadata_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.token_manager_erc721_with_metadata_abi, imaState.joAbiPublishResult_t_chain.token_manager_erc721_with_metadata_address ); // only s-chain
         imaState.jo_community_locker_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.community_locker_abi, imaState.joAbiPublishResult_t_chain.community_locker_address ); // only s-chain
         imaState.jo_message_proxy_s_chain_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.message_proxy_chain_abi, imaState.joAbiPublishResult_t_chain.message_proxy_chain_address );
         imaState.jo_token_manager_linker_target = new imaState.w3_s_chain.eth.Contract( imaState.joAbiPublishResult_t_chain.token_manager_linker_abi, imaState.joAbiPublishResult_t_chain.token_manager_linker_address );
@@ -1558,48 +1569,51 @@ function ima_common_init() {
 
     if( isPrintGathered ) {
         log.write( cc.bright( "IMA contracts(Main Net):" ) + "\n" );
-        log.write( cc.sunny( "DepositBoxEth" ) + cc.debug( "................address is....." ) + oct( imaState.jo_deposit_box_eth ) + "\n" );
-        log.write( cc.sunny( "DepositBoxERC20" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_deposit_box_erc20 ) + "\n" );
-        log.write( cc.sunny( "DepositBoxERC721" ) + cc.debug( ".............address is....." ) + oct( imaState.jo_deposit_box_erc721 ) + "\n" );
-        log.write( cc.sunny( "DepositBoxERC1155" ) + cc.debug( "............address is....." ) + oct( imaState.jo_deposit_box_erc1155 ) + "\n" );
-        log.write( cc.sunny( "CommunityPool" ) + cc.debug( "................address is....." ) + oct( imaState.jo_community_pool ) + "\n" );
-        log.write( cc.sunny( "MessageProxy" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_message_proxy_main_net ) + "\n" );
-        log.write( cc.sunny( "Linker" ) + cc.debug( ".......................address is....." ) + oct( imaState.jo_linker ) + "\n" );
+        log.write( cc.sunny( "DepositBoxEth" ) + cc.debug( "...................address is....." ) + oct( imaState.jo_deposit_box_eth ) + "\n" );
+        log.write( cc.sunny( "DepositBoxERC20" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_deposit_box_erc20 ) + "\n" );
+        log.write( cc.sunny( "DepositBoxERC721" ) + cc.debug( "................address is....." ) + oct( imaState.jo_deposit_box_erc721 ) + "\n" );
+        log.write( cc.sunny( "DepositBoxERC1155" ) + cc.debug( "...............address is....." ) + oct( imaState.jo_deposit_box_erc1155 ) + "\n" );
+        log.write( cc.sunny( "DepositBoxERC721WithMetadata" ) + cc.debug( "....address is....." ) + oct( imaState.jo_deposit_box_erc721_with_metadata ) + "\n" );
+        log.write( cc.sunny( "CommunityPool" ) + cc.debug( "...................address is....." ) + oct( imaState.jo_community_pool ) + "\n" );
+        log.write( cc.sunny( "MessageProxy" ) + cc.debug( "....................address is....." ) + oct( imaState.jo_message_proxy_main_net ) + "\n" );
+        log.write( cc.sunny( "Linker" ) + cc.debug( "..........................address is....." ) + oct( imaState.jo_linker ) + "\n" );
         log.write( cc.bright( "IMA contracts(S-Chain):" ) + "\n" );
-        log.write( cc.sunny( "TokenManagerEth" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_token_manager_eth ) + "\n" );
-        log.write( cc.sunny( "TokenManagerERC20" ) + cc.debug( "............address is....." ) + oct( imaState.jo_token_manager_erc20 ) + "\n" );
-        log.write( cc.sunny( "TokenManagerERC721" ) + cc.debug( " ..........address is....." ) + oct( imaState.jo_token_manager_erc721 ) + "\n" );
-        log.write( cc.sunny( "TokenManagerERC1155" ) + cc.debug( "..........address is....." ) + oct( imaState.jo_token_manager_erc1155 ) + "\n" );
-        log.write( cc.sunny( "CommunityLocker" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_community_locker ) + "\n" );
-        log.write( cc.sunny( "MessageProxy" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_message_proxy_s_chain ) + "\n" );
-        log.write( cc.sunny( "TokenManagerLinker" ) + cc.debug( " ..........address is....." ) + oct( imaState.jo_token_manager_linker ) + "\n" );
-        log.write( cc.sunny( "ERC20" ) + cc.debug( " .......................address is....." ) + oct( imaState.eth_erc20 ) + "\n" );
+        log.write( cc.sunny( "TokenManagerEth" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_token_manager_eth ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC20" ) + cc.debug( "...............address is....." ) + oct( imaState.jo_token_manager_erc20 ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC721" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_token_manager_erc721 ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC1155" ) + cc.debug( ".............address is....." ) + oct( imaState.jo_token_manager_erc1155 ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC721WithMetadata" ) + cc.debug( "..address is....." ) + oct( imaState.jo_token_manager_erc721_with_metadata ) + "\n" );
+        log.write( cc.sunny( "CommunityLocker" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_community_locker ) + "\n" );
+        log.write( cc.sunny( "MessageProxy" ) + cc.debug( "....................address is....." ) + oct( imaState.jo_message_proxy_s_chain ) + "\n" );
+        log.write( cc.sunny( "TokenManagerLinker" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_token_manager_linker ) + "\n" );
+        log.write( cc.sunny( "ERC20" ) + cc.debug( " ..........................address is....." ) + oct( imaState.eth_erc20 ) + "\n" );
         // log.write( "S-Chain  " ) + cc.sunny( "ERC721" ) + cc.debug( " ......................address is....." ) + oct( imaState.eth_erc721 ) + "\n" );
         // log.write( "S-Chain  " ) + cc.sunny( "ERC1155" ) + cc.debug( " .....................address is....." ) + oct( imaState.eth_erc1155 ) + "\n" );
         log.write( cc.bright( "IMA contracts(Target S-Chain):" ) + "\n" );
         // log.write( "S-Chain  " ) + cc.sunny( "TokenManagerEth" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_token_manager_eth_target ) + "\n" );
-        log.write( cc.sunny( "TokenManagerERC20" ) + cc.debug( "............address is....." ) + oct( imaState.jo_token_manager_erc20_target ) + "\n" );
-        log.write( cc.sunny( "TokenManagerERC721" ) + cc.debug( " ..........address is....." ) + oct( imaState.jo_token_manager_erc721_target ) + "\n" );
-        log.write( cc.sunny( "TokenManagerERC1155" ) + cc.debug( "..........address is....." ) + oct( imaState.jo_token_manager_erc1155_target ) + "\n" );
-        log.write( cc.sunny( "CommunityLocker" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_community_locker_target ) + "\n" );
-        log.write( cc.sunny( "MessageProxy" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_message_proxy_s_chain_target ) + "\n" );
-        log.write( cc.sunny( "TokenManagerLinker" ) + cc.debug( " ..........address is....." ) + oct( imaState.jo_token_manager_linker_target ) + "\n" );
-        log.write( cc.sunny( "ERC20" ) + cc.debug( " .......................address is....." ) + oct( imaState.eth_erc20_target ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC20" ) + cc.debug( "...............address is....." ) + oct( imaState.jo_token_manager_erc20_target ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC721" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_token_manager_erc721_target ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC1155" ) + cc.debug( ".............address is....." ) + oct( imaState.jo_token_manager_erc1155_target ) + "\n" );
+        log.write( cc.sunny( "TokenManagerERC721WithMetadata" ) + cc.debug( "..address is....." ) + oct( imaState.jo_token_manager_erc721_with_metadata_target ) + "\n" );
+        log.write( cc.sunny( "CommunityLocker" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_community_locker_target ) + "\n" );
+        log.write( cc.sunny( "MessageProxy" ) + cc.debug( "....................address is....." ) + oct( imaState.jo_message_proxy_s_chain_target ) + "\n" );
+        log.write( cc.sunny( "TokenManagerLinker" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_token_manager_linker_target ) + "\n" );
+        log.write( cc.sunny( "ERC20" ) + cc.debug( " ..........................address is....." ) + oct( imaState.eth_erc20_target ) + "\n" );
         // log.write( "S-Chain  " ) + cc.sunny( "ERC721" ) + cc.debug( " ......................address is....." ) + oct( imaState.eth_erc721_target ) + "\n" );
         // log.write( "S-Chain  " ) + cc.sunny( "ERC1155" ) + cc.debug( " .....................address is....." ) + oct( imaState.eth_erc1155_target ) + "\n" );
 
         // if( imaState.bHaveSkaleManagerABI ) {
         log.write( cc.bright( "Skale Manager contracts:" ) + "\n" );
-        log.write( cc.sunny( "ConstantsHolder" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_constants_holder ) + "\n" );
-        log.write( cc.sunny( "Nodes" ) + cc.debug( "........................address is....." ) + oct( imaState.jo_nodes ) + "\n" );
-        log.write( cc.sunny( "KeyStorage" ) + cc.debug( "...................address is....." ) + oct( imaState.jo_key_storage ) + "\n" );
-        log.write( cc.sunny( "Schains" ) + cc.debug( "......................address is....." ) + oct( imaState.jo_schains ) + "\n" );
-        log.write( cc.sunny( "SchainsInternal" ) + cc.debug( "..............address is....." ) + oct( imaState.jo_schains_internal ) + "\n" );
-        log.write( cc.sunny( "SkaleDKG" ) + cc.debug( ".....................address is....." ) + oct( imaState.jo_skale_dkg ) + "\n" );
-        log.write( cc.sunny( "SkaleManager" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_skale_manager ) + "\n" );
-        log.write( cc.sunny( "SkaleToken" ) + cc.debug( "...................address is....." ) + oct( imaState.jo_skale_token ) + "\n" );
-        log.write( cc.sunny( "ValidatorService" ) + cc.debug( ".............address is....." ) + oct( imaState.jo_validator_service ) + "\n" );
-        log.write( cc.sunny( "Wallets" ) + cc.debug( "......................address is....." ) + oct( imaState.jo_wallets ) + "\n" );
+        log.write( cc.sunny( "ConstantsHolder" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_constants_holder ) + "\n" );
+        log.write( cc.sunny( "Nodes" ) + cc.debug( "...........................address is....." ) + oct( imaState.jo_nodes ) + "\n" );
+        log.write( cc.sunny( "KeyStorage" ) + cc.debug( "......................address is....." ) + oct( imaState.jo_key_storage ) + "\n" );
+        log.write( cc.sunny( "Schains" ) + cc.debug( ".........................address is....." ) + oct( imaState.jo_schains ) + "\n" );
+        log.write( cc.sunny( "SchainsInternal" ) + cc.debug( ".................address is....." ) + oct( imaState.jo_schains_internal ) + "\n" );
+        log.write( cc.sunny( "SkaleDKG" ) + cc.debug( "........................address is....." ) + oct( imaState.jo_skale_dkg ) + "\n" );
+        log.write( cc.sunny( "SkaleManager" ) + cc.debug( "....................address is....." ) + oct( imaState.jo_skale_manager ) + "\n" );
+        log.write( cc.sunny( "SkaleToken" ) + cc.debug( "......................address is....." ) + oct( imaState.jo_skale_token ) + "\n" );
+        log.write( cc.sunny( "ValidatorService" ) + cc.debug( "................address is....." ) + oct( imaState.jo_validator_service ) + "\n" );
+        log.write( cc.sunny( "Wallets" ) + cc.debug( ".........................address is....." ) + oct( imaState.jo_wallets ) + "\n" );
         // } else
         //     log.write( cc.error( "WARNING:" ) + " " + cc.warning( "no Skale Manager contracts to list, Skale Manager ABI was not provided" ) + "\n" );
     } // if( isPrintGathered )
diff --git a/agent/main.js b/agent/main.js
index a6479b098..32a6b6f29 100644
--- a/agent/main.js
+++ b/agent/main.js
@@ -167,6 +167,7 @@ global.imaState = {
     "jo_deposit_box_erc20": null, // only main net
     "jo_deposit_box_erc721": null, // only main net
     "jo_deposit_box_erc1155": null, // only main net
+    "jo_deposit_box_erc721_with_metadata": null, // only main net
     "jo_linker": null, // only main net
 
     "jo_token_manager_eth": null, // only s-chain
@@ -177,6 +178,8 @@ global.imaState = {
     "jo_token_manager_erc721_target": null, // only s-chain target
     "jo_token_manager_erc1155": null, // only s-chain
     "jo_token_manager_erc1155_target": null, // only s-chain target
+    "jo_token_manager_erc721_with_metadata": null, // only s-chain target
+    "jo_token_manager_erc721_with_metadata_target": null, // only s-chain target
     "jo_community_locker": null, // only s-chain
     "jo_community_locker_target": null, // only s-chain target
     "jo_message_proxy_main_net": null,
@@ -1058,6 +1061,8 @@ imaCLI.parse( {
                 // const jo_token_manager_erc721_dst = isForward ? imaState.jo_token_manager_erc721_target : imaState.jo_token_manager_erc721
                 const jo_token_manager_erc1155_src = isForward ? imaState.jo_token_manager_erc1155 : imaState.jo_token_manager_erc1155_target;
                 // const jo_token_manager_erc1155_dst = isForward ? imaState.jo_token_manager_erc1155_target : imaState.jo_token_manager_erc1155
+                // const jo_token_manager_erc72_with_metadata_src = isForward ? imaState.jo_token_manager_erc721_with_metadata : imaState.jo_token_manager_erc721_with_metadata_target;
+                // const jo_token_manager_erc72_with_metadata_dst = isForward ? imaState.jo_token_manager_erc721_with_metadata_target : imaState.jo_token_manager_erc721_with_metadata
                 // const strChainName_src = isForward ? imaState.strChainName_s_chain : imaState.strChainName_t_chain;
                 const strChainName_dst = isForward ? imaState.strChainName_t_chain : imaState.strChainName_s_chain;
                 const strCoinNameErc20_src = isForward ? imaState.strCoinNameErc20_s_chain : imaState.strCoinNameErc20_t_chain;
@@ -2291,6 +2296,7 @@ async function register_step1( isPrintSummaryRegistrationCosts ) {
             imaState.jo_token_manager_erc20, // only s-chain
             imaState.jo_token_manager_erc721, // only s-chain
             imaState.jo_token_manager_erc1155, // only s-chain
+            imaState.jo_token_manager_erc721_with_metadata, // only s-chain
             imaState.jo_community_locker, // only s-chain
             imaState.jo_token_manager_linker, // only s-chain
             imaState.strChainName_s_chain,
diff --git a/npms/skale-ima/index.js b/npms/skale-ima/index.js
index 526d99fde..27f20fc83 100644
--- a/npms/skale-ima/index.js
+++ b/npms/skale-ima/index.js
@@ -1549,6 +1549,7 @@ async function register_s_chain_in_deposit_boxes( // step 1
     jo_token_manager_erc20, // only s-chain
     jo_token_manager_erc721, // only s-chain
     jo_token_manager_erc1155, // only s-chain
+    jo_token_manager_erc721_with_metadata, // only s-chain
     jo_community_locker, // only s-chain
     jo_token_manager_linker,
     chain_id_s_chain,
@@ -1580,7 +1581,8 @@ async function register_s_chain_in_deposit_boxes( // step 1
                 jo_token_manager_eth.options.address, // call params
                 jo_token_manager_erc20.options.address, // call params
                 jo_token_manager_erc721.options.address, // call params
-                jo_token_manager_erc1155.options.address // call params
+                jo_token_manager_erc1155.options.address, // call params
+                jo_token_manager_erc721_with_metadata.options.address // call params
             ]
         );
         const dataTx = methodWithArguments.encodeABI(); // the encoded ABI of the method
diff --git a/proxy/contracts/MessageProxy.sol b/proxy/contracts/MessageProxy.sol
index 43d5abc62..6efde8494 100644
--- a/proxy/contracts/MessageProxy.sol
+++ b/proxy/contracts/MessageProxy.sol
@@ -52,6 +52,7 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
     bytes32 public constant EXTRA_CONTRACT_REGISTRAR_ROLE = keccak256("EXTRA_CONTRACT_REGISTRAR_ROLE");
     bytes32 public constant CONSTANT_SETTER_ROLE = keccak256("CONSTANT_SETTER_ROLE");
     uint256 public constant MESSAGES_LENGTH = 10;
+    uint256 public constant REVERT_REASON_LENGTH = 64;
 
     //   schainHash => ConnectedChainInfo
     mapping(bytes32 => ConnectedChainInfo) public connectedChains;
@@ -303,11 +304,11 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
     }
 
     /**
-     * @dev Allows LockAndData to remove connected chain from this contract.
+     * @dev Allows CHAIN_CONNECTOR_ROLE to remove connected chain from this contract.
      * 
      * Requirements:
      * 
-     * - `msg.sender` must be LockAndData contract.
+     * - `msg.sender` must be granted CHAIN_CONNECTOR_ROLE.
      * - `schainName` must be initialized.
      */
     function removeConnectedChain(string memory schainName) public virtual override onlyChainConnector {
@@ -316,7 +317,9 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
         delete connectedChains[schainHash];
     }    
 
-    // Registration state detection
+    /**
+     * @dev Checks whether chain is currently connected.
+     */
     function isConnectedChain(
         string memory schainName
     )
@@ -416,36 +419,41 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
         uint counter
     )
         internal
-        returns (address)
     {
         if (!message.destinationContract.isContract()) {
             emit PostMessageError(
                 counter,
                 "Destination contract is not a contract"
             );
-            return address(0);
+            return;
         }
         try IMessageReceiver(message.destinationContract).postMessage{gas: gasLimit}(
             schainHash,
             message.sender,
             message.data
-        ) returns (address receiver) {
-            return receiver;
+        ) {
+            return;
         } catch Error(string memory reason) {
             emit PostMessageError(
                 counter,
-                bytes(reason)
+                _getSlice(bytes(reason), REVERT_REASON_LENGTH)
+            );
+        } catch Panic(uint errorCode) {
+               emit PostMessageError(
+                counter,
+                abi.encodePacked(errorCode)
             );
-            return address(0);
         } catch (bytes memory revertData) {
             emit PostMessageError(
                 counter,
-                revertData
+                _getSlice(revertData, REVERT_REASON_LENGTH)
             );
-            return address(0);
         }
     }
 
+    /**
+     * @dev Returns receiver of message.
+     */
     function _getGasPayer(
         bytes32 schainHash,
         Message calldata message,
@@ -463,18 +471,27 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
         } catch Error(string memory reason) {
             emit PostMessageError(
                 counter,
-                bytes(reason)
+                _getSlice(bytes(reason), REVERT_REASON_LENGTH)
+            );
+            return address(0);
+        } catch Panic(uint errorCode) {
+               emit PostMessageError(
+                counter,
+                abi.encodePacked(errorCode)
             );
             return address(0);
         } catch (bytes memory revertData) {
             emit PostMessageError(
                 counter,
-                revertData
+                _getSlice(revertData, REVERT_REASON_LENGTH)
             );
             return address(0);
         }
     }
 
+    /**
+     * @dev Checks whether msg.sender is registered as custom extra contract.
+     */
     function _authorizeOutgoingMessageSender(bytes32 targetChainHash) internal view virtual {
         require(
             isContractRegistered(bytes32(0), msg.sender) || isContractRegistered(targetChainHash, msg.sender),
@@ -482,6 +499,9 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
         );        
     }
 
+    /**
+     * @dev Returns list of registered custom extra contracts.
+     */
     function _getRegistryContracts()
         internal
         view
@@ -516,4 +536,13 @@ abstract contract MessageProxy is AccessControlEnumerableUpgradeable, IMessagePr
         }
         return hash;
     }
+
+    function _getSlice(bytes memory text, uint end) private pure returns (bytes memory) {
+        uint slicedEnd = end < text.length ? end : text.length;
+        bytes memory sliced = new bytes(slicedEnd);
+        for(uint i = 0; i < slicedEnd; i++){
+            sliced[i] = text[i];
+        }
+        return sliced;    
+    }
 }
diff --git a/proxy/contracts/Messages.sol b/proxy/contracts/Messages.sol
index 24f69d71c..ca685ec73 100644
--- a/proxy/contracts/Messages.sol
+++ b/proxy/contracts/Messages.sol
@@ -45,7 +45,9 @@ library Messages {
         TRANSFER_ERC1155,
         TRANSFER_ERC1155_AND_TOKEN_INFO,
         TRANSFER_ERC1155_BATCH,
-        TRANSFER_ERC1155_BATCH_AND_TOKEN_INFO
+        TRANSFER_ERC1155_BATCH_AND_TOKEN_INFO,
+        TRANSFER_ERC721_WITH_METADATA,
+        TRANSFER_ERC721_WITH_METADATA_AND_TOKEN_INFO
     }
 
     /**
@@ -119,6 +121,14 @@ library Messages {
         uint256 tokenId;
     }
 
+    /**
+     * @dev Structure for describing base ERC721 with metadata.
+     */
+    struct TransferErc721MessageWithMetadata {
+        TransferErc721Message erc721message;
+        string tokenURI;
+    }
+
     /**
      * @dev Structure for describing ERC20 with token info.
      */
@@ -135,6 +145,14 @@ library Messages {
         Erc721TokenInfo tokenInfo;
     }
 
+    /**
+     * @dev Structure for describing additional data for ERC721 token with metadata.
+     */
+    struct TransferErc721WithMetadataAndTokenInfoMessage {
+        TransferErc721MessageWithMetadata baseErc721transferWithMetadata;
+        Erc721TokenInfo tokenInfo;
+    }
+
     /**
      * @dev Structure for describing whether interchain connection is allowed.
      */
@@ -388,6 +406,82 @@ library Messages {
         return abi.decode(data, (TransferErc721AndTokenInfoMessage));
     }
 
+    /**
+     * @dev Encodes message for transferring ERC721. 
+     * Returns encoded message.
+     */
+    function encodeTransferErc721MessageWithMetadata(
+        address token,
+        address receiver,
+        uint256 tokenId,
+        string memory tokenURI
+    ) internal pure returns (bytes memory) {
+        TransferErc721MessageWithMetadata memory message = TransferErc721MessageWithMetadata(
+            TransferErc721Message(
+                BaseMessage(MessageType.TRANSFER_ERC721_WITH_METADATA),
+                token,
+                receiver,
+                tokenId
+            ),
+            tokenURI
+        );
+        return abi.encode(message);
+    }
+
+    /**
+     * @dev Decodes message for transferring ERC721. 
+     * Returns structure `TransferErc721MessageWithMetadata`.
+     */
+    function decodeTransferErc721MessageWithMetadata(
+        bytes calldata data
+    ) internal pure returns (TransferErc721MessageWithMetadata memory) {
+        require(
+            getMessageType(data) == MessageType.TRANSFER_ERC721_WITH_METADATA,
+            "Message type is not ERC721 transfer"
+        );
+        return abi.decode(data, (TransferErc721MessageWithMetadata));
+    }
+
+    /**
+     * @dev Encodes message for transferring ERC721 with token info. 
+     * Returns encoded message.
+     */
+    function encodeTransferErc721WithMetadataAndTokenInfoMessage(
+        address token,
+        address receiver,
+        uint256 tokenId,
+        string memory tokenURI,
+        Erc721TokenInfo memory tokenInfo
+    ) internal pure returns (bytes memory) {
+        TransferErc721WithMetadataAndTokenInfoMessage memory message = TransferErc721WithMetadataAndTokenInfoMessage(
+            TransferErc721MessageWithMetadata(
+                TransferErc721Message(
+                    BaseMessage(MessageType.TRANSFER_ERC721_WITH_METADATA_AND_TOKEN_INFO),
+                    token,
+                    receiver,
+                    tokenId
+                ),
+                tokenURI
+            ),
+            tokenInfo
+        );
+        return abi.encode(message);
+    }
+
+    /**
+     * @dev Decodes message for transferring ERC721 with token info. 
+     * Returns structure `TransferErc721WithMetadataAndTokenInfoMessage`.
+     */
+    function decodeTransferErc721WithMetadataAndTokenInfoMessage(
+        bytes calldata data
+    ) internal pure returns (TransferErc721WithMetadataAndTokenInfoMessage memory) {
+        require(
+            getMessageType(data) == MessageType.TRANSFER_ERC721_WITH_METADATA_AND_TOKEN_INFO,
+            "Message type is not ERC721 transfer with token info"
+        );
+        return abi.decode(data, (TransferErc721WithMetadataAndTokenInfoMessage));
+    }
+
     /**
      * @dev Encodes message for activating user on schain. 
      * Returns encoded message.
diff --git a/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataMainnet.sol b/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataMainnet.sol
index df2f4647f..b3d3a25e7 100644
--- a/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataMainnet.sol
+++ b/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataMainnet.sol
@@ -67,7 +67,6 @@ contract ERC721ReferenceMintAndMetadataMainnet is MessageReceiver, IERC721Refere
         external
         override
         onlyMessageProxy
-        returns (address)
     {
         require(schainHash == keccak256(abi.encodePacked(schainName)), "Incorrect name of schain");
         require(sender == senderContractOnSchain, "Incorrect sender contract");
@@ -81,6 +80,5 @@ contract ERC721ReferenceMintAndMetadataMainnet is MessageReceiver, IERC721Refere
             "Token URI was not set"
         );
         ERC721OnChain(erc721ContractOnMainnet).transferFrom(address(this), to, tokenId);
-        return address(0);
     }
 }
diff --git a/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataSchain.sol b/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataSchain.sol
index f9abcfa08..ae1fd91b3 100644
--- a/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataSchain.sol
+++ b/proxy/contracts/extensions/ERC721ReferenceMintAndMetadataSchain.sol
@@ -30,8 +30,8 @@ import "./interfaces/MessageSender.sol";
 /**
  * @title Token Manager
  * @dev Runs on SKALE Chains, accepts messages from mainnet, and instructs
- * TokenFactory to create clones. TokenManager mints tokens via
- * LockAndDataForSchain*. When a user exits a SKALE chain, TokenFactory
+ * TokenFactory to create clones.
+  When a user exits a SKALE chain, TokenFactory
  * burns tokens.
  */
 contract ERC721ReferenceMintAndMetadataSchain is MessageSender, IERC721ReferenceMintAndMetadataSchain {
diff --git a/proxy/contracts/mainnet/CommunityPool.sol b/proxy/contracts/mainnet/CommunityPool.sol
index b82564ca4..153337635 100644
--- a/proxy/contracts/mainnet/CommunityPool.sol
+++ b/proxy/contracts/mainnet/CommunityPool.sol
@@ -122,7 +122,7 @@ contract CommunityPool is Twin, ICommunityPool {
         returns (bool)
     {
         if (gas > 0) {
-            IWallets(contractManagerOfSkaleManager.getContract("Wallets")).refundGasBySchain(
+            IWallets(payable(contractManagerOfSkaleManager.getContract("Wallets"))).refundGasBySchain(
                 schainHash,
                 node,
                 gas,
@@ -206,10 +206,15 @@ contract CommunityPool is Twin, ICommunityPool {
         return _userWallets[user][keccak256(abi.encodePacked(schainName))];
     }
 
+    /**
+     * @dev Checks whether user is active and wallet was recharged for sufficient amount.
+     */
     function checkUserBalance(bytes32 schainHash, address receiver) external view override returns (bool) {
         return activeUsers[receiver][schainHash] && _balanceIsSufficient(schainHash, receiver, 0);
     }
-
+    /**
+     * @dev Checks whether user wallet was recharged for sufficient amount.
+     */
     function _balanceIsSufficient(bytes32 schainHash, address receiver, uint256 delta) private view returns (bool) {
         return delta + _userWallets[receiver][schainHash] >= minTransactionGas * tx.gasprice;
     } 
diff --git a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC1155.sol b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC1155.sol
index e54c69943..8de91f055 100644
--- a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC1155.sol
+++ b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC1155.sol
@@ -190,7 +190,6 @@ contract DepositBoxERC1155 is DepositBox, ERC1155ReceiverUpgradeable, IDepositBo
         onlyMessageProxy
         whenNotKilled(schainHash)
         checkReceiverChain(schainHash, sender)
-        returns (address receiver)
     {
         Messages.MessageType operation = Messages.getMessageType(data);
         if (operation == Messages.MessageType.TRANSFER_ERC1155) {
@@ -209,7 +208,6 @@ contract DepositBoxERC1155 is DepositBox, ERC1155ReceiverUpgradeable, IDepositBo
                 message.amount,
                 ""
             );
-            receiver = message.receiver;
         } else if (operation == Messages.MessageType.TRANSFER_ERC1155_BATCH) {
             Messages.TransferErc1155BatchMessage memory message = Messages.decodeTransferErc1155BatchMessage(data);
             require(message.token.isContract(), "Given address is not a contract");
@@ -221,7 +219,6 @@ contract DepositBoxERC1155 is DepositBox, ERC1155ReceiverUpgradeable, IDepositBo
                 message.amounts,
                 ""
             );
-            receiver = message.receiver;
         }
     }
 
@@ -285,6 +282,13 @@ contract DepositBoxERC1155 is DepositBox, ERC1155ReceiverUpgradeable, IDepositBo
         );
     }
 
+    /**
+     * @dev Returns receiver of message.
+     *
+     * Requirements:
+     *
+     * - Sender contract should be defined and schain name cannot be `Mainnet`.
+     */
     function gasPayer(
         bytes32 schainHash,
         address sender,
@@ -307,6 +311,10 @@ contract DepositBoxERC1155 is DepositBox, ERC1155ReceiverUpgradeable, IDepositBo
         return address(0);
     }
 
+
+    /**
+     * @dev Returns selector of onERC1155Received.
+     */
     function onERC1155Received(
         address operator,
         address,
@@ -323,6 +331,10 @@ contract DepositBoxERC1155 is DepositBox, ERC1155ReceiverUpgradeable, IDepositBo
         return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
     }
 
+
+    /**
+     * @dev Returns selector of onERC1155BatchReceived.
+     */
     function onERC1155BatchReceived(
         address operator,
         address,
diff --git a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC20.sol b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC20.sol
index e74f60e89..69a67a6b0 100644
--- a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC20.sol
+++ b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC20.sol
@@ -150,7 +150,6 @@ contract DepositBoxERC20 is DepositBox, IDepositBoxERC20 {
         onlyMessageProxy
         whenNotKilled(schainHash)
         checkReceiverChain(schainHash, sender)
-        returns (address)
     {
         Messages.TransferErc20Message memory message = Messages.decodeTransferErc20Message(data);
         require(message.token.isContract(), "Given address is not a contract");
@@ -160,7 +159,6 @@ contract DepositBoxERC20 is DepositBox, IDepositBoxERC20 {
             ERC20Upgradeable(message.token).transfer(message.receiver, message.amount),
             "Transfer was failed"
         );
-        return message.receiver;
     }
 
     /**
@@ -208,6 +206,13 @@ contract DepositBoxERC20 is DepositBox, IDepositBoxERC20 {
         );
     }
 
+    /**
+     * @dev Returns receiver of message.
+     *
+     * Requirements:
+     *
+     * - Sender contract should be defined and schain name cannot be `Mainnet`.
+     */
     function gasPayer(
         bytes32 schainHash,
         address sender,
diff --git a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721.sol b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721.sol
index baf67e29a..9fea45a1e 100644
--- a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721.sol
+++ b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721.sol
@@ -136,18 +136,17 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
         bytes calldata data
     )
         external
+        virtual
         override
         onlyMessageProxy
         whenNotKilled(schainHash)
         checkReceiverChain(schainHash, sender)
-        returns (address)
     {
         Messages.TransferErc721Message memory message = Messages.decodeTransferErc721Message(data);
         require(message.token.isContract(), "Given address is not a contract");
         require(IERC721Upgradeable(message.token).ownerOf(message.tokenId) == address(this), "Incorrect tokenId");
         _removeTransferredAmount(message.token, message.tokenId);
         IERC721Upgradeable(message.token).transferFrom(address(this), message.receiver, message.tokenId);
-        return message.receiver;
     }
 
     /**
@@ -192,6 +191,13 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
         IERC721Upgradeable(erc721OnMainnet).transferFrom(address(this), receiver, tokenId);
     }
 
+    /**
+     * @dev Returns receiver of message.
+     *
+     * Requirements:
+     *
+     * - Sender contract should be defined and schain name cannot be `Mainnet`.
+     */
     function gasPayer(
         bytes32 schainHash,
         address sender,
@@ -199,6 +205,7 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
     )
         external
         view
+        virtual
         override
         checkReceiverChain(schainHash, sender)
         returns (address)
@@ -207,22 +214,6 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
         return message.receiver;
     }
 
-    /**
-     * @dev Should return true if token was added by Schain owner or 
-     * automatically added after sending to schain if whitelist was turned off.
-     */
-    function getSchainToERC721(
-        string calldata schainName,
-        address erc721OnMainnet
-    )
-        external
-        view
-        override
-        returns (bool)
-    {
-        return _schainToERC721[keccak256(abi.encodePacked(schainName))].contains(erc721OnMainnet);
-    }
-
     /**
      * @dev Should return length of a set of all mapped tokens which were added by Schain owner 
      * or added automatically after sending to schain if whitelist was turned off.
@@ -271,16 +262,25 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
     }
 
     /**
-     * @dev Saves the ids of tokens that was transferred to schain.
+     * @dev Should return true if token was added by Schain owner or 
+     * automatically added after sending to schain if whitelist was turned off.
      */
-    function _saveTransferredAmount(bytes32 schainHash, address erc721Token, uint256 tokenId) private {
-        transferredAmount[erc721Token][tokenId] = schainHash;
+    function getSchainToERC721(
+        string calldata schainName,
+        address erc721OnMainnet
+    )
+        public
+        view
+        override
+        returns (bool)
+    {
+        return _schainToERC721[keccak256(abi.encodePacked(schainName))].contains(erc721OnMainnet);
     }
 
     /**
      * @dev Removes the ids of tokens that was transferred from schain.
      */
-    function _removeTransferredAmount(address erc721Token, uint256 tokenId) private {
+    function _removeTransferredAmount(address erc721Token, uint256 tokenId) internal {
         transferredAmount[erc721Token][tokenId] = bytes32(0);
     }
 
@@ -299,7 +299,8 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
         address to,
         uint256 tokenId
     )
-        private
+        internal
+        virtual
         returns (bytes memory data)
     {
         bytes32 schainHash = keccak256(abi.encodePacked(schainName));
@@ -328,7 +329,7 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
      * 
      * - Given address should be contract.
      */
-    function _addERC721ForSchain(string calldata schainName, address erc721OnMainnet) private {
+    function _addERC721ForSchain(string calldata schainName, address erc721OnMainnet) internal {
         bytes32 schainHash = keccak256(abi.encodePacked(schainName));
         require(erc721OnMainnet.isContract(), "Given address is not a contract");
         require(!_schainToERC721[schainHash].contains(erc721OnMainnet), "ERC721 Token was already added");
@@ -339,10 +340,17 @@ contract DepositBoxERC721 is DepositBox, IDepositBoxERC721 {
     /**
      * @dev Returns info about ERC721 token such as token name, symbol.
      */
-    function _getTokenInfo(IERC721MetadataUpgradeable erc721) private view returns (Messages.Erc721TokenInfo memory) {
+    function _getTokenInfo(IERC721MetadataUpgradeable erc721) internal view returns (Messages.Erc721TokenInfo memory) {
         return Messages.Erc721TokenInfo({
             name: erc721.name(),
             symbol: erc721.symbol()
         });
     }
+
+    /**
+     * @dev Saves the ids of tokens that was transferred to schain.
+     */
+    function _saveTransferredAmount(bytes32 schainHash, address erc721Token, uint256 tokenId) private {
+        transferredAmount[erc721Token][tokenId] = schainHash;
+    }
 }
diff --git a/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721WithMetadata.sol b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721WithMetadata.sol
new file mode 100644
index 000000000..926410655
--- /dev/null
+++ b/proxy/contracts/mainnet/DepositBoxes/DepositBoxERC721WithMetadata.sol
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ *   DepositBoxERC721WithMetadata.sol - SKALE Interchain Messaging Agent
+ *   Copyright (C) 2021-Present SKALE Labs
+ *   @author Artem Payvin
+ *
+ *   SKALE IMA is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Affero General Public License as published
+ *   by the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SKALE IMA is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Affero General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public License
+ *   along with SKALE IMA.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+pragma solidity 0.8.6;
+
+import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
+
+import "./DepositBoxERC721.sol";
+import "../../Messages.sol";
+
+/**
+ * @title DepositBoxERC721WithMetadata
+ * @dev Runs on mainnet,
+ * accepts messages from schain,
+ * stores deposits of ERC721.
+ */
+contract DepositBoxERC721WithMetadata is DepositBoxERC721 {
+    using AddressUpgradeable for address;
+
+    /**
+     * @dev Allows MessageProxyForMainnet contract to execute transferring ERC721 token from schain to mainnet.
+     * 
+     * Requirements:
+     * 
+     * - Schain from which the tokens came should not be killed.
+     * - Sender contract should be defined and schain name cannot be `Mainnet`.
+     * - DepositBoxERC721 contract should own token.
+     */
+    function postMessage(
+        bytes32 schainHash,
+        address sender,
+        bytes calldata data
+    )
+        external
+        override
+        onlyMessageProxy
+        whenNotKilled(schainHash)
+        checkReceiverChain(schainHash, sender)
+    {
+        Messages.TransferErc721MessageWithMetadata memory message =
+            Messages.decodeTransferErc721MessageWithMetadata(data);
+        require(message.erc721message.token.isContract(), "Given address is not a contract");
+        require(
+            IERC721Upgradeable(message.erc721message.token).ownerOf(message.erc721message.tokenId) == address(this),
+            "Incorrect tokenId"
+        );
+        _removeTransferredAmount(message.erc721message.token, message.erc721message.tokenId);
+        IERC721Upgradeable(message.erc721message.token).transferFrom(
+            address(this),
+            message.erc721message.receiver,
+            message.erc721message.tokenId
+        );
+    }
+
+    /**
+     * @dev Returns receiver of message.
+     *
+     * Requirements:
+     *
+     * - Sender contract should be defined and schain name cannot be `Mainnet`.
+     */
+    function gasPayer(
+        bytes32 schainHash,
+        address sender,
+        bytes calldata data
+    )
+        external
+        view
+        override
+        checkReceiverChain(schainHash, sender)
+        returns (address)
+    {
+        Messages.TransferErc721MessageWithMetadata memory message =
+            Messages.decodeTransferErc721MessageWithMetadata(data);
+        return message.erc721message.receiver;
+    }
+
+    /**
+     * @dev Allows DepositBoxERC721 to receive ERC721 tokens.
+     * 
+     * Emits an {ERC721TokenReady} event.
+     * 
+     * Requirements:
+     * 
+     * - Whitelist should be turned off for auto adding tokens to DepositBoxERC721.
+     */
+    function _receiveERC721(
+        string calldata schainName,
+        address erc721OnMainnet,
+        address to,
+        uint256 tokenId
+    )
+        internal
+        override
+        returns (bytes memory data)
+    {
+        bool isERC721AddedToSchain = getSchainToERC721(schainName, erc721OnMainnet);
+        if (!isERC721AddedToSchain) {
+            require(!isWhitelisted(schainName), "Whitelist is enabled");
+            _addERC721ForSchain(schainName, erc721OnMainnet);
+            data = Messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnMainnet,
+                to,
+                tokenId,
+                _getTokenURI(IERC721MetadataUpgradeable(erc721OnMainnet), tokenId),
+                _getTokenInfo(IERC721MetadataUpgradeable(erc721OnMainnet))
+            );
+        } else {
+            data = Messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnMainnet,
+                to,
+                tokenId,
+                _getTokenURI(IERC721MetadataUpgradeable(erc721OnMainnet), tokenId)
+            );
+        }
+        emit ERC721TokenReady(erc721OnMainnet, tokenId);
+    }
+
+    /**
+     * @dev Returns tokenURI of ERC721 token.
+     */
+    function _getTokenURI(IERC721MetadataUpgradeable erc721, uint256 tokenId) private view returns (string memory) {
+        return erc721.tokenURI(tokenId);
+    }
+
+}
\ No newline at end of file
diff --git a/proxy/contracts/mainnet/DepositBoxes/DepositBoxEth.sol b/proxy/contracts/mainnet/DepositBoxes/DepositBoxEth.sol
index ed6f4b047..8968c1933 100644
--- a/proxy/contracts/mainnet/DepositBoxes/DepositBoxEth.sol
+++ b/proxy/contracts/mainnet/DepositBoxes/DepositBoxEth.sol
@@ -94,7 +94,6 @@ contract DepositBoxEth is DepositBox, IDepositBoxEth {
         onlyMessageProxy
         whenNotKilled(schainHash)
         checkReceiverChain(schainHash, sender)
-        returns (address)
     {
         Messages.TransferEthMessage memory message = Messages.decodeTransferEthMessage(data);
         require(
@@ -107,7 +106,6 @@ contract DepositBoxEth is DepositBox, IDepositBoxEth {
         } else {
             payable(message.receiver).sendValue(message.amount);
         }
-        return message.receiver;
     }
 
     /**
diff --git a/proxy/contracts/mainnet/MessageProxyForMainnet.sol b/proxy/contracts/mainnet/MessageProxyForMainnet.sol
index b001e5eac..e3cf29187 100644
--- a/proxy/contracts/mainnet/MessageProxyForMainnet.sol
+++ b/proxy/contracts/mainnet/MessageProxyForMainnet.sol
@@ -26,6 +26,8 @@ import "@skalenetwork/skale-manager-interfaces/IWallets.sol";
 import "@skalenetwork/skale-manager-interfaces/ISchains.sol";
 import "@skalenetwork/ima-interfaces/mainnet/IMessageProxyForMainnet.sol";
 import "@skalenetwork/ima-interfaces/mainnet/ICommunityPool.sol";
+import "@skalenetwork/skale-manager-interfaces/ISchainsInternal.sol";
+
 
 import "../MessageProxy.sol";
 import "./SkaleManagerClient.sol";
@@ -92,6 +94,9 @@ contract MessageProxyForMainnet is SkaleManagerClient, MessageProxy, IMessagePro
         uint256 newValue
     );
 
+    /**
+     * @dev Reentrancy guard for postIncomingMessages.
+     */
     modifier messageInProgressLocker() {
         require(!messageInProgress, "Message is in progress");
         messageInProgress = true;
@@ -132,7 +137,9 @@ contract MessageProxyForMainnet is SkaleManagerClient, MessageProxy, IMessagePro
      */
     function addConnectedChain(string calldata schainName) external override {
         bytes32 schainHash = keccak256(abi.encodePacked(schainName));
-        require(schainHash != MAINNET_HASH, "SKALE chain name is incorrect");
+        require(ISchainsInternal(
+            contractManagerOfSkaleManager.getContract("SchainsInternal")
+        ).isSchainExist(schainHash), "SKALE chain must exist");
         _addConnectedChain(schainHash);
     }
 
@@ -225,6 +232,7 @@ contract MessageProxyForMainnet is SkaleManagerClient, MessageProxy, IMessagePro
         uint additionalGasPerMessage = 
             (gasTotal - gasleft() + headerMessageGasCost + messages.length * messageGasCost) / messages.length;
         uint notReimbursedGas = 0;
+        connectedChains[fromSchainHash].incomingMessageCounter += messages.length;
         for (uint256 i = 0; i < messages.length; i++) {
             gasTotal = gasleft();
             if (isContractRegistered(bytes32(0), messages[i].destinationContract)) {
@@ -241,7 +249,6 @@ contract MessageProxyForMainnet is SkaleManagerClient, MessageProxy, IMessagePro
                 notReimbursedGas += gasTotal - gasleft() + additionalGasPerMessage;
             }
         }
-        connectedChains[fromSchainHash].incomingMessageCounter += messages.length;
         communityPool.refundGasBySchainWallet(fromSchainHash, payable(msg.sender), notReimbursedGas);
     }
 
@@ -288,7 +295,7 @@ contract MessageProxyForMainnet is SkaleManagerClient, MessageProxy, IMessagePro
     function initialize(IContractManager contractManagerOfSkaleManagerValue) public virtual override initializer {
         SkaleManagerClient.initialize(contractManagerOfSkaleManagerValue);
         MessageProxy.initializeMessageProxy(1e6);
-        headerMessageGasCost = 70000;
+        headerMessageGasCost = 73800;
         messageGasCost = 9000;
     }
 
@@ -351,12 +358,19 @@ contract MessageProxyForMainnet is SkaleManagerClient, MessageProxy, IMessagePro
         );
     }
 
+    /**
+     * @dev Checks whether balance of schain wallet is sufficient for 
+     * for reimbursement custom message.
+     */
     function _checkSchainBalance(bytes32 schainHash) internal view returns (bool) {
         return IWallets(
-            contractManagerOfSkaleManager.getContract("Wallets")
+            payable(contractManagerOfSkaleManager.getContract("Wallets"))
         ).getSchainBalance(schainHash) >= (MESSAGES_LENGTH + 1) * gasLimit * tx.gasprice;
     }
 
+    /**
+     * @dev Returns list of registered custom extra contracts.
+     */
     function _getRegistryContracts()
         internal
         view
diff --git a/proxy/contracts/schain/CommunityLocker.sol b/proxy/contracts/schain/CommunityLocker.sol
index 884244e4c..58ed57644 100644
--- a/proxy/contracts/schain/CommunityLocker.sol
+++ b/proxy/contracts/schain/CommunityLocker.sol
@@ -137,7 +137,6 @@ contract CommunityLocker is ICommunityLocker, AccessControlEnumerableUpgradeable
     )
         external
         override
-        returns (address)
     {
         require(msg.sender == address(messageProxy), "Sender is not a message proxy");
         require(sender == communityPool, "Sender must be CommunityPool");
@@ -152,7 +151,6 @@ contract CommunityLocker is ICommunityLocker, AccessControlEnumerableUpgradeable
         } else {
             emit LockUser(schainHash, message.receiver);
         }
-        return message.receiver;
     }
 
     /**
diff --git a/proxy/contracts/schain/MessageProxyForSchain.sol b/proxy/contracts/schain/MessageProxyForSchain.sol
index 955e9cf60..bc34bf823 100644
--- a/proxy/contracts/schain/MessageProxyForSchain.sol
+++ b/proxy/contracts/schain/MessageProxyForSchain.sol
@@ -101,6 +101,17 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
     mapping(bytes32 => EnumerableSetUpgradeable.AddressSet) private _registryContracts;
 
     string public version;
+    bool public override messageInProgress;
+
+    /**
+     * @dev Reentrancy guard for postIncomingMessages.
+     */
+    modifier messageInProgressLocker() {
+        require(!messageInProgress, "Message is in progress");
+        messageInProgress = true;
+        _;
+        messageInProgress = false;
+    }
 
     /**
      * @dev Allows DEFAULT_ADMIN_ROLE to initialize registered contracts
@@ -126,6 +137,14 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
         }
     }
 
+    /**
+     * @dev Allows MessageProxy to register extra contract for being able to transfer messages from custom contracts.
+     * 
+     * Requirements:
+     * 
+     * - Function caller has to be granted with {EXTRA_CONTRACT_REGISTRAR_ROLE}.
+     * - Destination chain hash cannot be equal to itself
+     */
     function registerExtraContract(
         string memory chainName,
         address extraContract
@@ -135,10 +154,19 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
         onlyExtraContractRegistrar
     {
         bytes32 chainHash = keccak256(abi.encodePacked(chainName));
-        require(chainHash != schainHash, "Schain hash can not be equal Mainnet");
+        require(chainHash != schainHash, "Destination chain hash cannot be equal to itself");
         _registerExtraContract(chainHash, extraContract);
     }
 
+    /**
+     * @dev Allows MessageProxy to remove extra contract,
+     * thus `extraContract` will no longer be available to transfer messages from chain to chain.
+     * 
+     * Requirements:
+     * 
+     * - Function caller has to be granted with {EXTRA_CONTRACT_REGISTRAR_ROLE}.
+     * - Destination chain hash cannot be equal to itself
+     */
     function removeExtraContract(
         string memory chainName,
         address extraContract
@@ -148,7 +176,7 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
         onlyExtraContractRegistrar
     {
         bytes32 chainHash = keccak256(abi.encodePacked(chainName));
-        require(chainHash != schainHash, "Schain hash can not be equal Mainnet");
+        require(chainHash != schainHash, "Destination chain hash cannot be equal to itself");
         _removeExtraContract(chainHash, extraContract);
     }
 
@@ -188,6 +216,7 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
     )
         external
         override(IMessageProxy, MessageProxy)
+        messageInProgressLocker
     {
         bytes32 fromChainHash = keccak256(abi.encodePacked(fromChainName));
         require(connectedChains[fromChainHash].inited, "Chain is not initialized");
@@ -198,10 +227,10 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
         require(
             startingCounter == connectedChains[fromChainHash].incomingMessageCounter,
             "Starting counter is not qual to incoming message counter");
+        connectedChains[fromChainHash].incomingMessageCounter += messages.length;
         for (uint256 i = 0; i < messages.length; i++) {
             _callReceiverContract(fromChainHash, messages[i], startingCounter + 1);
         }
-        connectedChains[fromChainHash].incomingMessageCounter += messages.length;
         _topUpBalance();
     }
 
@@ -351,6 +380,9 @@ contract MessageProxyForSchain is MessageProxy, IMessageProxyForSchainInitialize
         );
     }
 
+    /**
+     * @dev Returns list of registered custom extra contracts.
+     */
     function _getRegistryContracts()
         internal
         view
diff --git a/proxy/contracts/schain/TokenManager.sol b/proxy/contracts/schain/TokenManager.sol
index 562562c94..0a5a76c82 100644
--- a/proxy/contracts/schain/TokenManager.sol
+++ b/proxy/contracts/schain/TokenManager.sol
@@ -264,6 +264,9 @@ abstract contract TokenManager is AccessControlEnumerableUpgradeable, ITokenMana
         emit DepositBoxWasChanged(address(0), newDepositBox);
     }
 
+    /**
+     * @dev Checks whether sender contract is DepositBox or TokenManager depending on chainHash.
+     */
     function _checkSender(bytes32 fromChainHash, address sender) internal view virtual returns (bool) {
         return fromChainHash == MAINNET_HASH ? sender == depositBox : sender == tokenManagers[fromChainHash];
     }
diff --git a/proxy/contracts/schain/TokenManagerLinker.sol b/proxy/contracts/schain/TokenManagerLinker.sol
index d23227674..563d99c03 100644
--- a/proxy/contracts/schain/TokenManagerLinker.sol
+++ b/proxy/contracts/schain/TokenManagerLinker.sol
@@ -102,6 +102,9 @@ contract TokenManagerLinker is ITokenManagerLinker, AccessControlEnumerableUpgra
         delete _interchainConnections;
     }  
 
+    /**
+     * @dev Adds new TokenManager.
+     */
     function registerTokenManager(ITokenManager newTokenManager) external override onlyRegistrar {
         tokenManagers.push(newTokenManager);
     }
diff --git a/proxy/contracts/schain/TokenManagers/TokenManagerERC1155.sol b/proxy/contracts/schain/TokenManagers/TokenManagerERC1155.sol
index 7c21f9c1a..394812100 100644
--- a/proxy/contracts/schain/TokenManagers/TokenManagerERC1155.sol
+++ b/proxy/contracts/schain/TokenManagers/TokenManagerERC1155.sol
@@ -31,6 +31,13 @@ import "../TokenManager.sol";
 import "../../thirdparty/ERC1155ReceiverUpgradeableWithoutGap.sol";
 
 
+interface ITokenManagerERC1155InitializeFunction is ITokenManagerERC1155 {
+    function initializeAllClonesERC1155(
+        address[] calldata contracts
+    ) external;
+}
+
+
 /**
  * @title TokenManagerERC1155
  * @dev Runs on SKALE Chains,
@@ -38,7 +45,11 @@ import "../../thirdparty/ERC1155ReceiverUpgradeableWithoutGap.sol";
  * and creates ERC1155 clones.
  * TokenManagerERC1155 mints tokens. When a user exits a SKALE chain, it burns them.
  */
-contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutGap, ITokenManagerERC1155 {
+contract TokenManagerERC1155 is
+    TokenManager,
+    ERC1155ReceiverUpgradeableWithoutGap,
+    ITokenManagerERC1155InitializeFunction
+{
     using AddressUpgradeable for address;
     using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
 
@@ -94,6 +105,27 @@ contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutG
         uint256[] amounts
     );
 
+    /**
+     * @dev Allows DEFAULT_ADMIN_ROLE to initialize clones ERC1155
+     * Notice - this function will be executed only once during upgrade
+     * 
+     * Requirements:
+     * 
+     * `msg.sender` should have DEFAULT_ADMIN_ROLE
+     */
+    function initializeAllClonesERC1155(address[] calldata contracts) external override {
+        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Sender is not authorized");
+        for (uint256 i = 0; i < contracts.length; i++) {
+            if (
+                address(deprecatedClonesErc1155[contracts[i]]).isContract() &&
+                !address(clonesErc1155[MAINNET_HASH][contracts[i]]).isContract()
+            ) {
+                clonesErc1155[MAINNET_HASH][contracts[i]] = deprecatedClonesErc1155[contracts[i]];
+                delete deprecatedClonesErc1155[contracts[i]];
+            }
+        }
+    }
+
     /**
      * @dev Move tokens from schain to mainnet.
      * 
@@ -175,7 +207,7 @@ contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutG
      * Requirements:
      * 
      * - MessageProxy must be the sender.
-     * - `fromSchainName` must exist in TokenManagerERC1155 addresses.
+     * - `fromChainHash` must exist in TokenManagerERC1155 addresses.
      */
     function postMessage(
         bytes32 fromChainHash,
@@ -186,7 +218,6 @@ contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutG
         override
         onlyMessageProxy
         checkReceiverChain(fromChainHash, sender)
-        returns (address)
     {
         Messages.MessageType operation = Messages.getMessageType(data);
         address receiver = address(0);
@@ -203,7 +234,6 @@ contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutG
         } else {
             revert("MessageType is unknown");
         }
-        return receiver;
     }
 
     /**
@@ -420,15 +450,17 @@ contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutG
         private
     {
         bool isMainChainToken;
-        ERC1155BurnableUpgradeable contractOnSchain = clonesErc1155[chainHash][contractOnMainChain];
+        ERC1155OnChain contractOnSchain = clonesErc1155[chainHash][contractOnMainChain];
         if (address(contractOnSchain) == address(0)) {
-            contractOnSchain = ERC1155BurnableUpgradeable(contractOnMainChain);
+            contractOnSchain = ERC1155OnChain(contractOnMainChain);
+            require(!addedClones[contractOnSchain], "Incorrect main chain token");
             isMainChainToken = true;
         }
         require(address(contractOnSchain).isContract(), "No token clone on schain");
         require(contractOnSchain.isApprovedForAll(msg.sender, address(this)), "Not allowed ERC1155 Token");
         bytes memory data = Messages.encodeTransferErc1155Message(contractOnMainChain, to, id, amount);
         if (isMainChainToken) {
+            require(chainHash != MAINNET_HASH, "Main chain token could not be transfered to Mainnet");
             data = _receiveERC1155(
                 chainHash,
                 address(contractOnSchain),
@@ -463,15 +495,17 @@ contract TokenManagerERC1155 is TokenManager, ERC1155ReceiverUpgradeableWithoutG
         private
     {
         bool isMainChainToken;
-        ERC1155BurnableUpgradeable contractOnSchain = clonesErc1155[chainHash][contractOnMainChain];
+        ERC1155OnChain contractOnSchain = clonesErc1155[chainHash][contractOnMainChain];
         if (address(contractOnSchain) == address(0)) {
-            contractOnSchain = ERC1155BurnableUpgradeable(contractOnMainChain);
+            contractOnSchain = ERC1155OnChain(contractOnMainChain);
+            require(!addedClones[contractOnSchain], "Incorrect main chain token");
             isMainChainToken = true;
         }
         require(address(contractOnSchain).isContract(), "No token clone on schain");
         require(contractOnSchain.isApprovedForAll(msg.sender, address(this)), "Not allowed ERC1155 Token");
         bytes memory data = Messages.encodeTransferErc1155BatchMessage(contractOnMainChain, to, ids, amounts);
         if (isMainChainToken) {
+            require(chainHash != MAINNET_HASH, "Main chain token could not be transfered to Mainnet");
             data = _receiveERC1155Batch(
                 chainHash,
                 address(contractOnSchain),
diff --git a/proxy/contracts/schain/TokenManagers/TokenManagerERC20.sol b/proxy/contracts/schain/TokenManagers/TokenManagerERC20.sol
index 247b69d32..fa9a4463f 100644
--- a/proxy/contracts/schain/TokenManagers/TokenManagerERC20.sol
+++ b/proxy/contracts/schain/TokenManagers/TokenManagerERC20.sol
@@ -30,6 +30,12 @@ import "../../Messages.sol";
 import "../tokens/ERC20OnChain.sol";
 import "../TokenManager.sol";
 
+interface ITokenManagerERC20InitializeFunction is ITokenManagerERC20 {
+    function initializeAllClonesERC20(
+        address[] calldata contracts
+    ) external;
+}
+
 
 /**
  * @title TokenManagerERC20
@@ -38,7 +44,7 @@ import "../TokenManager.sol";
  * and creates ERC20 clones.
  * TokenManagerERC20 mints tokens. When a user exits a SKALE chain, it burns them.
  */
-contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
+contract TokenManagerERC20 is TokenManager, ITokenManagerERC20InitializeFunction {
     using AddressUpgradeable for address;
     using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
 
@@ -87,10 +93,31 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
      */
     event ERC20TokenReady(bytes32 indexed chainHash, address indexed contractOnMainnet, uint256 amount);
 
+    /**
+     * @dev Allows DEFAULT_ADMIN_ROLE to initialize clones ERC20
+     * Notice - this function will be executed only once during upgrade
+     * 
+     * Requirements:
+     * 
+     * `msg.sender` should have DEFAULT_ADMIN_ROLE
+     */
+    function initializeAllClonesERC20(address[] calldata contracts) external override {
+        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Sender is not authorized");
+        for (uint256 i = 0; i < contracts.length; i++) {
+            if (
+                address(deprecatedClonesErc20[contracts[i]]).isContract() &&
+                !address(clonesErc20[MAINNET_HASH][contracts[i]]).isContract()
+            ) {
+                clonesErc20[MAINNET_HASH][contracts[i]] = deprecatedClonesErc20[contracts[i]];
+                delete deprecatedClonesErc20[contracts[i]];
+            }
+        }
+    }
+
     /**
      * @dev Move tokens from schain to mainnet.
      * 
-     * {contractOnMainnet} tokens are burned on schain and unlocked on mainnet for {to} address.
+     * {contractOnMainnet} tokens are burned on schain and unlocked on mainnet for {msg.sender} address.
      */
     function exitToMainERC20(
         address contractOnMainnet,
@@ -107,7 +134,7 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
      * @dev Move tokens from schain to schain.
      * 
      * {contractOnMainnet} tokens are burned on origin schain
-     * and are minted on {targetSchainName} schain for {to} address.
+     * and are minted on {targetSchainName} schain for {msg.sender} address.
      */
     function transferToSchainERC20(
         string calldata targetSchainName,
@@ -129,7 +156,7 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
      * Requirements:
      * 
      * - MessageProxy must be the sender.
-     * - `fromSchainName` must exist in TokenManager addresses.
+     * - `fromChainHash` must exist in TokenManager addresses.
      */
     function postMessage(
         bytes32 fromChainHash,
@@ -140,7 +167,6 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
         override
         onlyMessageProxy
         checkReceiverChain(fromChainHash, sender)
-        returns (address)
     {
         Messages.MessageType operation = Messages.getMessageType(data);
         address receiver = address(0);
@@ -153,11 +179,10 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
         } else {
             revert("MessageType is unknown");
         }
-        return receiver;
     }
 
     /**
-     * @dev Allows Schain owner to register an ERC20 token clone in the token manager.
+     * @dev Allows Schain owner to register an ERC20 token clone in the TokenManager.
      */
     function addERC20TokenByOwner(
         string calldata targetChainName,
@@ -271,9 +296,10 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
         private
     {
         bool isMainChainToken;
-        ERC20BurnableUpgradeable contractOnSchain = clonesErc20[chainHash][contractOnMainChain];
+        ERC20OnChain contractOnSchain = clonesErc20[chainHash][contractOnMainChain];
         if (address(contractOnSchain) == address(0)) {
-            contractOnSchain = ERC20BurnableUpgradeable(contractOnMainChain);
+            contractOnSchain = ERC20OnChain(contractOnMainChain);
+            require(!addedClones[contractOnSchain], "Incorrect main chain token");
             isMainChainToken = true;
         }
         require(address(contractOnSchain).isContract(), "No token clone on schain");
@@ -287,6 +313,7 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
         );
         bytes memory data = Messages.encodeTransferErc20Message(address(contractOnMainChain), to, amount);
         if (isMainChainToken) {
+            require(chainHash != MAINNET_HASH, "Main chain token could not be transfered to Mainnet");
             data = _receiveERC20(
                 chainHash,
                 address(contractOnSchain),
@@ -403,6 +430,10 @@ contract TokenManagerERC20 is TokenManager, ITokenManagerERC20 {
         });
     }
 
+
+    /**
+     * @dev Decodes ERC20 transfer message depending on type of message.
+     */
     function _decodeErc20Message(bytes calldata data)
         private
         pure
diff --git a/proxy/contracts/schain/TokenManagers/TokenManagerERC721.sol b/proxy/contracts/schain/TokenManagers/TokenManagerERC721.sol
index de3b00151..b19404ca6 100644
--- a/proxy/contracts/schain/TokenManagers/TokenManagerERC721.sol
+++ b/proxy/contracts/schain/TokenManagers/TokenManagerERC721.sol
@@ -29,6 +29,13 @@ import "../tokens/ERC721OnChain.sol";
 import "../TokenManager.sol";
 
 
+interface ITokenManagerERC721InitializeFunction is ITokenManagerERC721 {
+    function initializeAllClonesERC721(
+        address[] calldata contracts
+    ) external;
+}
+
+
 /**
  * @title TokenManagerERC721
  * @dev Runs on SKALE Chains,
@@ -36,7 +43,7 @@ import "../TokenManager.sol";
  * and creates ERC721 clones.
  * TokenManagerERC721 mints tokens. When a user exits a SKALE chain, it burns them.
  */
-contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
+contract TokenManagerERC721 is TokenManager, ITokenManagerERC721InitializeFunction {
     using AddressUpgradeable for address;
     using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
 
@@ -86,10 +93,31 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
      */
     event ERC721TokenReady(bytes32 indexed chainHash, address indexed contractOnMainnet, uint256 tokenId);
 
+    /**
+     * @dev Allows DEFAULT_ADMIN_ROLE to initialize clones ERC721
+     * Notice - this function will be executed only once during upgrade
+     * 
+     * Requirements:
+     * 
+     * `msg.sender` should have DEFAULT_ADMIN_ROLE
+     */
+    function initializeAllClonesERC721(address[] calldata contracts) external override {
+        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Sender is not authorized");
+        for (uint256 i = 0; i < contracts.length; i++) {
+            if (
+                address(deprecatedClonesErc721[contracts[i]]).isContract() &&
+                !address(clonesErc721[MAINNET_HASH][contracts[i]]).isContract()
+            ) {
+                clonesErc721[MAINNET_HASH][contracts[i]] = deprecatedClonesErc721[contracts[i]];
+                delete deprecatedClonesErc721[contracts[i]];
+            }
+        }
+    }
+
     /**
      * @dev Move tokens from schain to mainnet.
      * 
-     * {contractOnMainnet} tokens are burned on schain and unlocked on mainnet for {to} address.
+     * {contractOnMainnet} tokens are burned on schain and unlocked on mainnet for {msg.sender} address.
      */
     function exitToMainERC721(
         address contractOnMainnet,
@@ -106,7 +134,7 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
      * @dev Move tokens from schain to schain.
      * 
      * {contractOnMainnet} tokens are burned on origin schain
-     * and are minted on {targetSchainName} schain for {to} address.
+     * and are minted on {targetSchainName} schain for {msg.sender} address.
      */
     function transferToSchainERC721(
         string calldata targetSchainName,
@@ -128,7 +156,7 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
      * Requirements:
      * 
      * - MessageProxy must be the sender.
-     * - `fromSchainName` must exist in TokenManager addresses.
+     * - `fromChainHash` must exist in TokenManager addresses.
      */
     function postMessage(
         bytes32 fromChainHash,
@@ -136,10 +164,10 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
         bytes calldata data
     )
         external
+        virtual
         override
         onlyMessageProxy
         checkReceiverChain(fromChainHash, sender)
-        returns (address)
     {
         Messages.MessageType operation = Messages.getMessageType(data);
         address receiver = address(0);
@@ -151,7 +179,6 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
         } else {
             revert("MessageType is unknown");
         }
-        return receiver;
     }
 
     /**
@@ -206,7 +233,7 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
      * Emits a {ERC20TokenCreated} event if token did not exist and was automatically deployed.
      * Emits a {ERC20TokenReceived} event on success.
      */
-    function _sendERC721(bytes32 fromChainHash, bytes calldata data) private returns (address) {
+    function _sendERC721(bytes32 fromChainHash, bytes calldata data) internal virtual returns (address) {
         Messages.MessageType messageType = Messages.getMessageType(data);
         address receiver;
         address token;
@@ -249,55 +276,10 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
         return receiver;
     }
 
-    /**
-     * @dev Burn tokens on schain and send message to unlock them on target chain.
-     */
-    function _exit(
-        bytes32 chainHash,
-        address messageReceiver,
-        address contractOnMainChain,
-        address to,
-        uint256 tokenId
-    )
-        private
-    {
-        bool isMainChainToken;
-        ERC721BurnableUpgradeable contractOnSchain = clonesErc721[chainHash][contractOnMainChain];
-        if (address(contractOnSchain) == address(0)) {
-            contractOnSchain = ERC721BurnableUpgradeable(contractOnMainChain);
-            isMainChainToken = true;
-        }
-        require(address(contractOnSchain).isContract(), "No token clone on schain");
-        require(contractOnSchain.getApproved(tokenId) == address(this), "Not allowed ERC721 Token");
-        bytes memory data = Messages.encodeTransferErc721Message(contractOnMainChain, to, tokenId);
-        if (isMainChainToken) {
-            data = _receiveERC721(
-                chainHash,
-                address(contractOnSchain),
-                msg.sender,
-                tokenId
-            );
-            _saveTransferredAmount(chainHash, address(contractOnSchain), tokenId);
-            contractOnSchain.transferFrom(msg.sender, address(this), tokenId);
-        } else {
-            contractOnSchain.transferFrom(msg.sender, address(this), tokenId);
-            contractOnSchain.burn(tokenId);
-        }
-        messageProxy.postOutgoingMessage(chainHash, messageReceiver, data);
-    }
-
-    /**
-     * @dev Saves the ids of tokens that was transferred to schain.
-     */
-    function _saveTransferredAmount(bytes32 chainHash, address erc721Token, uint256 tokenId) private {
-        require(transferredAmount[erc721Token][tokenId] == bytes32(0), "Token was already transferred to chain");
-        transferredAmount[erc721Token][tokenId] = chainHash;
-    }
-
     /**
      * @dev Removes the ids of tokens that was transferred from schain.
      */
-    function _removeTransferredAmount(bytes32 chainHash, address erc721Token, uint256 tokenId) private {
+    function _removeTransferredAmount(bytes32 chainHash, address erc721Token, uint256 tokenId) internal {
         require(transferredAmount[erc721Token][tokenId] == chainHash, "Token was already transferred from chain");
         transferredAmount[erc721Token][tokenId] = bytes32(0);
     }
@@ -317,7 +299,8 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
         address to,
         uint256 tokenId
     )
-        private
+        internal
+        virtual
         returns (bytes memory data)
     {
         bool isERC721AddedToSchain = _schainToERC721[chainHash].contains(erc721OnMainChain);
@@ -344,7 +327,7 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
      * 
      * - Given address should be contract.
      */
-    function _addERC721ForSchain(bytes32 chainHash, address erc721OnMainChain) private {
+    function _addERC721ForSchain(bytes32 chainHash, address erc721OnMainChain) internal {
         require(erc721OnMainChain.isContract(), "Given address is not a contract");
         require(!_schainToERC721[chainHash].contains(erc721OnMainChain), "ERC721 Token was already added");
         _schainToERC721[chainHash].add(erc721OnMainChain);
@@ -354,10 +337,61 @@ contract TokenManagerERC721 is TokenManager, ITokenManagerERC721 {
     /**
      * @dev Returns info about ERC721 token such as token name, symbol.
      */
-    function _getTokenInfo(IERC721MetadataUpgradeable erc721) private view returns (Messages.Erc721TokenInfo memory) {
+    function _getTokenInfo(IERC721MetadataUpgradeable erc721) internal view returns (Messages.Erc721TokenInfo memory) {
         return Messages.Erc721TokenInfo({
             name: erc721.name(),
             symbol: erc721.symbol()
         });
     }
+
+    function _isERC721AddedToSchain(bytes32 chainHash, address erc721OnMainChain) internal view returns (bool) {
+        return _schainToERC721[chainHash].contains(erc721OnMainChain);
+    }
+
+    /**
+     * @dev Burn tokens on schain and send message to unlock them on target chain.
+     */
+    function _exit(
+        bytes32 chainHash,
+        address messageReceiver,
+        address contractOnMainChain,
+        address to,
+        uint256 tokenId
+    )
+        private
+    {
+        bool isMainChainToken;
+        ERC721OnChain contractOnSchain = clonesErc721[chainHash][contractOnMainChain];
+        if (address(contractOnSchain) == address(0)) {
+            contractOnSchain = ERC721OnChain(contractOnMainChain);
+            require(!addedClones[contractOnSchain], "Incorrect main chain token");
+            isMainChainToken = true;
+        }
+        require(address(contractOnSchain).isContract(), "No token clone on schain");
+        require(contractOnSchain.getApproved(tokenId) == address(this), "Not allowed ERC721 Token");
+        bytes memory data = Messages.encodeTransferErc721Message(contractOnMainChain, to, tokenId);
+        if (isMainChainToken) {
+            require(chainHash != MAINNET_HASH, "Main chain token could not be transfered to Mainnet");
+            data = _receiveERC721(
+                chainHash,
+                address(contractOnSchain),
+                msg.sender,
+                tokenId
+            );
+            _saveTransferredAmount(chainHash, address(contractOnSchain), tokenId);
+            contractOnSchain.transferFrom(msg.sender, address(this), tokenId);
+        } else {
+            contractOnSchain.transferFrom(msg.sender, address(this), tokenId);
+            contractOnSchain.burn(tokenId);
+        }
+        messageProxy.postOutgoingMessage(chainHash, messageReceiver, data);
+    }
+
+    /**
+     * @dev Saves the ids of tokens that was transferred to schain.
+     */
+    function _saveTransferredAmount(bytes32 chainHash, address erc721Token, uint256 tokenId) private {
+        require(transferredAmount[erc721Token][tokenId] == bytes32(0), "Token was already transferred to chain");
+        transferredAmount[erc721Token][tokenId] = chainHash;
+    }
 }
diff --git a/proxy/contracts/schain/TokenManagers/TokenManagerERC721WithMetadata.sol b/proxy/contracts/schain/TokenManagers/TokenManagerERC721WithMetadata.sol
new file mode 100644
index 000000000..717e52900
--- /dev/null
+++ b/proxy/contracts/schain/TokenManagers/TokenManagerERC721WithMetadata.sol
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ *   TokenManagerERC721WithMetadata.sol - SKALE Interchain Messaging Agent
+ *   Copyright (C) 2021-Present SKALE Labs
+ *   @author Artem Payvin
+ *
+ *   SKALE IMA is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Affero General Public License as published
+ *   by the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SKALE IMA is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Affero General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public License
+ *   along with SKALE IMA.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+pragma solidity 0.8.6;
+
+import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
+
+import "./TokenManagerERC721.sol";
+import "../../Messages.sol";
+
+
+/**
+ * @title TokenManagerERC721WithMetadata
+ * @dev Runs on SKALE Chains,
+ * accepts messages from mainnet,
+ * and creates ERC721 clones.
+ * TokenManagerERC721 mints tokens. When a user exits a SKALE chain, it burns them.
+ */
+contract TokenManagerERC721WithMetadata is TokenManagerERC721 {
+    using AddressUpgradeable for address;
+
+    /**
+     * @dev Allows MessageProxy to post operational message from mainnet
+     * or SKALE chains.
+     *
+     * Requirements:
+     * 
+     * - MessageProxy must be the sender.
+     * - `fromChainHash` must exist in TokenManager addresses.
+     */
+    function postMessage(
+        bytes32 fromChainHash,
+        address sender,
+        bytes calldata data
+    )
+        external
+        override
+        onlyMessageProxy
+        checkReceiverChain(fromChainHash, sender)
+    {
+        Messages.MessageType operation = Messages.getMessageType(data);
+        address receiver = address(0);
+        if (
+            operation == Messages.MessageType.TRANSFER_ERC721_WITH_METADATA_AND_TOKEN_INFO ||
+            operation == Messages.MessageType.TRANSFER_ERC721_WITH_METADATA
+        ) {
+            receiver = _sendERC721(fromChainHash, data);
+        } else {
+            revert("MessageType is unknown");
+        }
+    }
+
+    /**
+     * @dev Allows TokenManager to send ERC721 tokens.
+     *  
+     * Emits a {ERC20TokenCreated} event if token did not exist and was automatically deployed.
+     * Emits a {ERC20TokenReceived} event on success.
+     */
+    function _sendERC721(bytes32 fromChainHash, bytes calldata data) internal override returns (address) {
+        Messages.MessageType messageType = Messages.getMessageType(data);
+        address receiver;
+        address token;
+        uint256 tokenId;
+        string memory tokenURI;
+        ERC721OnChain contractOnSchain;
+        if (messageType == Messages.MessageType.TRANSFER_ERC721_WITH_METADATA) {
+            Messages.TransferErc721MessageWithMetadata memory message =
+                Messages.decodeTransferErc721MessageWithMetadata(data);
+            receiver = message.erc721message.receiver;
+            token = message.erc721message.token;
+            tokenId = message.erc721message.tokenId;
+            tokenURI = message.tokenURI;
+            contractOnSchain = clonesErc721[fromChainHash][token];
+        } else {
+            Messages.TransferErc721WithMetadataAndTokenInfoMessage memory message =
+                Messages.decodeTransferErc721WithMetadataAndTokenInfoMessage(data);
+            receiver = message.baseErc721transferWithMetadata.erc721message.receiver;
+            token = message.baseErc721transferWithMetadata.erc721message.token;
+            tokenId = message.baseErc721transferWithMetadata.erc721message.tokenId;
+            tokenURI = message.baseErc721transferWithMetadata.tokenURI;
+            contractOnSchain = clonesErc721[fromChainHash][token];
+            if (address(contractOnSchain) == address(0)) {
+                require(automaticDeploy, "Automatic deploy is disabled");
+                contractOnSchain = new ERC721OnChain(message.tokenInfo.name, message.tokenInfo.symbol);           
+                clonesErc721[fromChainHash][token] = contractOnSchain;
+                addedClones[contractOnSchain] = true;
+                emit ERC721TokenCreated(fromChainHash, token, address(contractOnSchain));
+            }
+        }
+        if (
+            messageType == Messages.MessageType.TRANSFER_ERC721_WITH_METADATA &&
+            fromChainHash != MAINNET_HASH &&
+            _isERC721AddedToSchain(fromChainHash, token)
+        ) {
+            require(token.isContract(), "Incorrect main chain token");
+            require(IERC721Upgradeable(token).ownerOf(tokenId) == address(this), "Incorrect tokenId");
+            _removeTransferredAmount(fromChainHash, token, tokenId);
+            IERC721Upgradeable(token).transferFrom(address(this), receiver, tokenId);
+        } else {
+            contractOnSchain.mint(receiver, tokenId);
+            contractOnSchain.setTokenURI(tokenId, tokenURI);
+        }
+        emit ERC721TokenReceived(fromChainHash, token, address(contractOnSchain), tokenId);
+        return receiver;
+    }
+
+    /**
+     * @dev Allows DepositBoxERC721 to receive ERC721 tokens.
+     * 
+     * Emits an {ERC721TokenReady} event.
+     * 
+     * Requirements:
+     * 
+     * - Whitelist should be turned off for auto adding tokens to DepositBoxERC721.
+     */
+    function _receiveERC721(
+        bytes32 chainHash,
+        address erc721OnMainChain,
+        address to,
+        uint256 tokenId
+    )
+        internal
+        override
+        returns (bytes memory data)
+    {
+        bool isERC721AddedToSchain = _isERC721AddedToSchain(chainHash, erc721OnMainChain);
+        if (!isERC721AddedToSchain) {
+            _addERC721ForSchain(chainHash, erc721OnMainChain);
+            data = Messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnMainChain,
+                to,
+                tokenId,
+                _getTokenURI(IERC721MetadataUpgradeable(erc721OnMainChain), tokenId),
+                _getTokenInfo(IERC721MetadataUpgradeable(erc721OnMainChain))
+            );
+        } else {
+            data = Messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnMainChain,
+                to,
+                tokenId,
+                _getTokenURI(IERC721MetadataUpgradeable(erc721OnMainChain), tokenId)
+            );
+        }
+        emit ERC721TokenReady(chainHash, erc721OnMainChain, tokenId);
+    }
+
+    /**
+     * @dev Returns tokenURI of ERC721 token.
+     */
+    function _getTokenURI(IERC721MetadataUpgradeable erc721, uint256 tokenId) private view returns (string memory) {
+        return erc721.tokenURI(tokenId);
+    }
+
+}
\ No newline at end of file
diff --git a/proxy/contracts/schain/TokenManagers/TokenManagerEth.sol b/proxy/contracts/schain/TokenManagers/TokenManagerEth.sol
index 309ed36b3..37a749f8c 100644
--- a/proxy/contracts/schain/TokenManagers/TokenManagerEth.sol
+++ b/proxy/contracts/schain/TokenManagers/TokenManagerEth.sol
@@ -65,7 +65,7 @@ contract TokenManagerEth is TokenManager, ITokenManagerEth {
      * Requirements:
      * 
      * - MessageProxy must be the sender.
-     * - `fromSchainName` must exist in TokenManager addresses.
+     * - `fromChainHash` must exist in TokenManager addresses.
      */
     function postMessage(
         bytes32 fromChainHash,
@@ -76,13 +76,11 @@ contract TokenManagerEth is TokenManager, ITokenManagerEth {
         override
         onlyMessageProxy
         checkReceiverChain(fromChainHash, sender)
-        returns (address)
     {
         Messages.TransferEthMessage memory decodedMessage = Messages.decodeTransferEthMessage(data);
         address receiver = decodedMessage.receiver;
         require(receiver != address(0), "Incorrect receiver");
         ethErc20.mint(receiver, decodedMessage.amount);
-        return receiver;
     }
 
     /**
@@ -112,6 +110,9 @@ contract TokenManagerEth is TokenManager, ITokenManagerEth {
 
     // private
 
+    /**
+     * @dev Checks whether sender contract is DepositBox.
+     */
     function _checkSender(bytes32 fromChainHash, address sender) internal view override returns (bool) {
         return fromChainHash == MAINNET_HASH && sender == depositBox;
     }
diff --git a/proxy/contracts/schain/tokens/ERC721OnChain.sol b/proxy/contracts/schain/tokens/ERC721OnChain.sol
index 49388f239..98a1ec32f 100644
--- a/proxy/contracts/schain/tokens/ERC721OnChain.sol
+++ b/proxy/contracts/schain/tokens/ERC721OnChain.sol
@@ -70,7 +70,11 @@ contract ERC721OnChain is
         returns (bool)
     {
         require(_exists(tokenId), "Token does not exists");
-        require(_isApprovedOrOwner(msg.sender, tokenId), "Sender can not set token URI");
+        require(
+            _isApprovedOrOwner(msg.sender, tokenId) ||
+            hasRole(MINTER_ROLE, _msgSender()),
+            "Sender can not set token URI"
+        );
         _setTokenURI(tokenId, tokenUri);
         return true;
     }
diff --git a/proxy/migrations/deployMainnet.ts b/proxy/migrations/deployMainnet.ts
index a239a8b26..f9cf83f33 100644
--- a/proxy/migrations/deployMainnet.ts
+++ b/proxy/migrations/deployMainnet.ts
@@ -72,7 +72,7 @@ export async function getContractFactory(contract: string) {
 }
 
 
-function getContractManager() {
+export function getContractManager() {
     const defaultFilePath = "../data/skaleManagerComponents.json";
     const jsonData = require(defaultFilePath);
     try {
@@ -89,7 +89,8 @@ export const contractsToDeploy = [
     "DepositBoxEth",
     "DepositBoxERC20",
     "DepositBoxERC721",
-    "DepositBoxERC1155"
+    "DepositBoxERC1155",
+    "DepositBoxERC721WithMetadata"
 ]
 
 export const contracts = [
@@ -100,6 +101,8 @@ export const contracts = [
     "DepositBoxERC20",
     "DepositBoxERC721",
     "DepositBoxERC1155"
+
+    // "DepositBoxERC721WithMetadata"
 ]
 
 async function main() {
@@ -225,6 +228,9 @@ async function main() {
         outputObject[contractKey + "_abi"] = getAbi(deployed.get(contract)?.interface);
     }
 
+    outputObject[getContractKeyInAbiFile("DepositBoxERC721WithMetadata") + "_address"] = deployed.get("DepositBoxERC721WithMetadata")?.address;
+    outputObject[getContractKeyInAbiFile("DepositBoxERC721WithMetadata") + "_abi"] = getAbi(deployed.get("DepositBoxERC721WithMetadata")?.interface);
+
     await fs.writeFile("data/proxyMainnet.json", JSON.stringify(outputObject, null, 4));
 
     if( contractManager?.address !== null && contractManager?.address !== "" && contractManager?.address !== "0x0000000000000000000000000000000000000000" ) {
diff --git a/proxy/migrations/deploySchain.ts b/proxy/migrations/deploySchain.ts
index 871067844..c73d15c21 100644
--- a/proxy/migrations/deploySchain.ts
+++ b/proxy/migrations/deploySchain.ts
@@ -31,7 +31,17 @@ import { getAbi } from './tools/abi';
 import { Manifest, hashBytecode } from "@openzeppelin/upgrades-core";
 import { getManifestAdmin } from "@openzeppelin/hardhat-upgrades/dist/admin";
 import { Contract } from '@ethersproject/contracts';
-import { CommunityLocker, EthErc20, KeyStorage, MessageProxyForSchain, TokenManagerERC20, TokenManagerERC721, TokenManagerEth, TokenManagerLinker } from '../typechain';
+import {
+    CommunityLocker,
+    EthErc20,
+    KeyStorage,
+    MessageProxyForSchain,
+    TokenManagerERC20,
+    TokenManagerERC721,
+    TokenManagerEth,
+    TokenManagerLinker,
+    TokenManagerERC721WithMetadata
+} from '../typechain';
 import { TokenManagerERC1155 } from '../typechain/TokenManagerERC1155';
 import { getVersion } from './tools/version';
 
@@ -74,7 +84,7 @@ export async function getContractFactory(contract: string) {
     return await getLinkedContractFactory(contract, libraries);
 }
 
-function getProxyMainnet(contractName: string) {
+export function getProxyMainnet(contractName: string) {
     const defaultFilePath = "../data/proxyMainnet.json";
     const jsonData = require(defaultFilePath);
     try {
@@ -94,6 +104,7 @@ export const contracts = [
     "TokenManagerERC20",
     "TokenManagerERC721",
     "TokenManagerERC1155",
+    // "TokenManagerERC721WithMetadata",
     "EthErc20",
     "KeyStorage"
 ];
@@ -118,6 +129,8 @@ async function main() {
         getProxyMainnet("deposit_box_erc721_address") === "" ||
         getProxyMainnet("deposit_box_erc1155_address") === undefined ||
         getProxyMainnet("deposit_box_erc1155_address") === "" ||
+        getProxyMainnet("deposit_box_erc721_with_metadata_address") === undefined ||
+        getProxyMainnet("deposit_box_erc721_with_metadata_address") === "" ||
         getProxyMainnet("community_pool_address") === undefined ||
         getProxyMainnet("community_pool_address") === "" ||
         getProxyMainnet("linker_address") === undefined ||
@@ -130,6 +143,7 @@ async function main() {
     const depositBoxERC20Address = getProxyMainnet("deposit_box_erc20_address");
     const depositBoxERC721Address = getProxyMainnet("deposit_box_erc721_address");
     const depositBoxERC1155Address = getProxyMainnet("deposit_box_erc1155_address");
+    const depositBoxERC721WithMetadataAddress = getProxyMainnet("deposit_box_erc721_with_metadata_address");
     const communityPoolAddress = getProxyMainnet("community_pool_address");
     const linkerAddress = getProxyMainnet("linker_address");
 
@@ -233,11 +247,25 @@ async function main() {
     deployed.set( "TokenManagerERC1155", { address: tokenManagerERC1155.address, interface: tokenManagerERC1155.interface } );
     console.log("Contract TokenManagerERC1155 deployed to", tokenManagerERC1155.address);
 
+    console.log("Deploy TokenManagerERC721WithMetadata");
+    const tokenManagerERC721WithMetadataFactory = await ethers.getContractFactory("TokenManagerERC721WithMetadata");
+    const tokenManagerERC721WithMetadata = await upgrades.deployProxy(tokenManagerERC721WithMetadataFactory, [
+        schainName,
+        messageProxy.address,
+        tokenManagerLinker.address,
+        communityLocker.address,
+        depositBoxERC721WithMetadataAddress
+    ]) as TokenManagerERC721WithMetadata;
+    await tokenManagerERC721WithMetadata.deployTransaction.wait();
+    deployed.set( "TokenManagerERC721WithMetadata", { address: tokenManagerERC721WithMetadata.address, interface: tokenManagerERC721WithMetadata.interface } );
+    console.log("Contract TokenManagerERC721WithMetadata deployed to", tokenManagerERC721WithMetadata.address);
+
     console.log("Register token managers");
     await (await tokenManagerLinker.registerTokenManager(tokenManagerEth.address)).wait();
     await (await tokenManagerLinker.registerTokenManager(tokenManagerERC20.address)).wait();
     await (await tokenManagerLinker.registerTokenManager(tokenManagerERC721.address)).wait();
     await (await tokenManagerLinker.registerTokenManager(tokenManagerERC1155.address)).wait();
+    await (await tokenManagerLinker.registerTokenManager(tokenManagerERC721WithMetadata.address)).wait();
 
     console.log("Deploy EthErc20");
     const ethERC20Factory = await ethers.getContractFactory("EthErc20");
@@ -264,7 +292,8 @@ async function main() {
         tokenManagerERC20,
         tokenManagerERC721,
         tokenManagerERC1155,
-        communityLocker
+        communityLocker,
+        tokenManagerERC721WithMetadata
     ];
     const extraContractRegistrarRole = await messageProxy.EXTRA_CONTRACT_REGISTRAR_ROLE();
     await messageProxy.grantRole(extraContractRegistrarRole, owner.address);
@@ -280,6 +309,8 @@ async function main() {
         jsonObjectABI[propertyName + "_address"] = deployed.get( contractName )?.address;
         jsonObjectABI[propertyName + "_abi"] = getAbi(deployed.get( contractName )?.interface);
     }
+    jsonObjectABI[getContractKeyInAbiFile("TokenManagerERC721WithMetadata") + "_address"] = deployed.get( "TokenManagerERC721WithMetadata" )?.address;
+    jsonObjectABI[getContractKeyInAbiFile("TokenManagerERC721WithMetadata") + "_abi"] = getAbi(deployed.get( "TokenManagerERC721WithMetadata" )?.interface);
     const erc20OnChainFactory = await ethers.getContractFactory("ERC20OnChain");
     jsonObjectABI.ERC20OnChain_abi = getAbi(erc20OnChainFactory.interface);
     const erc721OnChainFactory = await ethers.getContractFactory("ERC721OnChain");
diff --git a/proxy/migrations/upgrade.ts b/proxy/migrations/upgrade.ts
index ed1fa0c6e..cb869bb1d 100644
--- a/proxy/migrations/upgrade.ts
+++ b/proxy/migrations/upgrade.ts
@@ -234,7 +234,7 @@ export async function upgrade(
             console.log(chalk.blue("Return ownership to wallet"));
             await (await safeMock.transferProxyAdminOwnership(proxyAdmin.address, deployer.address)).wait();
             if (await proxyAdmin.owner() === deployer.address) {
-                await (await safeMock.destroy()).wait();
+                await (await safeMock.destroy({gasLimit: 1000000})).wait();
             } else {
                 console.log(chalk.blue("Something went wrong with ownership transfer"));
                 process.exit(1);
diff --git a/proxy/migrations/upgradeMainnet.ts b/proxy/migrations/upgradeMainnet.ts
index 32e472f33..3c29c996c 100644
--- a/proxy/migrations/upgradeMainnet.ts
+++ b/proxy/migrations/upgradeMainnet.ts
@@ -1,16 +1,18 @@
-import { ethers } from "hardhat";
+import { ethers, upgrades } from "hardhat";
 import hre from "hardhat";
-import { contracts, getContractKeyInAbiFile } from "./deployMainnet";
+import { contracts, getContractKeyInAbiFile, getContractManager } from "./deployMainnet";
 import { upgrade } from "./upgrade";
 import { getManifestAdmin } from "@openzeppelin/hardhat-upgrades/dist/admin";
 import { Manifest } from "@openzeppelin/upgrades-core";
 import chalk from "chalk";
-import { MessageProxyForMainnet, DepositBoxERC20, DepositBoxERC721, DepositBoxERC1155 } from "../typechain/";
+import { MessageProxyForMainnet, DepositBoxERC20, DepositBoxERC721, DepositBoxERC1155, DepositBoxERC721WithMetadata, Linker } from "../typechain/";
 import { encodeTransaction } from "./tools/multiSend";
 import { TypedEvent, TypedEventFilter } from "../typechain/commons";
 import { promises as fs } from "fs";
 import { hexZeroPad } from "@ethersproject/bytes";
 import { Contract } from "@ethersproject/contracts";
+import { verifyProxy } from "./tools/verification";
+import { getAbi } from "./tools/abi";
 
 function prepareInitializeFromMap(
     safeTransactions: any,
@@ -190,7 +192,97 @@ async function main() {
     await upgrade(
         "1.1.0",
         contracts,
-        async (safeTransactions, abi) => undefined,
+        async (safeTransactions, abi) => {
+            const proxyAdmin = await getManifestAdmin(hre);
+            const safe = await proxyAdmin.owner();
+            const [ deployer ] = await ethers.getSigners();
+            const contractManager = getContractManager();
+            const chainId: number = (await ethers.provider.getNetwork()).chainId;
+
+            const linkerName = "Linker";
+            const messageProxyForMainnetName = "MessageProxyForMainnet";
+
+            const messageProxyForMainnetFactory = await ethers.getContractFactory(messageProxyForMainnetName);
+            const messageProxyForMainnetAddress = abi[getContractKeyInAbiFile(messageProxyForMainnetName) + "_address"];
+            let messageProxyForMainnet;
+            const linkerFactory = await ethers.getContractFactory(linkerName);
+            const linkerAddress = abi[getContractKeyInAbiFile(linkerName) + "_address"];
+            let linker;
+
+            if (messageProxyForMainnetAddress) {
+                messageProxyForMainnet = messageProxyForMainnetFactory.attach(messageProxyForMainnetAddress) as MessageProxyForMainnet;
+            } else {
+                console.log(chalk.red("Message Proxy address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+
+            if (linkerAddress) {
+                linker = linkerFactory.attach(linkerAddress) as Linker;
+            } else {
+                console.log(chalk.red("Linker address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+
+            const depositBoxERC721WithMetadataName = "DepositBoxERC721WithMetadata";
+            const depositBoxERC721WithMetadataFactory = await ethers.getContractFactory(depositBoxERC721WithMetadataName);
+            console.log("Deploy", depositBoxERC721WithMetadataName);
+            const depositBoxERC721WithMetadata = (
+                await upgrades.deployProxy(
+                    depositBoxERC721WithMetadataFactory,
+                    [
+                        contractManager?.address,
+                        abi[getContractKeyInAbiFile(linkerName) + "_address"],
+                        abi[getContractKeyInAbiFile(messageProxyForMainnetName) + "_address"]
+                    ],
+                    {
+                        initializer: 'initialize(address,address,address)'
+                    }
+                )
+            ) as DepositBoxERC721WithMetadata;
+            await depositBoxERC721WithMetadata.deployTransaction.wait();
+            console.log(chalk.yellowBright("Grant role DEFAULT_ADMIN_ROLE of", depositBoxERC721WithMetadata.address, "to", safe));
+            const defaultAdminRole = await depositBoxERC721WithMetadata.DEFAULT_ADMIN_ROLE();
+            await (await depositBoxERC721WithMetadata.grantRole(defaultAdminRole, safe)).wait();
+            if (chainId === 1) {
+                console.log(chalk.yellowBright("Revoke role DEFAULT_ADMIN_ROLE of", depositBoxERC721WithMetadata.address, "from", deployer.address));
+                await (await depositBoxERC721WithMetadata.revokeRole(defaultAdminRole, deployer.address)).wait();
+            }
+            const linkerRole = await linker.LINKER_ROLE();
+            console.log(chalk.yellow("Prepare transaction to grant Role LINKER for", safe, "in Linker", abi[getContractKeyInAbiFile(linkerName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(linkerName) + "_address"],
+                0,
+                linker.interface.encodeFunctionData("grantRole", [linkerRole, safe])
+            ));
+            console.log(chalk.yellow("Prepare transaction to register DepositBox", depositBoxERC721WithMetadata.address, "in Linker", abi[getContractKeyInAbiFile(linkerName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(linkerName) + "_address"],
+                0,
+                linker.interface.encodeFunctionData("registerMainnetContract", [depositBoxERC721WithMetadata.address])
+            ));
+            const extraContractRegistrarRole = await messageProxyForMainnet.EXTRA_CONTRACT_REGISTRAR_ROLE();
+            console.log(chalk.yellow("Prepare transaction to grant Role extraContract Registrar for", safe, "in MessageProxy", abi[getContractKeyInAbiFile(messageProxyForMainnetName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(messageProxyForMainnetName) + "_address"],
+                0,
+                messageProxyForMainnet.interface.encodeFunctionData("grantRole", [extraContractRegistrarRole, safe])
+            ));
+            console.log(chalk.yellow("Prepare transaction to register DepositBox", depositBoxERC721WithMetadata.address, "as contract for all in MessageProxy", abi[getContractKeyInAbiFile(messageProxyForMainnetName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(messageProxyForMainnetName) + "_address"],
+                0,
+                messageProxyForMainnet.interface.encodeFunctionData("registerExtraContractForAll", [depositBoxERC721WithMetadata.address])
+            ));
+            await verifyProxy(depositBoxERC721WithMetadataName, depositBoxERC721WithMetadata.address, []);
+            abi[getContractKeyInAbiFile(depositBoxERC721WithMetadataName) + "_abi"] = getAbi(depositBoxERC721WithMetadata.interface);
+            abi[getContractKeyInAbiFile(depositBoxERC721WithMetadataName) + "_address"] = depositBoxERC721WithMetadata.address;
+        },
         async (safeTransactions, abi) => {
             const proxyAdmin = await getManifestAdmin(hre);
             const owner = await proxyAdmin.owner();
@@ -218,6 +310,12 @@ async function main() {
                     0,
                     messageProxyForMainnet.interface.encodeFunctionData("setNewMessageGasCost", [9000])
                 ));
+                safeTransactions.push(encodeTransaction(
+                    0,
+                    messageProxyForMainnetAddress,
+                    0,
+                    messageProxyForMainnet.interface.encodeFunctionData("setNewHeaderMessageGasCost", [73800])
+                ));
             } else {
                 console.log(chalk.red("MessageProxyForMainnet was not found!"));
                 console.log(chalk.red("Check your abi!!!"));
diff --git a/proxy/migrations/upgradeSchain.ts b/proxy/migrations/upgradeSchain.ts
index da9f3dc93..18e15b1f0 100644
--- a/proxy/migrations/upgradeSchain.ts
+++ b/proxy/migrations/upgradeSchain.ts
@@ -1,13 +1,18 @@
-import { MessageProxyForSchain } from "../typechain";
-import { contracts, getContractKeyInAbiFile } from "./deploySchain";
+import { MessageProxyForSchain, TokenManagerERC20, TokenManagerERC721, TokenManagerERC1155, TokenManagerERC721WithMetadata, TokenManagerLinker } from "../typechain";
+import hre from "hardhat";
+import { contracts, getContractKeyInAbiFile, getProxyMainnet } from "./deploySchain";
 import { manifestSetup } from "./generateManifest";
 import { encodeTransaction } from "./tools/multiSend";
 import { upgrade } from "./upgrade";
 import { hexZeroPad } from "@ethersproject/bytes";
 import chalk from "chalk";
-import { ethers } from "hardhat";
+import { ethers, upgrades } from "hardhat";
 import { Contract } from "ethers";
 import { promises as fs } from "fs";
+import { TypedEvent, TypedEventFilter } from "../typechain/commons";
+import { getManifestAdmin } from "@openzeppelin/hardhat-upgrades/dist/admin";
+import { verifyProxy } from "./tools/verification";
+import { getAbi } from "./tools/abi";
 
 function stringValue(value: string | undefined) {
     if (value) {
@@ -78,13 +83,180 @@ async function prepareContractRegisteredAndInitialize(
     prepareInitializeFromMap(safeTransactions, chainToRegisteredContracts, messageProxyForSchain, "initializeAllRegisteredContracts");
 }
 
+async function runInitialize(
+    safeTransactions: string[],
+    abi: any,
+    events: TypedEvent<[string, string] & { tokenOnMainnet: string; tokenOnSchain: string; }>[],
+    tokenManagerName: "TokenManagerERC20" | "TokenManagerERC721" | "TokenManagerERC1155",
+    eventName: string
+) {
+    console.log(chalk.yellow("" + events.length + " events " + eventName + " found"));
+    const tokensToClones = new Map<string, string[]>();
+    for (const event of events) {
+        const addedTokens = tokensToClones.get(event.args.tokenOnMainnet);
+        if (!addedTokens) {
+            tokensToClones.set(event.args.tokenOnMainnet, [event.args.tokenOnSchain]);
+        }
+    }
+    const tokenManagerFactory = await ethers.getContractFactory(tokenManagerName);
+    const tokenManagerAddress = abi[getContractKeyInAbiFile(tokenManagerName) + "_address"];
+    if (tokenManagerAddress) {
+        let tokenManager: TokenManagerERC20 | TokenManagerERC721 | TokenManagerERC1155;
+        let functionName: string;
+        if (tokenManagerName === "TokenManagerERC20") {
+            tokenManager = tokenManagerFactory.attach(tokenManagerAddress) as TokenManagerERC20;
+            functionName = "initializeAllClonesERC20";
+        } else if (tokenManagerName === "TokenManagerERC721") {
+            tokenManager = tokenManagerFactory.attach(tokenManagerAddress) as TokenManagerERC721;
+            functionName = "initializeAllClonesERC721";
+        } else {
+            tokenManager = tokenManagerFactory.attach(tokenManagerAddress) as TokenManagerERC1155;
+            functionName = "initializeAllClonesERC1155";
+        }
+        prepareInitializeFromMap(safeTransactions, tokensToClones, tokenManager, functionName);
+    }
+}
+
+async function findEventsAndInitialize(
+    safeTransactions: string[],
+    abi: any,
+    tokenManagerName: "TokenManagerERC20" | "TokenManagerERC721" | "TokenManagerERC1155",
+    eventName: string
+) {
+    const tokenManagerFactory = await ethers.getContractFactory(tokenManagerName);
+    const tokenManagerAddress = abi[getContractKeyInAbiFile(tokenManagerName) + "_address"];
+    if (tokenManagerAddress) {
+        console.log(chalk.yellow("Will find all " + eventName + " events in " + tokenManagerName + " and initialize"));
+        let tokenManager: TokenManagerERC20 | TokenManagerERC721 | TokenManagerERC1155;
+        if (tokenManagerName === "TokenManagerERC20") {
+            tokenManager = tokenManagerFactory.attach(tokenManagerAddress) as TokenManagerERC20;
+        } else if (tokenManagerName === "TokenManagerERC721") {
+            tokenManager = tokenManagerFactory.attach(tokenManagerAddress) as TokenManagerERC721;
+        } else {
+            tokenManager = tokenManagerFactory.attach(tokenManagerAddress) as TokenManagerERC1155;
+        }
+        const eventFilter: TypedEventFilter<[string, string], { tokenOnMainnet: string, tokenOnSchain: string}> = {
+            address: tokenManagerAddress,
+            topics: [ ethers.utils.id(eventName + "(string,address)") ]
+        }
+        const events = await tokenManager.queryFilter(eventFilter);
+        if (events.length > 0) {
+            await runInitialize(safeTransactions, abi, events, tokenManagerName, eventName);
+        } else {
+            console.log(chalk.yellow("No events " + eventName + " found - no reason to run initialize"));
+        }
+    } else {
+        console.log(chalk.red("" + tokenManagerName + " was not found!"));
+        console.log(chalk.red("Check your abi!!!"));
+        process.exit(1);
+    }
+}
+
 async function main() {
     const pathToManifest: string = stringValue(process.env.MANIFEST);
     await manifestSetup( pathToManifest );
     await upgrade(
         "1.1.0",
         contracts,
-        async( safeTransactions, abi ) => undefined,
+        async (safeTransactions, abi) => {
+            const proxyAdmin = await getManifestAdmin(hre);
+            const safe = await proxyAdmin.owner();
+
+            const messageProxyForSchainName = "MessageProxyForSchain";
+            const tokenManagerLinkerName = "TokenManagerLinker";
+            const communityLockerName = "CommunityLocker";
+            const messageProxyForSchainFactory = await ethers.getContractFactory(messageProxyForSchainName);
+            const messageProxyForSchainAddress = abi[getContractKeyInAbiFile(messageProxyForSchainName) + "_address"];
+            let messageProxyForSchain;
+
+            const tokenManagerLinkerFactory = await ethers.getContractFactory(tokenManagerLinkerName);
+            const tokenManagerLinkerAddress = abi[getContractKeyInAbiFile(tokenManagerLinkerName) + "_address"];
+            let tokenManagerLinker;
+
+            const schainName = process.env.CHAIN_NAME_SCHAIN;
+
+            if (schainName === undefined || schainName === "" || schainName === null) {
+                console.log(chalk.red("PLEASE PROVIDE A CHAIN_NAME_SCHAIN"));
+                process.exit(1);
+            }
+
+            if (messageProxyForSchainAddress) {
+                messageProxyForSchain = messageProxyForSchainFactory.attach(messageProxyForSchainAddress) as MessageProxyForSchain;
+            } else {
+                console.log(chalk.red("Message Proxy address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+
+            if (tokenManagerLinkerAddress) {
+                tokenManagerLinker = tokenManagerLinkerFactory.attach(tokenManagerLinkerAddress) as TokenManagerLinker;
+            } else {
+                console.log(chalk.red("Token Manager Linker address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+
+            if(
+                getProxyMainnet("deposit_box_erc721_with_metadata_address") === undefined ||
+                getProxyMainnet("deposit_box_erc721_with_metadata_address") === ""
+            ) {
+                console.log( "Please provide correct abi for mainnet contracts in IMA/proxy/data/proxyMainnet.json" );
+                process.exit( 126 );
+            }
+            const depositBoxERC721WithMetadataAddress = getProxyMainnet("deposit_box_erc721_with_metadata_address");
+
+            const tokenManagerERC721WithMetadataName = "TokenManagerERC721WithMetadata";
+            const tokenManagerERC721WithMetadataFactory = await ethers.getContractFactory(tokenManagerERC721WithMetadataName);
+            console.log("Deploy", tokenManagerERC721WithMetadataName);
+            const tokenManagerERC721WithMetadata = (
+                await upgrades.deployProxy(
+                    tokenManagerERC721WithMetadataFactory,
+                    [
+                        schainName,
+                        abi[getContractKeyInAbiFile(messageProxyForSchainName) + "_address"],
+                        abi[getContractKeyInAbiFile(tokenManagerLinkerName) + "_address"],
+                        abi[getContractKeyInAbiFile(communityLockerName) + "_address"],
+                        depositBoxERC721WithMetadataAddress
+                    ]
+                )
+            ) as TokenManagerERC721WithMetadata;
+            await tokenManagerERC721WithMetadata.deployTransaction.wait();
+            console.log(chalk.yellowBright("Grant role DEFAULT_ADMIN_ROLE of", tokenManagerERC721WithMetadata.address, "to", safe));
+            await (await tokenManagerERC721WithMetadata.grantRole(await tokenManagerERC721WithMetadata.DEFAULT_ADMIN_ROLE(), safe)).wait();
+            const registrarRole = await tokenManagerLinker.REGISTRAR_ROLE();
+            console.log(chalk.yellow("Prepare transaction to grant Role REGISTRAR for", safe, "in TokenManagerLinker", abi[getContractKeyInAbiFile(tokenManagerLinkerName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(tokenManagerLinkerName) + "_address"],
+                0,
+                tokenManagerLinker.interface.encodeFunctionData("grantRole", [registrarRole, safe])
+            ));
+            console.log(chalk.yellow("Prepare transaction to register TokenManager", tokenManagerERC721WithMetadata.address, "in Linker", abi[getContractKeyInAbiFile(tokenManagerLinkerName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(tokenManagerLinkerName) + "_address"],
+                0,
+                tokenManagerLinker.interface.encodeFunctionData("registerTokenManager", [tokenManagerERC721WithMetadata.address])
+            ));
+            const extraContractRegistrarRole = await messageProxyForSchain.EXTRA_CONTRACT_REGISTRAR_ROLE();
+            console.log(chalk.yellow("Prepare transaction to grant Role extraContract Registrar for", safe, "as contract for all in MessageProxy", abi[getContractKeyInAbiFile(messageProxyForSchainName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(messageProxyForSchainName) + "_address"],
+                0,
+                messageProxyForSchain.interface.encodeFunctionData("grantRole", [extraContractRegistrarRole, safe])
+            ));
+            console.log(chalk.yellow("Prepare transaction to register TokenManager", tokenManagerERC721WithMetadata.address, "as contract for all in MessageProxy", abi[getContractKeyInAbiFile(messageProxyForSchainName) + "_address"]));
+            safeTransactions.push(encodeTransaction(
+                0,
+                abi[getContractKeyInAbiFile(messageProxyForSchainName) + "_address"],
+                0,
+                messageProxyForSchain.interface.encodeFunctionData("registerExtraContractForAll", [tokenManagerERC721WithMetadata.address])
+            ));
+            await verifyProxy(tokenManagerERC721WithMetadataName, tokenManagerERC721WithMetadata.address, []);
+            abi[getContractKeyInAbiFile(tokenManagerERC721WithMetadataName) + "_abi"] = getAbi(tokenManagerERC721WithMetadata.interface);
+            abi[getContractKeyInAbiFile(tokenManagerERC721WithMetadataName) + "_address"] = tokenManagerERC721WithMetadata.address;
+        },
         async( safeTransactions, abi ) => {
             const messageProxyForSchainName = "MessageProxyForSchain";
             const messageProxyForSchainFactory = await ethers.getContractFactory(messageProxyForSchainName);
@@ -100,7 +272,34 @@ async function main() {
                 messageProxyForSchain = messageProxyForSchainFactory.attach(messageProxyForSchainAddress) as MessageProxyForSchain;
                 await prepareContractRegisteredAndInitialize(safeTransactions, abi, messageProxyForSchain);
             } else {
-                console.log(chalk.red("Addresses were not found!"));
+                console.log(chalk.red("Message Proxy address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+            if (tokenManagerERC20Address) {
+                console.log(chalk.yellow("Prepare transaction to initialize all token clones for ERC20"));
+                findEventsAndInitialize(safeTransactions, abi, "TokenManagerERC20", "ERC20TokenCreated");
+                findEventsAndInitialize(safeTransactions, abi, "TokenManagerERC20", "ERC20TokenAdded");
+            } else {
+                console.log(chalk.red("Token Manager ERC20 address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+            if (tokenManagerERC721Address) {
+                console.log(chalk.yellow("Prepare transaction to initialize all token clones for ERC721"));
+                findEventsAndInitialize(safeTransactions, abi, "TokenManagerERC721", "ERC721TokenCreated");
+                findEventsAndInitialize(safeTransactions, abi, "TokenManagerERC721", "ERC721TokenAdded");
+            } else {
+                console.log(chalk.red("Token Manager ERC721 address was not found!"));
+                console.log(chalk.red("Check your abi!!!"));
+                process.exit(1);
+            }
+            if (tokenManagerERC1155Address) {
+                console.log(chalk.yellow("Prepare transaction to initialize all token clones for ERC1155"));
+                findEventsAndInitialize(safeTransactions, abi, "TokenManagerERC1155", "ERC1155TokenCreated");
+                findEventsAndInitialize(safeTransactions, abi, "TokenManagerERC1155", "ERC1155TokenAdded");
+            } else {
+                console.log(chalk.red("Token Manager ERC1155 address was not found!"));
                 console.log(chalk.red("Check your abi!!!"));
                 process.exit(1);
             }
diff --git a/proxy/package.json b/proxy/package.json
index e998b70e2..fbf8a3cf5 100644
--- a/proxy/package.json
+++ b/proxy/package.json
@@ -25,17 +25,17 @@
         "@openzeppelin/contracts-upgradeable": "^4.3.2",
         "@openzeppelin/hardhat-upgrades": "^1.9.0",
         "@skalenetwork/etherbase-interfaces": "^0.0.1-develop.20",
-        "@skalenetwork/ima-interfaces": "1.0.0-develop.14",
-        "@skalenetwork/skale-manager-interfaces": "^0.1.2",
+        "@skalenetwork/ima-interfaces": "1.0.0-develop.16",
+        "@skalenetwork/skale-manager-interfaces": "1.0.0-develop.1",
         "axios": "^0.21.4",
         "dotenv": "^10.0.0",
-        "web3": "^1.6.1",
         "ethereumjs-tx": "2.1.2",
         "ethereumjs-util": "^7.1.3",
         "ethers": "^5.4.7",
         "ethers-eip712": "^0.2.0",
         "hardhat": "^2.5.0",
-        "path": "^0.12.7"
+        "path": "^0.12.7",
+        "web3": "^1.6.1"
     },
     "devDependencies": {
         "@nomiclabs/hardhat-etherscan": "^2.1.1",
diff --git a/proxy/predeployed/src/ima_predeployed/addresses.py b/proxy/predeployed/src/ima_predeployed/addresses.py
index a5c5304ac..23f51c009 100644
--- a/proxy/predeployed/src/ima_predeployed/addresses.py
+++ b/proxy/predeployed/src/ima_predeployed/addresses.py
@@ -17,3 +17,5 @@
 TOKEN_MANAGER_LINKER_IMPLEMENTATION_ADDRESS = '0xd2aAA008D2000000000000000000000000000000'
 TOKEN_MANAGER_ERC1155_ADDRESS = '0xD2aaA00900000000000000000000000000000000'
 TOKEN_MANAGER_ERC1155_IMPLEMENTATION_ADDRESS = '0xD2AaA009d2000000000000000000000000000000'
+TOKEN_MANAGER_ERC721_WITH_METADATA_ADDRESS = '0xd2AaA00a00000000000000000000000000000000'
+TOKEN_MANAGER_ERC721_WITH_METADATA_IMPLEMENTATION_ADDRESS = '0xd2AAA00Ad2000000000000000000000000000000'
diff --git a/proxy/predeployed/src/ima_predeployed/contracts/token_manager_erc721_with_metadata.py b/proxy/predeployed/src/ima_predeployed/contracts/token_manager_erc721_with_metadata.py
new file mode 100644
index 000000000..708f29b42
--- /dev/null
+++ b/proxy/predeployed/src/ima_predeployed/contracts/token_manager_erc721_with_metadata.py
@@ -0,0 +1,40 @@
+from ima_predeployed.contracts.token_manager import TokenManagerGenerator
+
+
+class TokenManagerErc721WithMetadataGenerator(TokenManagerGenerator):
+    ARTIFACT_FILENAME = "TokenManagerERC721WithMetadata.json"
+
+    # ---------- storage ----------
+    # --------Initializable--------
+    # 0:    _initialized, _initializing;
+    # -----ContextUpgradeable------
+    # 1:    __gap
+    # ...   __gap
+    # 50:   __gap
+    # ------ERC165Upgradeable------
+    # 51:   __gap
+    # ...   __gap
+    # 100:  __gap
+    # --AccessControlUpgradeable---
+    # 101:  _roles
+    # 102:  __gap
+    # ...   __gap
+    # 150:  __gap
+    # AccessControlEnumerableUpgradeable
+    # 151:  _roleMembers
+    # 152:  __gap
+    # ...   __gap
+    # 200:  __gap
+    # ---------TokenManager---------
+    # 201:  messageProxy
+    # 202:  tokenManagerLinker
+    # 203:  communityLocker
+    # 204:  schainHash
+    # 205:  depositBox, automaticDeploy
+    # 206:  tokenManagers
+    # ------TokenManagerERC721------
+    # 207:  clonesErc721
+    # ------TokenManagerERC721WithMetadata------
+
+    def __init__(self, deployer_address: str, deposit_box_address: str, schain_name: str):
+        super().__init__(deployer_address, deposit_box_address, schain_name)
diff --git a/proxy/predeployed/src/ima_predeployed/generator.py b/proxy/predeployed/src/ima_predeployed/generator.py
index f57db7e0e..7fdd60c69 100644
--- a/proxy/predeployed/src/ima_predeployed/generator.py
+++ b/proxy/predeployed/src/ima_predeployed/generator.py
@@ -5,6 +5,7 @@
 from ima_predeployed.contracts.token_manager_erc20 import TokenManagerErc20Generator
 from ima_predeployed.contracts.token_manager_erc721 import TokenManagerErc721Generator
 from ima_predeployed.contracts.token_manager_erc1155 import TokenManagerErc1155Generator
+from ima_predeployed.contracts.token_manager_erc721_with_metadata import TokenManagerErc721WithMetadataGenerator
 from ima_predeployed.contracts.token_manager_eth import TokenManagerEthGenerator
 from ima_predeployed.contracts.token_manager_linker import TokenManagerLinkerGenerator
 from .contracts.community_locker import CommunityLockerGenerator
@@ -22,7 +23,7 @@
     TOKEN_MANAGER_LINKER_ADDRESS, TOKEN_MANAGER_ETH_IMPLEMENTATION_ADDRESS, TOKEN_MANAGER_ETH_ADDRESS, \
     TOKEN_MANAGER_ERC20_IMPLEMENTATION_ADDRESS, TOKEN_MANAGER_ERC20_ADDRESS, \
     TOKEN_MANAGER_ERC721_IMPLEMENTATION_ADDRESS, TOKEN_MANAGER_ERC721_ADDRESS, ETH_ERC20_IMPLEMENTATION_ADDRESS, \
-    ETH_ERC20_ADDRESS
+    ETH_ERC20_ADDRESS, TOKEN_MANAGER_ERC721_WITH_METADATA_IMPLEMENTATION_ADDRESS, TOKEN_MANAGER_ERC721_WITH_METADATA_ADDRESS
 
 
 def generate_contracts(
@@ -85,6 +86,13 @@ def generate_contracts(
         TokenManagerErc1155Generator(owner_address, contracts_on_mainnet['deposit_box_erc1155_address'], schain_name)
     )
 
+    token_manager_erc721_with_metadata_implementation = ContractGenerator(TokenManagerErc721WithMetadataGenerator.ARTIFACT_FILENAME)
+    token_manager_erc721_with_metadata = UpgradeableContractGenerator(
+        TOKEN_MANAGER_ERC721_WITH_METADATA_IMPLEMENTATION_ADDRESS,
+        PROXY_ADMIN_ADDRESS,
+        TokenManagerErc721WithMetadataGenerator(owner_address, contracts_on_mainnet['deposit_box_erc721_with_metadata_address'], schain_name)
+    )
+
     eth_erc20_implementation = ContractGenerator(EthErc20Generator.ARTIFACT_FILENAME)
     eth_erc20 = UpgradeableContractGenerator(
         ETH_ERC20_IMPLEMENTATION_ADDRESS,
@@ -119,6 +127,9 @@ def generate_contracts(
         TOKEN_MANAGER_ERC1155_ADDRESS: token_manager_erc1155.generate_contract(),
         TOKEN_MANAGER_ERC1155_IMPLEMENTATION_ADDRESS: token_manager_erc1155_implementation.generate_contract(),
 
+        TOKEN_MANAGER_ERC721_WITH_METADATA_ADDRESS: token_manager_erc721_with_metadata.generate_contract(),
+        TOKEN_MANAGER_ERC721_WITH_METADATA_IMPLEMENTATION_ADDRESS: token_manager_erc721_with_metadata_implementation.generate_contract(),
+
         ETH_ERC20_ADDRESS: eth_erc20.generate_contract(),
         ETH_ERC20_IMPLEMENTATION_ADDRESS: eth_erc20_implementation.generate_contract()
     }
@@ -169,6 +180,10 @@ def generate_abi() -> dict:
             'token_manager_erc1155',
             TOKEN_MANAGER_ERC1155_ADDRESS,
             ContractGenerator(TokenManagerErc1155Generator.ARTIFACT_FILENAME).abi),
+        **generate_abi_key(
+            'token_manager_erc721_with_metadata',
+            TOKEN_MANAGER_ERC721_WITH_METADATA_ADDRESS,
+            ContractGenerator(TokenManagerErc721WithMetadataGenerator.ARTIFACT_FILENAME).abi),
         **generate_abi_key(
             'eth_erc20',
             ETH_ERC20_ADDRESS,
diff --git a/proxy/yarn.lock b/proxy/yarn.lock
index d5c99a897..cbd737641 100644
--- a/proxy/yarn.lock
+++ b/proxy/yarn.lock
@@ -757,13 +757,18 @@
   resolved "https://registry.yarnpkg.com/@skalenetwork/etherbase-interfaces/-/etherbase-interfaces-0.0.1-develop.20.tgz#33f61e18d695fd47063aa39dce4df335d26b9528"
   integrity sha512-j3xnuQtOtjvjAoUMJgSUFxRa9/Egkg1RyA8r6PjcEb33VksE4LWLBy0PNFUFehLZv48595JROTcViGeXXwg5HQ==
 
-"@skalenetwork/ima-interfaces@1.0.0-develop.14":
-  version "1.0.0-develop.14"
-  resolved "https://registry.yarnpkg.com/@skalenetwork/ima-interfaces/-/ima-interfaces-1.0.0-develop.14.tgz#51efd2fa0ed3bc12c2a9a0e3a97dfff8dd98a9c8"
-  integrity sha512-R3rJl1u5c9miAFbE07YjII9uRZNZAVT7BaeKgMer69plVRnIf3Sd9NLth8K/e9ATLIjb7V0tk7mz32E3ptF+Ow==
+"@skalenetwork/ima-interfaces@1.0.0-develop.16":
+  version "1.0.0-develop.16"
+  resolved "https://registry.yarnpkg.com/@skalenetwork/ima-interfaces/-/ima-interfaces-1.0.0-develop.16.tgz#9f79790c6e7eb960498f6930e978705f4ee60cc2"
+  integrity sha512-lsNonWKt3BLUlS7tk+w1Yp1o/07Bjj9Hr22zt0dzAqvRhCAB8ITBEeHlysXeQs60lE+bBqvmU/SRMdmMF/kliA==
   dependencies:
     "@skalenetwork/skale-manager-interfaces" "^0.1.2"
 
+"@skalenetwork/skale-manager-interfaces@1.0.0-develop.1":
+  version "1.0.0-develop.1"
+  resolved "https://registry.yarnpkg.com/@skalenetwork/skale-manager-interfaces/-/skale-manager-interfaces-1.0.0-develop.1.tgz#8a2d06872dcdc235c041448efc454c0d5f1e55cf"
+  integrity sha512-ZOSWEqvz76Ez5cuWE8n9+j/tnTVQg+t7vaZar/XME0hxnM1YwNFqy34nqjEC/SMhFXg/WnxdOVX9JWWXVZRlkQ==
+
 "@skalenetwork/skale-manager-interfaces@^0.1.2":
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/@skalenetwork/skale-manager-interfaces/-/skale-manager-interfaces-0.1.2.tgz#88e543c8cc298cd0cc9559892d746d2d74786da8"


Test patch:
diff --git a/proxy/contracts/test/MessagesTester.sol b/proxy/contracts/test/MessagesTester.sol
index 233422367..3fcd9877e 100644
--- a/proxy/contracts/test/MessagesTester.sol
+++ b/proxy/contracts/test/MessagesTester.sol
@@ -56,6 +56,19 @@ interface IMessagesTester {
         uint256 tokenId,
         Messages.Erc721TokenInfo memory tokenInfo
     ) external pure returns (bytes memory);
+    function encodeTransferErc721MessageWithMetadata(
+        address token,
+        address receiver,
+        uint256 tokenId,
+        string memory tokenURI
+    ) external pure returns (bytes memory);
+    function encodeTransferErc721WithMetadataAndTokenInfoMessage(
+        address token,
+        address receiver,
+        uint256 tokenId,
+        string memory tokenURI,
+        Messages.Erc721TokenInfo memory tokenInfo
+    ) external pure returns (bytes memory);
     function encodeActivateUserMessage(address receiver) external pure returns (bytes memory);
     function encodeLockUserMessage(address receiver) external pure returns (bytes memory);
     function encodeInterchainConnectionMessage(bool isAllowed) external pure returns (bytes memory);
@@ -138,6 +151,31 @@ contract MessagesTester is IMessagesTester {
         return Messages.encodeTransferErc721AndTokenInfoMessage(token, receiver, tokenId, tokenInfo);
     }
 
+    function encodeTransferErc721MessageWithMetadata(
+        address token,
+        address receiver,
+        uint256 tokenId,
+        string memory tokenURI
+    ) external pure override returns (bytes memory) {
+        return Messages.encodeTransferErc721MessageWithMetadata(token, receiver, tokenId, tokenURI);
+    }
+
+    function encodeTransferErc721WithMetadataAndTokenInfoMessage(
+        address token,
+        address receiver,
+        uint256 tokenId,
+        string memory tokenURI,
+        Messages.Erc721TokenInfo memory tokenInfo
+    ) external pure override returns (bytes memory) {
+        return Messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+            token,
+            receiver,
+            tokenId,
+            tokenURI,
+            tokenInfo
+        );
+    }
+
     function encodeActivateUserMessage(address receiver) external pure override returns (bytes memory) {
         return Messages.encodeActivateUserMessage(receiver);
     }
diff --git a/proxy/contracts/test/ReceiverGasLimitMainnetMock.sol b/proxy/contracts/test/ReceiverGasLimitMainnetMock.sol
index af782d52c..eed7d733e 100644
--- a/proxy/contracts/test/ReceiverGasLimitMainnetMock.sol
+++ b/proxy/contracts/test/ReceiverGasLimitMainnetMock.sol
@@ -34,11 +34,9 @@ contract ReceiverGasLimitMainnetMock is IMessageReceiver {
     )
         external
         override
-        returns (address) 
     {
         while(true) {
             a++;
         }
-        return address(0);
     }
 }
\ No newline at end of file
diff --git a/proxy/contracts/test/ReceiverGasLimitSchainMock.sol b/proxy/contracts/test/ReceiverGasLimitSchainMock.sol
index a7f4e11e4..65b8d6eb4 100644
--- a/proxy/contracts/test/ReceiverGasLimitSchainMock.sol
+++ b/proxy/contracts/test/ReceiverGasLimitSchainMock.sol
@@ -34,11 +34,9 @@ contract ReceiverGasLimitSchainMock is IMessageReceiver {
     )
         external
         override
-        returns (address) 
     {
         while(true) {
             a++;
         }
-        return address(0);
     }
 }
\ No newline at end of file
diff --git a/proxy/contracts/test/ReceiverMock.sol b/proxy/contracts/test/ReceiverMock.sol
index 9dd8e2424..1c93bf76c 100644
--- a/proxy/contracts/test/ReceiverMock.sol
+++ b/proxy/contracts/test/ReceiverMock.sol
@@ -34,8 +34,7 @@ contract ReceiverMock is IMessageReceiver {
         external
         pure
         override
-        returns (address) 
     {
-        return address(0);
+        return;
     }
 }
diff --git a/proxy/contracts/test/TestCallReceiverContract.sol b/proxy/contracts/test/TestCallReceiverContract.sol
new file mode 100644
index 000000000..d8570351e
--- /dev/null
+++ b/proxy/contracts/test/TestCallReceiverContract.sol
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ *   ReceiverMock.sol - SKALE Interchain Messaging Agent
+ *   Copyright (C) 2021-Present SKALE Labs
+ *   @author Dmytro Stebaiev
+ *
+ *   SKALE IMA is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Affero General Public License as published
+ *   by the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SKALE IMA is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Affero General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public License
+ *   along with SKALE IMA.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+pragma solidity 0.8.6;
+
+import "@skalenetwork/ima-interfaces/IMessageReceiver.sol";
+
+
+contract TestCallReceiverContract is IMessageReceiver {
+    event Error(uint error);
+
+    function postMessage(
+        bytes32,
+        address,
+        bytes calldata data
+    )
+        external
+        override
+    {
+        uint revertCode = abi.decode(data, (uint));
+        uint one = 1;
+        uint zero = 0;
+        if (revertCode == 1) {
+            revert("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+        } else if (revertCode == 2) {
+            emit Error(one / zero);
+        }
+    }
+}
\ No newline at end of file
diff --git a/proxy/contracts/test/TestContractManager.sol b/proxy/contracts/test/TestContractManager.sol
index 477f28ad2..48273b421 100644
--- a/proxy/contracts/test/TestContractManager.sol
+++ b/proxy/contracts/test/TestContractManager.sol
@@ -22,10 +22,13 @@
 pragma solidity 0.8.6;
 
 import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
-import "@skalenetwork/skale-manager-interfaces/IContractManager.sol";
 
+interface IContractManagerTester {
+    function setContractsAddress(string calldata contractsName, address newContractsAddress) external;
+    function getContract(string memory contractName) external view returns (address);
+}
 
-contract ContractManager is IContractManager {
+contract ContractManager is IContractManagerTester {
     using AddressUpgradeable for address;
 
     // mapping of actual smart contracts addresses
diff --git a/proxy/contracts/test/TestSchains.sol b/proxy/contracts/test/TestSchains.sol
index f4b7f6cf2..25b708c49 100644
--- a/proxy/contracts/test/TestSchains.sol
+++ b/proxy/contracts/test/TestSchains.sol
@@ -22,15 +22,25 @@
 
 pragma solidity 0.8.6;
 
-import "@skalenetwork/skale-manager-interfaces/ISchains.sol";
-
 import "./TestContractManager.sol";
 import "./KeyStorageMock.sol";
 import "./SkaleVerifierMock.sol";
 
 
-interface ISchainsTester is ISchains {
+interface ISchainsTester {
     function addContractManager(address newContractManager) external;
+        function verifySchainSignature(
+        uint signatureA,
+        uint signatureB,
+        bytes32 hash,
+        uint counter,
+        uint hashA,
+        uint hashB,
+        string calldata schainName
+    )
+        external
+        view
+        returns (bool);
 }
 
 
diff --git a/proxy/contracts/test/TestSchainsInternal.sol b/proxy/contracts/test/TestSchainsInternal.sol
index 70258ce0b..bba5e9645 100644
--- a/proxy/contracts/test/TestSchainsInternal.sol
+++ b/proxy/contracts/test/TestSchainsInternal.sol
@@ -22,13 +22,11 @@
 
 pragma solidity 0.8.6;
 
-import "@skalenetwork/skale-manager-interfaces/ISchainsInternal.sol";
-
 import "./TestContractManager.sol";
 import "./TestNodes.sol";
 
 
-interface ISchainsInternalTester is ISchainsInternal {
+interface ISchainsInternalTester {
     function addContractManager(address newContractManager) external;
     function initializeSchain(
         string calldata name,
@@ -36,6 +34,9 @@ interface ISchainsInternalTester is ISchainsInternal {
         uint lifetime,
         uint deposit) external;
     function addNodesToSchainsGroups(bytes32 schainHash, uint[] memory nodes) external;
+    function isNodeAddressesInGroup(bytes32 schainHash, address sender) external view returns (bool);
+    function isOwnerAddress(address from, bytes32 schainHash) external view returns (bool);
+    function isSchainExist(bytes32 schainHash) external view returns (bool);
 }
 
 
@@ -99,4 +100,8 @@ contract SchainsInternal is ISchainsInternalTester {
     function isOwnerAddress(address from, bytes32 schainHash) external view override returns (bool) {
         return schains[schainHash].owner == from;
     }
+
+    function isSchainExist(bytes32) external view override returns (bool) {
+        return true;
+    }
 }
diff --git a/proxy/contracts/test/TestWallets.sol b/proxy/contracts/test/TestWallets.sol
index 61c14b220..73abc434f 100644
--- a/proxy/contracts/test/TestWallets.sol
+++ b/proxy/contracts/test/TestWallets.sol
@@ -21,12 +21,18 @@
 
 pragma solidity 0.8.6;
 
-import "@skalenetwork/skale-manager-interfaces/IWallets.sol";
-
 import "./TestSchainsInternal.sol";
 
-interface IWalletsTester is IWallets {
+interface IWalletsTester {
     function addContractManager(address newContractManager) external;
+     function refundGasBySchain(
+        bytes32 schainHash,
+        address payable spender,
+        uint spentGas,
+        bool
+    ) external;
+    function rechargeSchainWallet(bytes32 schainHash) external payable;
+    function getSchainBalance(bytes32 schainHash) external view returns (uint);
 }
 
 
diff --git a/proxy/predeployed/test/config.json b/proxy/predeployed/test/config.json
index bfadd5bd0..8d72fd8c3 100644
--- a/proxy/predeployed/test/config.json
+++ b/proxy/predeployed/test/config.json
@@ -6,5 +6,6 @@
     "erc721_deposit_box": "0xd200000000000000000000000000000000000003",
     "erc1155_deposit_box": "0xD200000000000000000000000000000000000004",
     "linker": "0xd200000000000000000000000000000000000005",
-    "community_pool": "0xD200000000000000000000000000000000000006"
+    "community_pool": "0xD200000000000000000000000000000000000006",
+    "erc721_with_metadata_deposit_box": "0xd200000000000000000000000000000000000007"
 }
\ No newline at end of file
diff --git a/proxy/predeployed/test/contracts/token_manager_erc721_with_metadata.py b/proxy/predeployed/test/contracts/token_manager_erc721_with_metadata.py
new file mode 100644
index 000000000..d615e82cf
--- /dev/null
+++ b/proxy/predeployed/test/contracts/token_manager_erc721_with_metadata.py
@@ -0,0 +1,27 @@
+from ima_predeployed.addresses import MESSAGE_PROXY_FOR_SCHAIN_ADDRESS, \
+    TOKEN_MANAGER_LINKER_ADDRESS, COMMUNITY_LOCKER_ADDRESS, TOKEN_MANAGER_ERC721_WITH_METADATA_ADDRESS
+from ima_predeployed.contracts.token_manager_erc721_with_metadata import TokenManagerErc721WithMetadataGenerator
+from tools import w3, load_abi
+
+
+def check_token_manager_erc721_with_metadata(deployer_address, deposit_box_address, schain_name):
+    token_manager_erc721_with_metadata = w3.eth.contract(
+        address=TOKEN_MANAGER_ERC721_WITH_METADATA_ADDRESS, abi=load_abi(TokenManagerErc721WithMetadataGenerator.ARTIFACT_FILENAME))
+    if not token_manager_erc721_with_metadata.functions.getRoleMember(
+        TokenManagerErc721WithMetadataGenerator.DEFAULT_ADMIN_ROLE, 0).call() == deployer_address: raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.hasRole(
+        TokenManagerErc721WithMetadataGenerator.DEFAULT_ADMIN_ROLE, deployer_address).call(): raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.getRoleMember(
+        TokenManagerErc721WithMetadataGenerator.AUTOMATIC_DEPLOY_ROLE, 0).call() == deployer_address: raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.hasRole(
+        TokenManagerErc721WithMetadataGenerator.AUTOMATIC_DEPLOY_ROLE, deployer_address).call(): raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.getRoleMember(
+        TokenManagerErc721WithMetadataGenerator.TOKEN_REGISTRAR_ROLE, 0).call() == deployer_address: raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.hasRole(
+        TokenManagerErc721WithMetadataGenerator.TOKEN_REGISTRAR_ROLE, deployer_address).call(): raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.messageProxy().call() == MESSAGE_PROXY_FOR_SCHAIN_ADDRESS: raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.tokenManagerLinker().call() == TOKEN_MANAGER_LINKER_ADDRESS: raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.communityLocker().call() == COMMUNITY_LOCKER_ADDRESS: raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.schainHash().call() == w3.solidityKeccak(['string'], [schain_name]): raise AssertionError
+    if not token_manager_erc721_with_metadata.functions.depositBox().call() == deposit_box_address: raise AssertionError
+    if token_manager_erc721_with_metadata.functions.automaticDeploy().call(): raise AssertionError
diff --git a/proxy/predeployed/test/generate_genesis.py b/proxy/predeployed/test/generate_genesis.py
index 50f410ef1..f4e504f30 100755
--- a/proxy/predeployed/test/generate_genesis.py
+++ b/proxy/predeployed/test/generate_genesis.py
@@ -28,7 +28,8 @@ def main():
                     'deposit_box_erc721_address': config['erc721_deposit_box'],
                     'deposit_box_erc1155_address': config['erc1155_deposit_box'],
                     'linker_address': config['linker'],
-                    'community_pool_address': config['community_pool']
+                    'community_pool_address': config['community_pool'],
+                    'deposit_box_erc721_with_metadata_address': config['erc721_with_metadata_deposit_box']
                 }))
             print(json.dumps(genesis, indent=4, sort_keys=True))
 
diff --git a/proxy/predeployed/test/prepare_environment.sh b/proxy/predeployed/test/prepare_environment.sh
index 12af5eb02..7a19160f0 100755
--- a/proxy/predeployed/test/prepare_environment.sh
+++ b/proxy/predeployed/test/prepare_environment.sh
@@ -8,6 +8,7 @@ python3 -m venv test/venv
 source test/venv/bin/activate
 pip install --force-reinstall dist/ima_predeployed-*.whl
 BLOCKCHAIN_DIR="/tmp/blockchain/"
+#BLOCKCHAIN_DIR="$(pwd)/blockchain/"
 python test/generate_genesis.py test/base_genesis.json test/config.json > test/genesis.json
 rm -r "$BLOCKCHAIN_DIR" || true
 mkdir "$BLOCKCHAIN_DIR"
diff --git a/proxy/predeployed/test/test.py b/proxy/predeployed/test/test.py
index 5b79b3a6a..8703a2c51 100644
--- a/proxy/predeployed/test/test.py
+++ b/proxy/predeployed/test/test.py
@@ -7,6 +7,7 @@
 from contracts.token_manager_erc20 import check_token_manager_erc20
 from contracts.token_manager_erc721 import check_token_manager_erc721
 from contracts.token_manager_erc1155 import check_token_manager_erc1155
+from contracts.token_manager_erc721_with_metadata import check_token_manager_erc721_with_metadata
 from contracts.token_manager_eth import check_token_manager_eth
 from contracts.token_manager_linker import check_token_manager_linker
 import json
@@ -22,6 +23,7 @@
     erc1155_deposit_box = config['erc1155_deposit_box']
     linker_address = config['linker']
     community_pool = config['community_pool']
+    erc721_with_metadata_deposit_box = config['erc721_with_metadata_deposit_box']
 
 
 def main():
@@ -35,6 +37,7 @@ def main():
     check_token_manager_erc20(owner_address, erc20_deposit_box, schain_name)
     check_token_manager_erc721(owner_address, erc721_deposit_box, schain_name)
     check_token_manager_erc1155(owner_address, erc1155_deposit_box, schain_name)
+    check_token_manager_erc721_with_metadata(owner_address, erc721_with_metadata_deposit_box, schain_name)
     check_eth_erc20(owner_address)
 
     print('All tests pass')
diff --git a/proxy/predeployed/test/test.sh b/proxy/predeployed/test/test.sh
index 207e30d50..ed01cba13 100755
--- a/proxy/predeployed/test/test.sh
+++ b/proxy/predeployed/test/test.sh
@@ -4,9 +4,21 @@ set -e
 
 cd "$(dirname "$0")"
 
-geth --datadir /tmp/blockchain/ --dev --http &
+killall -9 geth
+echo "--- Checking there are no old geth instances are still running..."
+ps -A | grep geth
+
+BLOCKCHAIN_DIR="/tmp/blockchain/"
+#BLOCKCHAIN_DIR="$(pwd)/blockchain/"
+
+geth --datadir $BLOCKCHAIN_DIR --dev --http &
 GETH_PID=$!
-sleep 3
+echo --- GETH_PID=$GETH_PID
+echo "--- Sleeping to let new geth instance geth start..."
+sleep 20
+echo "--- Checking new geth instance geth is running..."
+ps -lax | grep geth
+echo "--- Done"
 
 source venv/bin/activate
 pip install -r requirements.txt
diff --git a/proxy/scripts/test_upgrade.sh b/proxy/scripts/test_upgrade.sh
index 3d086b110..efbc68560 100644
--- a/proxy/scripts/test_upgrade.sh
+++ b/proxy/scripts/test_upgrade.sh
@@ -1,6 +1,7 @@
 #!/usr/bin/env bash
 
 DEPLOYED_TAG="$(cat "$GITHUB_WORKSPACE"/proxy/DEPLOYED)"
+VERSION_TAG="$(cat "$GITHUB_WORKSPACE"/VERSION)"
 DEPLOYED_VERSION="$(echo "$DEPLOYED_TAG" | cut -d '-' -f 1)"
 DEPLOYED_DIR=$GITHUB_WORKSPACE/deployed-proxy/
 
@@ -39,6 +40,10 @@ cd proxy
 
 ABI="data/$ABI_FILENAME_MAINNET" TEST_UPGRADE=true npx hardhat run migrations/upgradeMainnet.ts --network localhost || exit $?
 
-ABI="data/$ABI_FILENAME_SCHAIN" MANIFEST="data/ima-schain-$DEPLOYED_VERSION-manifest.json" npx hardhat run migrations/upgradeSchain.ts --network localhost || exit $?
+VERSION="$(git describe --tags | echo "$VERSION_TAG")"
+echo "$VERSION"
+mv "data/proxyMainnet-$VERSION-localhost-abi.json" "data/proxyMainnet.json"
+
+ABI="data/$ABI_FILENAME_SCHAIN" MANIFEST="data/ima-schain-$DEPLOYED_VERSION-manifest.json" CHAIN_NAME_SCHAIN="Test" npx hardhat run migrations/upgradeSchain.ts --network localhost || exit $?
 
 kill "$GANACHE_PID"
diff --git a/proxy/test/DepositBoxERC721WithMetadata.ts b/proxy/test/DepositBoxERC721WithMetadata.ts
new file mode 100644
index 000000000..3304f30fe
--- /dev/null
+++ b/proxy/test/DepositBoxERC721WithMetadata.ts
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ * @license
+ * SKALE IMA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file DepositBox.spec.ts
+ * @copyright SKALE Labs 2019-Present
+ */
+
+import chaiAsPromised from "chai-as-promised";
+// import chaiAlmost from "chai-almost";
+import {
+    ContractManager,
+    DepositBoxERC721WithMetadata,
+    ERC721OnChain,
+    Linker,
+    MessageProxyForMainnet,
+    MessagesTester,
+    CommunityPool
+} from "../typechain";
+import { randomString, stringFromHex, stringValue } from "./utils/helper";
+
+import chai = require("chai");
+import chaiAlmost = require("chai-almost");
+
+chai.should();
+chai.use((chaiAsPromised as any));
+chai.use(chaiAlmost(0.002));
+
+import { deployDepositBoxERC721WithMetadata } from "./utils/deploy/mainnet/depositBoxERC721WithMetadata";
+import { deployLinker } from "./utils/deploy/mainnet/linker";
+import { deployMessageProxyForMainnet } from "./utils/deploy/mainnet/messageProxyForMainnet";
+import { deployContractManager } from "./utils/skale-manager-utils/contractManager";
+import { initializeSchain } from "./utils/skale-manager-utils/schainsInternal";
+import { rechargeSchainWallet } from "./utils/skale-manager-utils/wallets";
+import { setCommonPublicKey } from "./utils/skale-manager-utils/keyStorage";
+import { deployMessages } from "./utils/deploy/messages";
+import { deployERC721OnChain } from "./utils/deploy/erc721OnChain";
+import { deployCommunityPool } from "./utils/deploy/mainnet/communityPool";
+
+import { ethers, web3 } from "hardhat";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
+import { BigNumber } from "ethers";
+
+import { assert, expect } from "chai";
+
+const BlsSignature: [BigNumber, BigNumber] = [
+    BigNumber.from("178325537405109593276798394634841698946852714038246117383766698579865918287"),
+    BigNumber.from("493565443574555904019191451171395204672818649274520396086461475162723833781")
+];
+const HashA = "3080491942974172654518861600747466851589809241462384879086673256057179400078";
+const HashB = "15163860114293529009901628456926790077787470245128337652112878212941459329347";
+const Counter = 0;
+
+async function getBalance(address: string) {
+    return parseFloat(web3.utils.fromWei(await web3.eth.getBalance(address)));
+}
+describe("DepositBoxERC721WithMetadata", () => {
+    let deployer: SignerWithAddress;
+    let user: SignerWithAddress;
+    let user2: SignerWithAddress;
+
+    let depositBoxERC721WithMetadata: DepositBoxERC721WithMetadata;
+    let contractManager: ContractManager;
+    let messageProxy: MessageProxyForMainnet;
+    let linker: Linker;
+    let communityPool: CommunityPool;
+    const contractManagerAddress = "0x0000000000000000000000000000000000000000";
+    const schainName = "Schain";
+
+    before(async () => {
+        [deployer, user, user2] = await ethers.getSigners();
+    });
+
+    beforeEach(async () => {
+        contractManager = await deployContractManager(contractManagerAddress);
+        messageProxy = await deployMessageProxyForMainnet(contractManager);
+        linker = await deployLinker(contractManager, messageProxy);
+        depositBoxERC721WithMetadata = await deployDepositBoxERC721WithMetadata(contractManager, linker, messageProxy);
+        communityPool = await deployCommunityPool(contractManager, linker, messageProxy);
+        await messageProxy.grantRole(await messageProxy.CHAIN_CONNECTOR_ROLE(), linker.address);
+        await messageProxy.grantRole(await messageProxy.EXTRA_CONTRACT_REGISTRAR_ROLE(), deployer.address);
+        await initializeSchain(contractManager, schainName, user.address, 1, 1);
+        await rechargeSchainWallet(contractManager, schainName, user2.address, "1000000000000000000");
+        await messageProxy.registerExtraContractForAll(depositBoxERC721WithMetadata.address);
+        await messageProxy.registerExtraContract(schainName, communityPool.address);
+        await messageProxy.registerExtraContract(schainName, linker.address);
+    });
+
+    describe("tests with `ERC721`", async () => {
+        let erc721: ERC721OnChain;
+        let erc721OnChain: ERC721OnChain;
+
+        beforeEach(async () => {
+            erc721 = await deployERC721OnChain("ERC721OnChain", "ERC721");
+            erc721OnChain = await deployERC721OnChain("ERC721OnChain", "ERC721");
+
+            // mint some ERC721 of  for `deployer` address
+            const tokenId = 10;
+            const tokenURI = "Hello10";
+            await erc721OnChain.connect(deployer).mint(deployer.address, tokenId);
+            await erc721OnChain.connect(deployer).setTokenURI(tokenId, tokenURI);
+            const tokenId2 = 11;
+            const tokenURI2 = "Hello11";
+            await erc721OnChain.connect(deployer).mint(deployer.address, tokenId2);
+            await erc721OnChain.connect(deployer).setTokenURI(tokenId2, tokenURI2);
+
+        });
+
+        describe("tests for `depositERC721` function", async () => {
+
+            it("should rejected with `Unconnected chain`", async () => {
+                //  preparation
+                const amount = 10;
+
+                await depositBoxERC721WithMetadata.connect(user).depositERC721(schainName, erc721.address, amount)
+                    .should.be.eventually.rejectedWith("Unconnected chain");
+            });
+
+            it("should rejected with `DepositBox was not approved for ERC721 token`", async () => {
+                // preparation
+                const error = "DepositBox was not approved for ERC721 token";
+                const contractHere = erc721OnChain.address;
+                const to = user.address;
+                const tokenId = 10;
+                // the wei should be MORE than (55000 * 1000000000)
+                // GAS_AMOUNT_POST_MESSAGE * AVERAGE_TX_PRICE constants in DepositBox.sol
+                const wei = "20000000000000000";
+                // add schain to avoid the `Unconnected chain` error
+                await linker
+                    .connect(deployer)
+                    .connectSchain(schainName, [deployer.address, deployer.address, deployer.address]);
+
+                // execution/expectation
+                await depositBoxERC721WithMetadata
+                    .connect(deployer)
+                    .depositERC721(schainName, contractHere, tokenId)
+                    .should.be.eventually.rejectedWith(error);
+            });
+
+            it("should invoke `depositERC721` without mistakes", async () => {
+                // preparation
+                const contractHere = erc721OnChain.address;
+                const to = user.address;
+                const tokenId = 10;
+                const tokenId2 = 11;
+                // the wei should be MORE than (55000 * 1000000000)
+                // GAS_AMOUNT_POST_MESSAGE * AVERAGE_TX_PRICE constants in DepositBox.sol
+                // add schain to avoid the `Unconnected chain` error
+                await linker
+                    .connect(deployer)
+                    .connectSchain(schainName, [deployer.address, deployer.address, deployer.address]);
+                // transfer tokenId from `deployer` to `depositBoxERC721WithMetadata`
+                await erc721OnChain.connect(deployer).approve(depositBoxERC721WithMetadata.address, tokenId);
+                await erc721OnChain.connect(deployer).approve(depositBoxERC721WithMetadata.address, tokenId2);
+                // execution
+                await depositBoxERC721WithMetadata
+                    .connect(deployer)
+                    .depositERC721(schainName, contractHere, tokenId).should.be.eventually.rejectedWith("Whitelist is enabled");
+                await depositBoxERC721WithMetadata.connect(user).disableWhitelist(schainName);
+                await depositBoxERC721WithMetadata
+                    .connect(deployer)
+                    .depositERC721(schainName, contractHere, tokenId);
+                await (await depositBoxERC721WithMetadata
+                    .connect(deployer)
+                    .depositERC721(schainName, contractHere, tokenId2)).wait();
+                // console.log("Gas for depositERC721:", res.receipt.gasUsed);
+                // expectation
+                expect(await erc721OnChain.ownerOf(tokenId)).to.equal(depositBoxERC721WithMetadata.address);
+                expect(await erc721OnChain.ownerOf(tokenId2)).to.equal(depositBoxERC721WithMetadata.address);
+            });
+
+        });
+
+        it("should get funds after kill", async () => {
+            const tokenId = 10;
+            const tokenId2 = 11;
+            await linker
+                .connect(deployer)
+                .connectSchain(schainName, [deployer.address, deployer.address, deployer.address]);
+            await erc721OnChain.connect(deployer).approve(depositBoxERC721WithMetadata.address, tokenId);
+            await depositBoxERC721WithMetadata.connect(user).disableWhitelist(schainName);
+            await depositBoxERC721WithMetadata
+                .connect(deployer)
+                .depositERC721(schainName, erc721OnChain.address, tokenId);
+            await depositBoxERC721WithMetadata.connect(user).getFunds(schainName, erc721OnChain.address, user.address, tokenId).should.be.eventually.rejectedWith("Schain is not killed");
+            await linker.connect(deployer).kill(schainName);
+            await linker.connect(user).kill(schainName);
+            await depositBoxERC721WithMetadata.connect(user).getFunds(schainName, erc721OnChain.address, user.address, tokenId2).should.be.eventually.rejectedWith("Incorrect tokenId");
+            await depositBoxERC721WithMetadata.connect(user).getFunds(schainName, erc721OnChain.address, user.address, tokenId);
+            expect(await erc721OnChain.ownerOf(tokenId)).to.equal(user.address);
+        });
+
+        it("should add erc721 token by schain owner", async () => {
+            const fakeERC721Contract = deployer.address;
+            await depositBoxERC721WithMetadata.connect(user).addERC721TokenByOwner(schainName, fakeERC721Contract)
+                .should.be.eventually.rejectedWith("Given address is not a contract");
+            await depositBoxERC721WithMetadata.connect(deployer).addERC721TokenByOwner(schainName, erc721.address)
+                .should.be.eventually.rejectedWith("Sender is not an Schain owner");
+
+            await depositBoxERC721WithMetadata.connect(user).addERC721TokenByOwner(schainName, erc721.address);
+            await depositBoxERC721WithMetadata.connect(user).addERC721TokenByOwner(schainName, erc721.address).should.be.eventually.rejectedWith("ERC721 Token was already added");
+            expect(await depositBoxERC721WithMetadata.getSchainToERC721(schainName, erc721.address)).to.be.equal(true);
+            expect((await depositBoxERC721WithMetadata.getSchainToAllERC721(schainName, 0, 1))[0]).to.be.equal(erc721.address);
+            expect((await depositBoxERC721WithMetadata.getSchainToAllERC721(schainName, 0, 1)).length).to.be.equal(1);
+            expect((await depositBoxERC721WithMetadata.getSchainToAllERC721Length(schainName)).toString()).to.be.equal("1");
+            await depositBoxERC721WithMetadata.getSchainToAllERC721(schainName, 1, 0).should.be.eventually.rejectedWith("Range is incorrect");
+            await depositBoxERC721WithMetadata.getSchainToAllERC721(schainName, 0, 11).should.be.eventually.rejectedWith("Range is incorrect");
+        });
+    });
+
+    describe("tests for `postMessage` function", async () => {
+        let erc721: ERC721OnChain;
+        let erc721OnChain: ERC721OnChain;
+        let messages: MessagesTester;
+        let weiAmount: string;
+        let sign: {
+            blsSignature: [BigNumber, BigNumber],
+            counter: number,
+            hashA: string,
+            hashB: string,
+        };
+
+        beforeEach(async () => {
+            weiAmount = 1e18.toString();
+            erc721 = await deployERC721OnChain("ERC721", "ERC721");
+            erc721OnChain = await deployERC721OnChain("ERC721OnChain", "ERC721OnChain");
+            messages = await deployMessages();
+
+            sign = {
+                blsSignature: BlsSignature,
+                counter: Counter,
+                hashA: HashA,
+                hashB: HashB,
+            };
+
+            await setCommonPublicKey(contractManager, schainName);
+            await depositBoxERC721WithMetadata.connect(user).disableWhitelist(schainName);
+
+            await linker
+                .connect(deployer)
+                .connectSchain(schainName, [deployer.address, deployer.address, deployer.address]);
+            await communityPool
+                .connect(user)
+                .rechargeUserWallet(schainName, user.address, { value: weiAmount });
+        });
+
+        it("should transfer ERC721 token", async () => {
+            //  preparation
+            const tokenId = 10;
+            const tokenURI = "Hello10";
+            const to = user.address;
+            const to0 = "0x0000000000000000000000000000000000000000";
+            const senderFromSchain = deployer.address;
+
+
+
+            const message = {
+                data: await messages.encodeTransferErc721MessageWithMetadata(erc721.address, to, tokenId, tokenURI),
+                destinationContract: depositBoxERC721WithMetadata.address,
+                sender: senderFromSchain
+            };
+
+            await erc721.connect(deployer).mint(deployer.address, tokenId);
+            await erc721.connect(deployer).setTokenURI(tokenId, tokenURI);
+            await erc721.connect(deployer).transferFrom(deployer.address, depositBoxERC721WithMetadata.address, tokenId);
+
+            const balanceBefore = await getBalance(deployer.address);
+            await messageProxy.connect(deployer).postIncomingMessages(schainName, 0, [message], sign);
+            const balance = await getBalance(deployer.address);
+            balance.should.not.be.lessThan(balanceBefore);
+            balance.should.be.almost(balanceBefore);
+
+            (await erc721.ownerOf(tokenId)).should.be.equal(user.address);
+            (await erc721.tokenURI(tokenId)).should.be.equal(tokenURI);
+        });
+
+        it("should revert `Given address is not a contract`", async () => {
+            //  preparation
+            const tokenId = 10;
+            const tokenURI = "Hello10";
+            const to = user.address;
+            const senderFromSchain = deployer.address;
+
+            const messageWithWrongTokenAddress = {
+                data: await messages.encodeTransferErc721MessageWithMetadata(user2.address, to, tokenId, tokenURI),
+                destinationContract: depositBoxERC721WithMetadata.address,
+                sender: senderFromSchain
+            };
+
+            await erc721.connect(deployer).mint(deployer.address, tokenId);
+            await erc721.connect(deployer).setTokenURI(tokenId, tokenURI);
+            await erc721.connect(deployer).transferFrom(deployer.address, depositBoxERC721WithMetadata.address, tokenId);
+
+            const res = await (await messageProxy.connect(deployer).postIncomingMessages(schainName, 0, [messageWithWrongTokenAddress], sign)).wait();
+            if (res.events) {
+                assert.equal(res.events[0].event, "PostMessageError");
+                assert.equal(stringFromHex(res.events[0].args?.message), "Given address is not a contract");
+            } else {
+                assert(false, "No events were emitted");
+            }
+        });
+
+        it("should revert `Incorrect tokenId`", async () => {
+            const tokenId = 10;
+            const tokenURI = "Hello10";
+            const to = user.address;
+            const senderFromSchain = deployer.address;
+
+            const messageWithWrongTokenAddress = {
+                data: await messages.encodeTransferErc721MessageWithMetadata(erc721.address, to, tokenId, tokenURI),
+                destinationContract: depositBoxERC721WithMetadata.address,
+                sender: senderFromSchain
+            };
+
+            await erc721.connect(deployer).mint(deployer.address, tokenId);
+            await erc721.connect(deployer).setTokenURI(tokenId, tokenURI);
+
+            const res = await (await messageProxy.connect(deployer).postIncomingMessages(schainName, 0, [messageWithWrongTokenAddress], sign)).wait();
+            if (res.events) {
+                assert.equal(res.events[0].event, "PostMessageError");
+                assert.equal(stringFromHex(res.events[0].args?.message), "Incorrect tokenId");
+            } else {
+                assert(false, "No events were emitted");
+            }
+        });
+
+        it("should transfer ERC721 token", async () => {
+            //  preparation
+            const tokenId = 10;
+            const tokenURI = "Hello10";
+            const to = user.address;
+            const schainHash = stringValue(web3.utils.soliditySha3(schainName));
+            const zeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000";
+            const senderFromSchain = deployer.address;
+
+            const message = {
+                data: await messages.encodeTransferErc721MessageWithMetadata(erc721.address, to, tokenId, tokenURI),
+                destinationContract: depositBoxERC721WithMetadata.address,
+                sender: senderFromSchain
+            };
+
+            await erc721.mint(deployer.address, tokenId);
+            await erc721.connect(deployer).setTokenURI(tokenId, tokenURI);
+            await erc721.approve(depositBoxERC721WithMetadata.address, tokenId);
+
+            await depositBoxERC721WithMetadata
+                .depositERC721(schainName, erc721.address, tokenId);
+
+            const balanceBefore = await getBalance(deployer.address);
+            await messageProxy.connect(deployer).postIncomingMessages(schainName, 0, [message], sign);
+            const balance = await getBalance(deployer.address);
+            balance.should.not.be.lessThan(balanceBefore);
+            balance.should.be.almost(balanceBefore);
+            expect(await depositBoxERC721WithMetadata.transferredAmount(erc721.address, tokenId)).to.be.equal(zeroHash);
+
+            (await erc721.ownerOf(tokenId)).should.be.equal(user.address);
+            (await erc721.tokenURI(tokenId)).should.be.equal(tokenURI);
+        });
+
+    });
+});
\ No newline at end of file
diff --git a/proxy/test/DepositBoxEth.ts b/proxy/test/DepositBoxEth.ts
index d51a013db..66a5d4ef6 100644
--- a/proxy/test/DepositBoxEth.ts
+++ b/proxy/test/DepositBoxEth.ts
@@ -63,7 +63,7 @@ import { deployFallbackEthTester } from "./utils/deploy/test/fallbackEthTester";
 
 import { ethers, web3 } from "hardhat";
 import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
-import { BigNumber } from "ethers";
+import { BigNumber, ContractTransaction } from "ethers";
 
 import { assert, expect } from "chai";
 
@@ -78,6 +78,29 @@ const Counter = 0;
 async function getBalance(address: string) {
     return parseFloat(web3.utils.fromWei(await web3.eth.getBalance(address)));
 }
+
+const weiTolerance = ethers.utils.parseEther("0.002").toNumber();
+
+async function reimbursed(transaction: ContractTransaction, operation?: string) {
+    const receipt = await transaction.wait();
+    const sender = transaction.from;
+    const balanceBefore = await ethers.provider.getBalance(sender, receipt.blockNumber - 1);
+    const balanceAfter = await ethers.provider.getBalance(sender, receipt.blockNumber);
+    if (balanceAfter.lt(balanceBefore)) {
+        const shortageEth = balanceBefore.sub(balanceAfter);
+        const shortageGas = shortageEth.div(receipt.effectiveGasPrice);
+
+        console.log("Reimbursement failed.")
+        console.log(`${shortageGas.toString()} gas units was not reimbursed`);
+        if (operation !== undefined) {
+            console.log(`During ${operation}`);
+        }
+
+    }
+    balanceAfter.should.be.least(balanceBefore);
+    balanceAfter.should.be.closeTo(balanceBefore, weiTolerance);
+}
+
 describe("DepositBoxEth", () => {
     let deployer: SignerWithAddress;
     let user: SignerWithAddress;
@@ -557,11 +580,7 @@ describe("DepositBoxEth", () => {
 
             expect(BigNumber.from(await depositBoxEth.transferredAmount(schainHash)).toString()).to.be.equal(BigNumber.from(wei).mul(2).toString());
 
-            const balanceBefore = await getBalance(deployer.address);
-            await messageProxy.connect(deployer).postIncomingMessages(schainName, 0, [message], sign);
-            const balance = await getBalance(deployer.address);
-            balance.should.not.be.lessThan(balanceBefore);
-            balance.should.be.almost(balanceBefore);
+            await reimbursed(await messageProxy.connect(deployer).postIncomingMessages(schainName, 0, [message], sign));
 
             expect(BigNumber.from(await depositBoxEth.approveTransfers(fallbackEthTester.address)).toString()).to.equal(BigNumber.from(wei).toString());
 
diff --git a/proxy/test/MessageProxy.ts b/proxy/test/MessageProxy.ts
index acee5990e..77ce09758 100644
--- a/proxy/test/MessageProxy.ts
+++ b/proxy/test/MessageProxy.ts
@@ -40,7 +40,7 @@ import {
     EtherbaseMock,
     SchainsInternal
 } from "../typechain/";
-import { randomString, stringValue } from "./utils/helper";
+import { randomString, stringToHex } from "./utils/helper";
 import ABIReceiverMock = require("../artifacts/contracts/test/ReceiverMock.sol/ReceiverMock.json");
 import { deployLinker } from "./utils/deploy/mainnet/linker";
 import { deployMessageProxyForMainnet } from "./utils/deploy/mainnet/messageProxyForMainnet";
@@ -504,6 +504,97 @@ describe("MessageProxy", () => {
 
         });
 
+        it("should slice revert message", async () => {
+            await initializeSchain(contractManager, schainName, deployer.address, 1, 1);
+            await rechargeSchainWallet(contractManager, schainName, deployer.address, "1000000000000000000");
+            await setCommonPublicKey(contractManager, schainName);
+            await messageProxyForMainnet.connect(deployer).addConnectedChain(schainName);
+
+            const testCallReceiverContract = await ethers.getContractFactory("TestCallReceiverContract");
+            const receiverMock = await testCallReceiverContract.deploy();
+            await messageProxyForMainnet.registerExtraContract(schainName, receiverMock.address);
+
+            const startingCounter = 0;
+            const message1 = {
+                amount: 0,
+                data: ethers.utils.defaultAbiCoder.encode(["uint"], [1]),
+                destinationContract: receiverMock.address,
+                sender: deployer.address,
+                to: client.address
+            };
+
+            const sign = {
+                blsSignature: BlsSignature,
+                counter: Counter,
+                hashA: HashA,
+                hashB: HashB,
+            };
+
+            const sixtyFourTimesA ="0x" + stringToHex("".padStart(64,"A"), null);
+            const event = {
+                msgCounter: BigNumber.from(0),
+                message: sixtyFourTimesA
+            }
+            await expect(
+                messageProxyForMainnet
+                .connect(deployer)
+                .postIncomingMessages(
+                    schainName,
+                    startingCounter,
+                    [message1],
+                    sign
+                ))
+                .to.emit(messageProxyForMainnet, 'PostMessageError')
+                .withArgs(event.msgCounter, event.message);
+
+        });
+
+        it("should return panic error message", async () => {
+            await initializeSchain(contractManager, schainName, deployer.address, 1, 1);
+            await rechargeSchainWallet(contractManager, schainName, deployer.address, "1000000000000000000");
+            await setCommonPublicKey(contractManager, schainName);
+            await messageProxyForMainnet.connect(deployer).addConnectedChain(schainName);
+
+            const testCallReceiverContract = await ethers.getContractFactory("TestCallReceiverContract");
+            const receiverMock = await testCallReceiverContract.deploy();
+            await messageProxyForMainnet.registerExtraContract(schainName, receiverMock.address);
+
+            const startingCounter = 0;
+
+            const message1 = {
+                amount: 0,
+                data: ethers.utils.defaultAbiCoder.encode(["uint"], [2]),
+                destinationContract: receiverMock.address,
+                sender: deployer.address,
+                to: client.address
+            };
+
+            const sign = {
+                blsSignature: BlsSignature,
+                counter: Counter,
+                hashA: HashA,
+                hashB: HashB,
+            };
+
+            const panicErrorCodeDivideByZero = "12";
+            const event = {
+                msgCounter: BigNumber.from(0),
+                message: "0x" + panicErrorCodeDivideByZero.padStart(64, "0")
+            }
+            await expect(
+                messageProxyForMainnet
+                .connect(deployer)
+                .postIncomingMessages(
+                    schainName,
+                    startingCounter,
+                    [message1],
+                    sign
+                ))
+                .to.emit(messageProxyForMainnet, 'PostMessageError')
+                .withArgs(event.msgCounter, event.message);
+        });
+
+
         it("should set version of contracts on mainnet", async () => {
             const version = "1.0.0"
             expect(await messageProxyForMainnet.version()).to.be.equal('');
diff --git a/proxy/test/TokenManagerERC1155.ts b/proxy/test/TokenManagerERC1155.ts
index d09ec6667..18e46ca82 100644
--- a/proxy/test/TokenManagerERC1155.ts
+++ b/proxy/test/TokenManagerERC1155.ts
@@ -977,6 +977,175 @@ describe("TokenManagerERC1155", () => {
 
         });
 
+        it("should not be able to transfer X->Y->Z", async () => {
+            // await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC1155.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc1155OnOriginChain.connect(deployer).mint(user.address, id, amount, "0x");
+            await erc1155OnOriginChain.connect(user).setApprovalForAll(tokenManagerERC1155.address, true);
+
+            await tokenManagerERC1155.addTokenManager(newSchainName, tokenManagerERC11552.address);
+
+            // execution:
+            await tokenManagerERC1155
+                .connect(user)
+                .transferToSchainERC1155(newSchainName, erc1155OnOriginChain.address, id, amount);
+
+            const data = await messages.encodeTransferErc1155AndTokenInfoMessage(
+                erc1155OnOriginChain.address,
+                user.address,
+                id,
+                amount,
+                {
+                    uri: await erc1155OnOriginChain.uri(0)
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC11552.addTokenManager(schainName, tokenManagerERC1155.address);
+
+            await tokenManagerERC11552.connect(deployer).grantRole(await tokenManagerERC11552.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC11552.connect(schainOwner).addERC1155TokenByOwner(schainName,  erc1155OnOriginChain.address, erc1155OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC11552.address, schainId, tokenManagerERC1155.address, data);
+
+            expect((await erc1155OnTargetChain.functions.balanceOf(user.address, id)).toString()).to.be.equal(amount.toString());
+
+            let erc1155OnTargetZChain: ERC1155OnChain;
+            let messageProxyForSchainZ: MessageProxyForSchainTester;
+            let tokenManagerLinkerZ: TokenManagerLinker;
+            let tokenManagerERC1155Z: TokenManagerERC1155;
+            let communityLockerZ: CommunityLocker;
+            const newSchainNameZ = "NewChainZ";
+
+            erc1155OnTargetZChain = await deployERC1155OnChain("NewTokenZ");
+
+            const keyStorageZ = await deployKeyStorageMock();
+            messageProxyForSchainZ = await deployMessageProxyForSchainTester(keyStorageZ.address, newSchainNameZ);
+            tokenManagerLinkerZ = await deployTokenManagerLinker(messageProxyForSchainZ, deployer.address);
+            communityLockerZ = await deployCommunityLocker(newSchainName, messageProxyForSchainZ.address, tokenManagerLinkerZ, fakeCommunityPool);
+            tokenManagerERC1155Z = await deployTokenManagerERC1155(newSchainNameZ, messageProxyForSchainZ.address, tokenManagerLinkerZ, communityLockerZ, fakeDepositBox);
+            await erc1155OnTargetZChain.connect(deployer).grantRole(await erc1155OnTargetZChain.MINTER_ROLE(), tokenManagerERC1155Z.address);
+            await tokenManagerLinkerZ.registerTokenManager(tokenManagerERC1155Z.address);
+
+            await messageProxyForSchain2.connect(deployer).grantRole(await messageProxyForSchain2.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(newSchainNameZ);
+
+            await messageProxyForSchain2.registerExtraContract(newSchainNameZ, tokenManagerERC11552.address);
+            await tokenManagerERC11552.addTokenManager(newSchainNameZ, tokenManagerERC1155Z.address);
+
+            await erc1155OnTargetChain.connect(user).setApprovalForAll(tokenManagerERC11552.address, true);
+
+            await tokenManagerERC11552
+                .connect(user)
+                .transferToSchainERC1155(newSchainNameZ, erc1155OnOriginChain.address, id, amount)
+                .should.be.eventually.rejectedWith("Not allowed ERC1155 Token");
+
+            await tokenManagerERC11552
+                .connect(user)
+                .transferToSchainERC1155(newSchainNameZ, erc1155OnTargetChain.address, id, amount)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+        });
+
+        it("should not be able to transfer main chain token or clone to mainnet", async () => {
+            // await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC1155.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc1155OnOriginChain.connect(deployer).mint(user.address, id, amount, "0x");
+            await erc1155OnOriginChain.connect(user).setApprovalForAll(tokenManagerERC1155.address, true);
+
+            await tokenManagerERC1155.addTokenManager(newSchainName, tokenManagerERC11552.address);
+
+            // execution:
+            await tokenManagerERC1155
+                .connect(user)
+                .transferToSchainERC1155(newSchainName, erc1155OnOriginChain.address, id, amount);
+
+            let data = await messages.encodeTransferErc1155AndTokenInfoMessage(
+                erc1155OnOriginChain.address,
+                user.address,
+                id,
+                amount,
+                {
+                    uri: await erc1155OnOriginChain.uri(0)
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC11552.addTokenManager(schainName, tokenManagerERC1155.address);
+
+            await tokenManagerERC11552.connect(deployer).grantRole(await tokenManagerERC11552.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC11552.connect(schainOwner).addERC1155TokenByOwner(schainName,  erc1155OnOriginChain.address, erc1155OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC11552.address, schainId, tokenManagerERC1155.address, data);
+
+            expect((await erc1155OnTargetChain.functions.balanceOf(user.address, id)).toString()).to.be.equal(amount.toString());
+
+            data = await messages.encodeActivateUserMessage(user.address);
+
+            await messageProxyForSchain2.postMessage(communityLocker2.address, mainnetId, fakeCommunityPool, data);
+
+            await erc1155OnTargetChain.connect(user).setApprovalForAll(tokenManagerERC11552.address, true);
+
+            await tokenManagerERC11552
+                .connect(user)
+                .exitToMainERC1155(erc1155OnOriginChain.address, id, amount)
+                .should.be.eventually.rejectedWith("Not allowed ERC1155 Token");
+
+            await tokenManagerERC11552
+                .connect(user)
+                .exitToMainERC1155(erc1155OnTargetChain.address, id, amount)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC11552.address);
+
+            await tokenManagerERC11552
+                .connect(user)
+                .transferToSchainERC1155(schainName, erc1155OnOriginChain.address, id, amount);
+
+            data = await messages.encodeTransferErc1155Message(
+                erc1155OnOriginChain.address,
+                user.address,
+                id,
+                amount
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC1155.address, newSchainId, tokenManagerERC11552.address, data);
+
+            expect((await erc1155OnOriginChain.functions.balanceOf(user.address, id)).toString()).to.be.equal(amount.toString());
+
+            await tokenManagerERC1155
+                .connect(user)
+                .exitToMainERC1155(erc1155OnOriginChain.address, id, amount)
+                .should.be.eventually.rejectedWith("Main chain token could not be transfered to Mainnet");
+
+            await tokenManagerERC1155
+                .connect(user)
+                .exitToMainERC1155(erc1155OnTargetChain.address, id, amount)
+                .should.be.eventually.rejectedWith("Not allowed ERC1155 Token");
+
+        });
+
     });
 
     describe("tests for transferToSchainERC1155Batch", async () => {
@@ -1867,6 +2036,190 @@ describe("TokenManagerERC1155", () => {
 
         });
 
+        it("should not be able to transfer X->Y->Z", async () => {
+            // await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC1155.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc1155OnOriginChain.connect(deployer).mintBatch(user.address, ids, amounts, "0x");
+            await erc1155OnOriginChain.connect(user).setApprovalForAll(tokenManagerERC1155.address, true);
+
+            await tokenManagerERC1155.addTokenManager(newSchainName, tokenManagerERC11552.address);
+
+            // execution:
+            await tokenManagerERC1155
+                .connect(user)
+                .transferToSchainERC1155Batch(newSchainName, erc1155OnOriginChain.address, ids, amounts);
+
+            const data = await messages.encodeTransferErc1155BatchAndTokenInfoMessage(
+                erc1155OnOriginChain.address,
+                user.address,
+                ids,
+                amounts,
+                {
+                    uri: await erc1155OnOriginChain.uri(0)
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC11552.addTokenManager(schainName, tokenManagerERC1155.address);
+
+            await tokenManagerERC11552.connect(deployer).grantRole(await tokenManagerERC11552.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC11552.connect(schainOwner).addERC1155TokenByOwner(schainName,  erc1155OnOriginChain.address, erc1155OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC11552.address, schainId, tokenManagerERC1155.address, data);
+
+            const balanceIds = await erc1155OnTargetChain.balanceOfBatch([user.address, user.address, user.address, user.address], ids);
+            const balanceIdsNumber: number[] = [];
+            balanceIds.forEach((element: any) => {
+                balanceIdsNumber.push(BigNumber.from(element).toNumber())
+            });
+            expect(balanceIdsNumber).to.deep.equal(amounts);
+
+            let erc1155OnTargetZChain: ERC1155OnChain;
+            let messageProxyForSchainZ: MessageProxyForSchainTester;
+            let tokenManagerLinkerZ: TokenManagerLinker;
+            let tokenManagerERC1155Z: TokenManagerERC1155;
+            let communityLockerZ: CommunityLocker;
+            const newSchainNameZ = "NewChainZ";
+
+            erc1155OnTargetZChain = await deployERC1155OnChain("NewTokenZ");
+
+            const keyStorageZ = await deployKeyStorageMock();
+            messageProxyForSchainZ = await deployMessageProxyForSchainTester(keyStorageZ.address, newSchainNameZ);
+            tokenManagerLinkerZ = await deployTokenManagerLinker(messageProxyForSchainZ, deployer.address);
+            communityLockerZ = await deployCommunityLocker(newSchainName, messageProxyForSchainZ.address, tokenManagerLinkerZ, fakeCommunityPool);
+            tokenManagerERC1155Z = await deployTokenManagerERC1155(newSchainNameZ, messageProxyForSchainZ.address, tokenManagerLinkerZ, communityLockerZ, fakeDepositBox);
+            await erc1155OnTargetZChain.connect(deployer).grantRole(await erc1155OnTargetZChain.MINTER_ROLE(), tokenManagerERC1155Z.address);
+            await tokenManagerLinkerZ.registerTokenManager(tokenManagerERC1155Z.address);
+
+            await messageProxyForSchain2.connect(deployer).grantRole(await messageProxyForSchain2.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(newSchainNameZ);
+
+            await messageProxyForSchain2.registerExtraContract(newSchainNameZ, tokenManagerERC11552.address);
+            await tokenManagerERC11552.addTokenManager(newSchainNameZ, tokenManagerERC1155Z.address);
+
+            await erc1155OnTargetChain.connect(user).setApprovalForAll(tokenManagerERC11552.address, true);
+
+            await tokenManagerERC11552
+                .connect(user)
+                .transferToSchainERC1155Batch(newSchainNameZ, erc1155OnOriginChain.address, ids, amounts)
+                .should.be.eventually.rejectedWith("Not allowed ERC1155 Token");
+
+            await tokenManagerERC11552
+                .connect(user)
+                .transferToSchainERC1155Batch(newSchainNameZ, erc1155OnTargetChain.address, ids, amounts)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+        });
+
+        it("should not be able to transfer main chain token or clone to mainnet", async () => {
+            // await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC1155.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc1155OnOriginChain.connect(deployer).mintBatch(user.address, ids, amounts, "0x");
+            await erc1155OnOriginChain.connect(user).setApprovalForAll(tokenManagerERC1155.address, true);
+
+            await tokenManagerERC1155.addTokenManager(newSchainName, tokenManagerERC11552.address);
+
+            // execution:
+            await tokenManagerERC1155
+                .connect(user)
+                .transferToSchainERC1155Batch(newSchainName, erc1155OnOriginChain.address, ids, amounts);
+
+            let data = await messages.encodeTransferErc1155BatchAndTokenInfoMessage(
+                erc1155OnOriginChain.address,
+                user.address,
+                ids,
+                amounts,
+                {
+                    uri: await erc1155OnOriginChain.uri(0)
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC11552.addTokenManager(schainName, tokenManagerERC1155.address);
+
+            await tokenManagerERC11552.connect(deployer).grantRole(await tokenManagerERC11552.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC11552.connect(schainOwner).addERC1155TokenByOwner(schainName,  erc1155OnOriginChain.address, erc1155OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC11552.address, schainId, tokenManagerERC1155.address, data);
+
+            let balanceIds = await erc1155OnTargetChain.balanceOfBatch([user.address, user.address, user.address, user.address], ids);
+            let balanceIdsNumber: number[] = [];
+            balanceIds.forEach((element: any) => {
+                balanceIdsNumber.push(BigNumber.from(element).toNumber())
+            });
+            expect(balanceIdsNumber).to.deep.equal(amounts);
+
+            data = await messages.encodeActivateUserMessage(user.address);
+
+            await messageProxyForSchain2.postMessage(communityLocker2.address, mainnetId, fakeCommunityPool, data);
+
+            await erc1155OnTargetChain.connect(user).setApprovalForAll(tokenManagerERC11552.address, true);
+
+            await tokenManagerERC11552
+                .connect(user)
+                .exitToMainERC1155Batch(erc1155OnOriginChain.address, ids, amounts)
+                .should.be.eventually.rejectedWith("Not allowed ERC1155 Token");
+
+            await tokenManagerERC11552
+                .connect(user)
+                .exitToMainERC1155Batch(erc1155OnTargetChain.address, ids, amounts)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC11552.address);
+
+            await tokenManagerERC11552
+                .connect(user)
+                .transferToSchainERC1155Batch(schainName, erc1155OnOriginChain.address, ids, amounts);
+
+            data = await messages.encodeTransferErc1155BatchMessage(
+                erc1155OnOriginChain.address,
+                user.address,
+                ids,
+                amounts
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC1155.address, newSchainId, tokenManagerERC11552.address, data);
+
+            balanceIds = await erc1155OnOriginChain.balanceOfBatch([user.address, user.address, user.address, user.address], ids);
+            balanceIdsNumber = [];
+            balanceIds.forEach((element: any) => {
+                balanceIdsNumber.push(BigNumber.from(element).toNumber())
+            });
+            expect(balanceIdsNumber).to.deep.equal(amounts);
+
+            await tokenManagerERC1155
+                .connect(user)
+                .exitToMainERC1155Batch(erc1155OnOriginChain.address, ids, amounts)
+                .should.be.eventually.rejectedWith("Main chain token could not be transfered to Mainnet");
+
+            await tokenManagerERC1155
+                .connect(user)
+                .exitToMainERC1155Batch(erc1155OnTargetChain.address, ids, amounts)
+                .should.be.eventually.rejectedWith("Not allowed ERC1155 Token");
+
+        });
+
     });
 
     describe("tests for `postMessage` function", async () => {
diff --git a/proxy/test/TokenManagerERC20.ts b/proxy/test/TokenManagerERC20.ts
index 3b6239b3b..2e327d982 100644
--- a/proxy/test/TokenManagerERC20.ts
+++ b/proxy/test/TokenManagerERC20.ts
@@ -953,7 +953,7 @@ describe("TokenManagerERC20", () => {
             await tokenManagerErc202.addTokenManager(schainName, tokenManagerErc20.address);
 
             await tokenManagerErc202.connect(deployer).grantRole(await tokenManagerErc202.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
-            await tokenManagerErc202.connect(schainOwner).addERC20TokenByOwner(schainName,  erc20OnOriginChain.address, erc20OnTargetChain.address);
+            await tokenManagerErc202.connect(schainOwner).addERC20TokenByOwner(schainName, erc20OnOriginChain.address, erc20OnTargetChain.address);
 
 
             await messageProxyForSchain2.postMessage(tokenManagerErc202.address, schainId, tokenManagerErc20.address, data);
@@ -1067,6 +1067,183 @@ describe("TokenManagerERC20", () => {
 
         });
 
+        it("should not be able to transfer X->Y->Z", async () => {
+            const amount = "20000000000000000";
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerErc20.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc20OnOriginChain.connect(deployer).mint(user.address, amount);
+            await erc20OnOriginChain.connect(user).approve(tokenManagerErc20.address, amount);
+
+            await tokenManagerErc20.addTokenManager(newSchainName, tokenManagerErc202.address);
+
+            // execution:
+            await tokenManagerErc20
+                .connect(user)
+                .transferToSchainERC20(newSchainName, erc20OnOriginChain.address, amount);
+
+            const data = await messages.encodeTransferErc20AndTokenInfoMessage(
+                erc20OnOriginChain.address,
+                user.address,
+                amount,
+                (await erc20OnOriginChain.totalSupply()).toString(),
+                {
+                    name: await erc20OnOriginChain.name(),
+                    symbol: await erc20OnOriginChain.symbol(),
+                    decimals: BigNumber.from(await erc20OnOriginChain.decimals()).toString()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerErc202.addTokenManager(schainName, tokenManagerErc20.address);
+
+            await tokenManagerErc202.connect(deployer).grantRole(await tokenManagerErc202.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerErc202.connect(schainOwner).addERC20TokenByOwner(schainName,  erc20OnOriginChain.address, erc20OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerErc202.address, schainId, tokenManagerErc20.address, data);
+
+            expect((await erc20OnTargetChain.functions.balanceOf(user.address)).toString()).to.be.equal(amount);
+
+            let erc20OnTargetZChain: ERC20OnChain;
+            let messageProxyForSchainZ: MessageProxyForSchainTester;
+            let tokenManagerLinkerZ: TokenManagerLinker;
+            let tokenManagerErc20Z: TokenManagerERC20;
+            let communityLockerZ: CommunityLocker;
+            const newSchainNameZ = "NewChainZ";
+
+            erc20OnTargetZChain = await deployERC20OnChain("NewTokenZ", "NTNZ");
+
+            const keyStorageZ = await deployKeyStorageMock();
+            messageProxyForSchainZ = await deployMessageProxyForSchainTester(keyStorageZ.address, newSchainNameZ);
+            tokenManagerLinkerZ = await deployTokenManagerLinker(messageProxyForSchainZ, deployer.address);
+            communityLockerZ = await deployCommunityLocker(newSchainName, messageProxyForSchainZ.address, tokenManagerLinkerZ, fakeCommunityPool);
+            tokenManagerErc20Z = await deployTokenManagerERC20(newSchainNameZ, messageProxyForSchainZ.address, tokenManagerLinkerZ, communityLockerZ, fakeDepositBox);
+            await erc20OnTargetZChain.connect(deployer).grantRole(await erc20OnTargetZChain.MINTER_ROLE(), tokenManagerErc20Z.address);
+            await tokenManagerLinkerZ.registerTokenManager(tokenManagerErc20Z.address);
+
+            await messageProxyForSchain2.connect(deployer).grantRole(await messageProxyForSchain2.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(newSchainNameZ);
+
+            await messageProxyForSchain2.registerExtraContract(newSchainNameZ, tokenManagerErc202.address);
+            await tokenManagerErc202.addTokenManager(newSchainNameZ, tokenManagerErc20Z.address);
+
+            await erc20OnTargetChain.connect(user).approve(tokenManagerErc202.address, amount);
+
+            await tokenManagerErc202
+                .connect(user)
+                .transferToSchainERC20(newSchainNameZ, erc20OnOriginChain.address, amount)
+                .should.be.eventually.rejectedWith("Insufficient funds");
+
+            await tokenManagerErc202
+                .connect(user)
+                .transferToSchainERC20(newSchainNameZ, erc20OnTargetChain.address, amount)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+        });
+
+        it("should not be able to transfer main chain token or clone to mainnet", async () => {
+            const amount = "20000000000000000";
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerErc20.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc20OnOriginChain.connect(deployer).mint(user.address, amount);
+            await erc20OnOriginChain.connect(user).approve(tokenManagerErc20.address, amount);
+
+            await tokenManagerErc20.addTokenManager(newSchainName, tokenManagerErc202.address);
+
+            // execution:
+            await tokenManagerErc20
+                .connect(user)
+                .transferToSchainERC20(newSchainName, erc20OnOriginChain.address, amount);
+
+            let data = await messages.encodeTransferErc20AndTokenInfoMessage(
+                erc20OnOriginChain.address,
+                user.address,
+                amount,
+                (await erc20OnOriginChain.totalSupply()).toString(),
+                {
+                    name: await erc20OnOriginChain.name(),
+                    symbol: await erc20OnOriginChain.symbol(),
+                    decimals: BigNumber.from(await erc20OnOriginChain.decimals()).toString()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerErc202.addTokenManager(schainName, tokenManagerErc20.address);
+
+            await tokenManagerErc202.connect(deployer).grantRole(await tokenManagerErc202.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerErc202.connect(schainOwner).addERC20TokenByOwner(schainName,  erc20OnOriginChain.address, erc20OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerErc202.address, schainId, tokenManagerErc20.address, data);
+
+            expect((await erc20OnTargetChain.functions.balanceOf(user.address)).toString()).to.be.equal(amount);
+
+            data = await messages.encodeActivateUserMessage(user.address);
+
+            await messageProxyForSchain2.postMessage(communityLocker2.address, mainnetId, fakeCommunityPool, data);
+
+            await erc20OnTargetChain.connect(user).approve(tokenManagerErc202.address, amount);
+
+            await tokenManagerErc202
+                .connect(user)
+                .exitToMainERC20(erc20OnOriginChain.address, amount)
+                .should.be.eventually.rejectedWith("Insufficient funds");
+
+            await tokenManagerErc202
+                .connect(user)
+                .exitToMainERC20(erc20OnTargetChain.address, amount)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerErc202.address);
+
+            await tokenManagerErc202
+                .connect(user)
+                .transferToSchainERC20(schainName, erc20OnOriginChain.address, amount);
+
+            data = await messages.encodeTransferErc20Message(
+                erc20OnOriginChain.address,
+                user.address,
+                amount
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerErc20.address, newSchainId, tokenManagerErc202.address, data);
+            expect((await erc20OnOriginChain.functions.balanceOf(user.address)).toString()).to.be.equal(amount);
+
+            await erc20OnOriginChain.connect(user).approve(tokenManagerErc20.address, amount);
+
+            await messageProxyForSchain.registerExtraContract("Mainnet", tokenManagerErc20.address);
+
+            await tokenManagerErc20
+                .connect(user)
+                .exitToMainERC20(erc20OnOriginChain.address, amount)
+                .should.be.eventually.rejectedWith("Main chain token could not be transfered to Mainnet");
+
+            await tokenManagerErc20
+                .connect(user)
+                .exitToMainERC20(erc20OnTargetChain.address, amount)
+                .should.be.eventually.rejectedWith("Insufficient funds");
+
+        });
+
     });
 
     describe("tests for `postMessage` function", async () => {
diff --git a/proxy/test/TokenManagerERC721.ts b/proxy/test/TokenManagerERC721.ts
index 433f8d88c..d3965db0c 100644
--- a/proxy/test/TokenManagerERC721.ts
+++ b/proxy/test/TokenManagerERC721.ts
@@ -992,6 +992,177 @@ describe("TokenManagerERC721", () => {
             expect((await erc721OnOriginChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
         });
 
+        it("should not be able to transfer X->Y->Z", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721.address, tokenId);
+
+            await tokenManagerERC721.addTokenManager(newSchainName, tokenManagerERC7212.address);
+
+            // execution:
+            await tokenManagerERC721
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            const data = await messages.encodeTransferErc721AndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC7212.addTokenManager(schainName, tokenManagerERC721.address);
+
+            await tokenManagerERC7212.connect(deployer).grantRole(await tokenManagerERC7212.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC7212.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC7212.address, schainId, tokenManagerERC721.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+
+            let erc721OnTargetZChain: ERC721OnChain;
+            let messageProxyForSchainZ: MessageProxyForSchainTester;
+            let tokenManagerLinkerZ: TokenManagerLinker;
+            let tokenManagerERC721Z: TokenManagerERC721;
+            let communityLockerZ: CommunityLocker;
+            const newSchainNameZ = "NewChainZ";
+
+            erc721OnTargetZChain = await deployERC721OnChain("NewTokenZ", "NTNZ");
+
+            const keyStorageZ = await deployKeyStorageMock();
+            messageProxyForSchainZ = await deployMessageProxyForSchainTester(keyStorageZ.address, newSchainNameZ);
+            tokenManagerLinkerZ = await deployTokenManagerLinker(messageProxyForSchainZ, deployer.address);
+            communityLockerZ = await deployCommunityLocker(newSchainName, messageProxyForSchainZ.address, tokenManagerLinkerZ, fakeCommunityPool);
+            tokenManagerERC721Z = await deployTokenManagerERC721(newSchainNameZ, messageProxyForSchainZ.address, tokenManagerLinkerZ, communityLockerZ, fakeDepositBox);
+            await erc721OnTargetZChain.connect(deployer).grantRole(await erc721OnTargetZChain.MINTER_ROLE(), tokenManagerERC721Z.address);
+            await tokenManagerLinkerZ.registerTokenManager(tokenManagerERC721Z.address);
+
+            await messageProxyForSchain2.connect(deployer).grantRole(await messageProxyForSchain2.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(newSchainNameZ);
+
+            await messageProxyForSchain2.registerExtraContract(newSchainNameZ, tokenManagerERC7212.address);
+            await tokenManagerERC7212.addTokenManager(newSchainNameZ, tokenManagerERC721Z.address);
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC7212.address, tokenId);
+
+            await tokenManagerERC7212
+                .connect(user)
+                .transferToSchainERC721(newSchainNameZ, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await tokenManagerERC7212
+                .connect(user)
+                .transferToSchainERC721(newSchainNameZ, erc721OnTargetChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+        });
+
+        it("should not be able to transfer main chain token or clone to mainnet", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721.address, tokenId);
+
+            await tokenManagerERC721.addTokenManager(newSchainName, tokenManagerERC7212.address);
+
+            // execution:
+            await tokenManagerERC721
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721AndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC7212.addTokenManager(schainName, tokenManagerERC721.address);
+
+            await tokenManagerERC7212.connect(deployer).grantRole(await tokenManagerERC7212.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC7212.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC7212.address, schainId, tokenManagerERC721.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+
+            data = await messages.encodeActivateUserMessage(user.address);
+
+            await messageProxyForSchain2.postMessage(communityLocker2.address, mainnetId, fakeCommunityPool, data);
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC7212.address, tokenId);
+
+            await tokenManagerERC7212
+                .connect(user)
+                .exitToMainERC721(erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await tokenManagerERC7212
+                .connect(user)
+                .exitToMainERC721(erc721OnTargetChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC7212.address);
+
+            await tokenManagerERC7212
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721Message(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721.address, newSchainId, tokenManagerERC7212.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721.address, tokenId);
+
+            await messageProxyForSchain.registerExtraContract("Mainnet", tokenManagerERC721.address);
+
+            await tokenManagerERC721
+                .connect(user)
+                .exitToMainERC721(erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Main chain token could not be transfered to Mainnet");
+
+            await tokenManagerERC721
+                .connect(user)
+                .exitToMainERC721(erc721OnTargetChain.address, tokenId)
+                .should.be.eventually.rejectedWith("ERC721: approved query for nonexistent token");
+
+        });
+
     });
 
     describe("tests for `postMessage` function", async () => {
diff --git a/proxy/test/TokenManagerERC721WithMetadata.ts b/proxy/test/TokenManagerERC721WithMetadata.ts
new file mode 100644
index 000000000..e6f73972c
--- /dev/null
+++ b/proxy/test/TokenManagerERC721WithMetadata.ts
@@ -0,0 +1,1298 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ * @license
+ * SKALE IMA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file TokenManagerERC721WithMetadata.spec.ts
+ * @copyright SKALE Labs 2019-Present
+ */
+
+import chaiAsPromised from "chai-as-promised";
+import chai = require("chai");
+import {
+    ERC721OnChain,
+    TokenManagerERC721WithMetadata,
+    TokenManagerLinker,
+    MessageProxyForSchainTester,
+    MessagesTester,
+    CommunityLocker
+} from "../typechain";
+
+import { randomString, stringValue } from "./utils/helper";
+
+chai.should();
+chai.use((chaiAsPromised as any));
+
+import { deployTokenManagerERC721WithMetadata } from "./utils/deploy/schain/tokenManagerERC721WithMetadata";
+import { deployERC721OnChain } from "./utils/deploy/erc721OnChain";
+import { deployMessageProxyForSchainTester } from "./utils/deploy/test/messageProxyForSchainTester";
+import { deployTokenManagerLinker } from "./utils/deploy/schain/tokenManagerLinker";
+import { deployMessages } from "./utils/deploy/messages";
+import { deployCommunityLocker } from "./utils/deploy/schain/communityLocker";
+
+import { ethers, web3 } from "hardhat";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
+import { BigNumber } from "ethers";
+
+import { assert, expect, should } from "chai";
+import { deployKeyStorageMock } from "./utils/deploy/test/keyStorageMock";
+
+describe("TokenManagerERC721WithMetadata", () => {
+    let deployer: SignerWithAddress;
+    let user: SignerWithAddress;
+    let schainOwner: SignerWithAddress;
+
+    const schainName = "V-chain";
+    const schainId = stringValue(web3.utils.soliditySha3(schainName));
+    const tokenId = 1;
+    const tokenURI = "Hello1";
+    const mainnetName = "Mainnet";
+    const mainnetId = stringValue(web3.utils.soliditySha3("Mainnet"));
+    let to: string;
+    let token: ERC721OnChain;
+    let tokenClone: ERC721OnChain;
+    let fakeDepositBox: string;
+    let fakeCommunityPool: string;
+    let tokenManagerERC721WithMetadata: TokenManagerERC721WithMetadata;
+    let tokenManagerLinker: TokenManagerLinker;
+    let messages: MessagesTester;
+    let messageProxyForSchain: MessageProxyForSchainTester;
+    let communityLocker: CommunityLocker;
+    let token2: ERC721OnChain;
+    let tokenClone2: ERC721OnChain;
+    let token3: ERC721OnChain;
+    let tokenClone3: ERC721OnChain;
+
+    before(async () => {
+        [deployer, user, schainOwner] = await ethers.getSigners();
+    });
+
+    beforeEach(async () => {
+        const keyStorage = await deployKeyStorageMock();
+        messageProxyForSchain = await deployMessageProxyForSchainTester(keyStorage.address, schainName);
+        tokenManagerLinker = await deployTokenManagerLinker(messageProxyForSchain, deployer.address);
+        messages = await deployMessages();
+        fakeDepositBox = messages.address;
+        fakeCommunityPool = messages.address;
+
+        communityLocker = await deployCommunityLocker(schainName, messageProxyForSchain.address, tokenManagerLinker, fakeCommunityPool);
+
+        tokenManagerERC721WithMetadata =
+            await deployTokenManagerERC721WithMetadata(
+                schainName,
+                messageProxyForSchain.address,
+                tokenManagerLinker,
+                communityLocker,
+                fakeDepositBox
+            );
+        await tokenManagerERC721WithMetadata.connect(deployer).grantRole(await tokenManagerERC721WithMetadata.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+        await tokenManagerERC721WithMetadata.connect(deployer).grantRole(await tokenManagerERC721WithMetadata.AUTOMATIC_DEPLOY_ROLE(), schainOwner.address);
+        await tokenManagerLinker.registerTokenManager(tokenManagerERC721WithMetadata.address);
+
+        tokenClone = await deployERC721OnChain("ELVIS", "ELV");
+        token = await deployERC721OnChain("SKALE", "SKL");
+        tokenClone2 = await deployERC721OnChain("ELVIS2", "ELV");
+        token2 = await deployERC721OnChain("SKALE2", "SKL");
+        tokenClone3 = await deployERC721OnChain("ELVIS3", "ELV");
+        token3 = await deployERC721OnChain("SKALE3", "SKL");
+
+        to = user.address;
+
+        const data = await messages.encodeActivateUserMessage(user.address);
+        await messageProxyForSchain.postMessage(communityLocker.address, mainnetId, fakeCommunityPool, data);
+
+        const extraContractRegistrarRole = await messageProxyForSchain.EXTRA_CONTRACT_REGISTRAR_ROLE();
+        await messageProxyForSchain.connect(deployer).grantRole(extraContractRegistrarRole, deployer.address);
+
+        await communityLocker.grantRole(await communityLocker.CONSTANT_SETTER_ROLE(), deployer.address);
+        await communityLocker.setTimeLimitPerMessage(0);
+    });
+
+    it("should change depositBox address", async () => {
+        const newDepositBox = user.address;
+        expect(await tokenManagerERC721WithMetadata.depositBox()).to.equal(messages.address);
+        await tokenManagerERC721WithMetadata.connect(user).changeDepositBoxAddress(newDepositBox)
+            .should.be.eventually.rejectedWith("DEFAULT_ADMIN_ROLE is required");
+        await tokenManagerERC721WithMetadata.connect(deployer).changeDepositBoxAddress(newDepositBox);
+        expect(await tokenManagerERC721WithMetadata.depositBox()).to.equal(newDepositBox);
+    });
+
+    it("should successfully call exitToMainERC721", async () => {
+        // should be "No token clone on schain" if chains were different
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId)
+            .should.be.eventually.rejectedWith("ERC721: approved query for nonexistent token");
+
+        await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token.address, tokenClone.address);
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId)
+            .should.be.eventually.rejectedWith("ERC721: approved query for nonexistent token");
+
+        await tokenClone.connect(deployer).mint(user.address, tokenId);
+        await tokenClone.connect(user).setTokenURI(tokenId, tokenURI);
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId)
+            .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+        await tokenClone.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId)
+            .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+        await messageProxyForSchain.registerExtraContract("Mainnet", tokenManagerERC721WithMetadata.address);
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId);
+
+        await messageProxyForSchain.removeExtraContract("Mainnet", tokenManagerERC721WithMetadata.address);
+        await tokenClone.connect(deployer).mint(user.address, tokenId);
+        await tokenClone.connect(user).setTokenURI(tokenId, tokenURI);
+        await tokenClone.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId)
+            .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+        const outgoingMessagesCounterMainnet = BigNumber.from(
+            await messageProxyForSchain.getOutgoingMessagesCounter("Mainnet")
+        );
+        outgoingMessagesCounterMainnet.should.be.deep.equal(BigNumber.from(1));
+    });
+
+    it("should be rejected when call exitToMainERC721 if remove contract for all chains", async () => {
+        await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token.address, tokenClone.address);
+        await tokenClone.connect(deployer).mint(user.address, tokenId);
+        await tokenClone.connect(user).setTokenURI(tokenId, tokenURI);
+        await tokenClone.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+        await messageProxyForSchain.registerExtraContractForAll(tokenManagerERC721WithMetadata.address);
+
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId);
+
+        await messageProxyForSchain.removeExtraContractForAll(tokenManagerERC721WithMetadata.address);
+        await tokenClone.connect(deployer).mint(user.address, tokenId);
+        await tokenClone.connect(user).setTokenURI(tokenId, tokenURI);
+        await tokenClone.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+        await tokenManagerERC721WithMetadata.connect(user).exitToMainERC721(token.address, tokenId)
+            .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+        const outgoingMessagesCounterMainnet = BigNumber.from(
+            await messageProxyForSchain.getOutgoingMessagesCounter("Mainnet")
+        );
+        outgoingMessagesCounterMainnet.should.be.deep.equal(BigNumber.from(1));
+    });
+
+    it("should successfully call addERC721TokenByOwner", async () => {
+        await tokenManagerERC721WithMetadata.connect(user).addERC721TokenByOwner(mainnetName,  token.address, tokenClone.address)
+            .should.be.eventually.rejectedWith("TOKEN_REGISTRAR_ROLE is required");
+
+        await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token.address, deployer.address)
+            .should.be.eventually.rejectedWith("Given address is not a contract");
+
+        await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token.address, tokenClone.address);
+
+        await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token2.address, tokenClone.address)
+            .should.be.eventually.rejectedWith("Clone was already added");
+
+        await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token.address, tokenClone2.address)
+            .should.be.eventually.rejectedWith("Could not relink clone");
+    });
+
+    describe("tests for transferToSchainERC721", async () => {
+
+        let erc721OnOriginChain: ERC721OnChain;
+        let erc721OnTargetChain: ERC721OnChain;
+        let messageProxyForSchain2: MessageProxyForSchainTester;
+        let tokenManagerLinker2: TokenManagerLinker;
+        let tokenManagerERC721WithMetadata2: TokenManagerERC721WithMetadata;
+        let communityLocker2: CommunityLocker;
+        const newSchainName = "NewChain";
+        const newSchainId = stringValue(web3.utils.soliditySha3(newSchainName));
+
+        beforeEach(async () => {
+            erc721OnOriginChain = await deployERC721OnChain("NewToken", "NTN");
+            erc721OnTargetChain = await deployERC721OnChain("NewToke1n", "NTN1");
+
+            const keyStorage2 = await deployKeyStorageMock();
+            messageProxyForSchain2 = await deployMessageProxyForSchainTester(keyStorage2.address, newSchainName);
+            tokenManagerLinker2 = await deployTokenManagerLinker(messageProxyForSchain2, deployer.address);
+            communityLocker2 = await deployCommunityLocker(newSchainName, messageProxyForSchain2.address, tokenManagerLinker2, fakeCommunityPool);
+            tokenManagerERC721WithMetadata2 = await deployTokenManagerERC721WithMetadata(newSchainName, messageProxyForSchain2.address, tokenManagerLinker2, communityLocker2, fakeDepositBox);
+            await erc721OnTargetChain.connect(deployer).grantRole(await erc721OnTargetChain.MINTER_ROLE(), tokenManagerERC721WithMetadata2.address);
+            await tokenManagerLinker2.registerTokenManager(tokenManagerERC721WithMetadata2.address);
+        });
+
+        it("should invoke `transferToSchainERC721` without mistakes", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+        });
+
+        it("should invoke `transferToSchainERC721` and receive tokens without mistakes", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            const data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data).should.be.eventually.rejectedWith("Automatic deploy is disabled");
+
+            await tokenManagerERC721WithMetadata2.enableAutomaticDeploy();
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            const addressERC721OnSchain = await tokenManagerERC721WithMetadata2.clonesErc721(schainId, erc721OnOriginChain.address);
+            const targetErc721OnChain = await (await ethers.getContractFactory("ERC721OnChain")).attach(addressERC721OnSchain) as ERC721OnChain;
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+        });
+
+        it("should invoke `transferToSchainERC721` and receive tokens without mistakes double", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data).should.be.eventually.rejectedWith("Automatic deploy is disabled");
+
+            await tokenManagerERC721WithMetadata2.enableAutomaticDeploy();
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            const addressERC721OnSchain = await tokenManagerERC721WithMetadata2.clonesErc721(schainId, erc721OnOriginChain.address);
+            const targetErc721OnChain = await (await ethers.getContractFactory("ERC721OnChain")).attach(addressERC721OnSchain) as ERC721OnChain;
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            const tokenId2 = 2;
+            const tokenURI2 = "Hello2";
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId2);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId2, tokenURI2);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId2);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId2);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId2,
+                tokenURI2
+            );
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId2)).toString()).to.be.equal(tokenURI2);
+
+        });
+
+        it("should invoke `transferToSchainERC721` and receive tokens without mistakes double with attached token", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address).should.be.eventually.rejectedWith("TOKEN_REGISTRAR_ROLE is required");
+            await tokenManagerERC721WithMetadata2.connect(deployer).grantRole(await tokenManagerERC721WithMetadata2.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address).should.be.eventually.rejectedWith("Chain is not connected");
+
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            const tokenId2 = 2;
+            const tokenURI2 = "Hello2";
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId2);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId2, tokenURI2);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId2);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId2);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId2,
+                tokenURI2
+            );
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId2)).toString()).to.be.equal(tokenURI2);
+
+        });
+
+        it("should invoke `transferToSchainERC721` and transfer back without mistakes", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data).should.be.eventually.rejectedWith("Automatic deploy is disabled");
+
+            await tokenManagerERC721WithMetadata2.enableAutomaticDeploy();
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            const addressERC721OnSchain = await tokenManagerERC721WithMetadata2.clonesErc721(schainId, erc721OnOriginChain.address);
+            const targetErc721OnChain = await (await ethers.getContractFactory("ERC721OnChain")).attach(addressERC721OnSchain) as ERC721OnChain;
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await targetErc721OnChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC721WithMetadata2.address);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+        });
+
+        it("should invoke `transferToSchainERC721` and transfer back without mistakes with attached tokens", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+            await tokenManagerERC721WithMetadata2.connect(deployer).grantRole(await tokenManagerERC721WithMetadata2.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC721WithMetadata2.address);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+        });
+
+
+        it("should invoke `transferToSchainERC721` and transfer back without mistakes double", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data).should.be.eventually.rejectedWith("Automatic deploy is disabled");
+
+            await tokenManagerERC721WithMetadata2.enableAutomaticDeploy();
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            const addressERC721OnSchain = await tokenManagerERC721WithMetadata2.clonesErc721(schainId, erc721OnOriginChain.address);
+            const targetErc721OnChain = await (await ethers.getContractFactory("ERC721OnChain")).attach(addressERC721OnSchain) as ERC721OnChain;
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await targetErc721OnChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC721WithMetadata2.address);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            const tokenId2 = 2;
+            const tokenURI2 = "Hello2";
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId2);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId2, tokenURI2);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId2);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId2);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId2,
+                tokenURI2
+            );
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+            expect((await targetErc721OnChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
+            expect((await targetErc721OnChain.functions.tokenURI(tokenId2)).toString()).to.be.equal(tokenURI2);
+
+            await targetErc721OnChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await targetErc721OnChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId2);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId2);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId2,
+                tokenURI2
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId2)).toString()).to.be.equal(tokenURI2);
+
+        });
+
+        it("should invoke `transferToSchainERC721` and transfer back without mistakes double with attached tokens", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+            await tokenManagerERC721WithMetadata2.connect(deployer).grantRole(await tokenManagerERC721WithMetadata2.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect Token Manager address");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721("Mainnet", erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("This function is not for transferring to Mainnet");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Sender contract is not registered");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC721WithMetadata2.address);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            const tokenId2 = 2;
+            const tokenURI2 = "Hello2";
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId2);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId2, tokenURI2);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId2);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId2);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId2,
+                tokenURI2
+            );
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId2)).toString()).to.be.equal(tokenURI2);
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId2);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId2);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId2,
+                tokenURI2
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId2)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId2)).toString()).to.be.equal(tokenURI2);
+        });
+
+        it("should not be able to transfer X->Y->Z", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            const data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+            await tokenManagerERC721WithMetadata2.connect(deployer).grantRole(await tokenManagerERC721WithMetadata2.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            let erc721OnTargetZChain: ERC721OnChain;
+            let messageProxyForSchainZ: MessageProxyForSchainTester;
+            let tokenManagerLinkerZ: TokenManagerLinker;
+            let tokenManagerERC721WithMetadataZ: TokenManagerERC721WithMetadata;
+            let communityLockerZ: CommunityLocker;
+            const newSchainNameZ = "NewChainZ";
+
+            erc721OnTargetZChain = await deployERC721OnChain("NewTokenZ", "NTNZ");
+
+            const keyStorageZ = await deployKeyStorageMock();
+            messageProxyForSchainZ = await deployMessageProxyForSchainTester(keyStorageZ.address, newSchainNameZ);
+            tokenManagerLinkerZ = await deployTokenManagerLinker(messageProxyForSchainZ, deployer.address);
+            communityLockerZ = await deployCommunityLocker(newSchainName, messageProxyForSchainZ.address, tokenManagerLinkerZ, fakeCommunityPool);
+            tokenManagerERC721WithMetadataZ = await deployTokenManagerERC721WithMetadata(newSchainNameZ, messageProxyForSchainZ.address, tokenManagerLinkerZ, communityLockerZ, fakeDepositBox);
+            await erc721OnTargetZChain.connect(deployer).grantRole(await erc721OnTargetZChain.MINTER_ROLE(), tokenManagerERC721WithMetadataZ.address);
+            await tokenManagerLinkerZ.registerTokenManager(tokenManagerERC721WithMetadataZ.address);
+
+            await messageProxyForSchain2.connect(deployer).grantRole(await messageProxyForSchain2.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(newSchainNameZ);
+
+            await messageProxyForSchain2.registerExtraContract(newSchainNameZ, tokenManagerERC721WithMetadata2.address);
+            await tokenManagerERC721WithMetadata2.addTokenManager(newSchainNameZ, tokenManagerERC721WithMetadataZ.address);
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(newSchainNameZ, erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(newSchainNameZ, erc721OnTargetChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+        });
+
+        it("should not be able to transfer main chain token or clone to mainnet", async () => {
+            await messageProxyForSchain.registerExtraContract(newSchainName, tokenManagerERC721WithMetadata.address);
+
+            // add connected chain:
+            await messageProxyForSchain.connect(deployer).grantRole(await messageProxyForSchain.CHAIN_CONNECTOR_ROLE(), deployer.address);
+            await messageProxyForSchain.connect(deployer).addConnectedChain(newSchainName);
+
+            await erc721OnOriginChain.connect(deployer).mint(user.address, tokenId);
+            await erc721OnOriginChain.connect(user).setTokenURI(tokenId, tokenURI);
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await tokenManagerERC721WithMetadata.addTokenManager(newSchainName, tokenManagerERC721WithMetadata2.address);
+
+            // execution:
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .transferToSchainERC721(newSchainName, erc721OnOriginChain.address, tokenId);
+
+            let data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI,
+                {
+                    name: await erc721OnOriginChain.name(),
+                    symbol: await erc721OnOriginChain.symbol()
+                }
+            );
+
+            // expectation:
+            const outgoingMessagesCounter = BigNumber.from(
+                await messageProxyForSchain.getOutgoingMessagesCounter(newSchainName));
+            outgoingMessagesCounter.should.be.deep.equal(BigNumber.from(1));
+
+            // receive:
+            //  registration:
+            await messageProxyForSchain2.connect(deployer).addConnectedChain(schainName);
+            await tokenManagerERC721WithMetadata2.addTokenManager(schainName, tokenManagerERC721WithMetadata.address);
+
+            await tokenManagerERC721WithMetadata2.connect(deployer).grantRole(await tokenManagerERC721WithMetadata2.TOKEN_REGISTRAR_ROLE(), schainOwner.address);
+            await tokenManagerERC721WithMetadata2.connect(schainOwner).addERC721TokenByOwner(schainName,  erc721OnOriginChain.address, erc721OnTargetChain.address);
+
+            await messageProxyForSchain2.postMessage(tokenManagerERC721WithMetadata2.address, schainId, tokenManagerERC721WithMetadata.address, data);
+
+            expect((await erc721OnTargetChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnTargetChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            data = await messages.encodeActivateUserMessage(user.address);
+
+            await messageProxyForSchain2.postMessage(communityLocker2.address, mainnetId, fakeCommunityPool, data);
+
+            await erc721OnTargetChain.connect(user).approve(tokenManagerERC721WithMetadata2.address, tokenId);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .exitToMainERC721(erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Not allowed ERC721 Token");
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .exitToMainERC721(erc721OnTargetChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Incorrect main chain token");
+
+            await messageProxyForSchain2.registerExtraContract(schainName, tokenManagerERC721WithMetadata2.address);
+
+            await tokenManagerERC721WithMetadata2
+                .connect(user)
+                .transferToSchainERC721(schainName, erc721OnOriginChain.address, tokenId);
+
+            data = await messages.encodeTransferErc721MessageWithMetadata(
+                erc721OnOriginChain.address,
+                user.address,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, newSchainId, tokenManagerERC721WithMetadata2.address, data);
+            expect((await erc721OnOriginChain.functions.ownerOf(tokenId)).toString()).to.be.equal(user.address);
+            expect((await erc721OnOriginChain.functions.tokenURI(tokenId)).toString()).to.be.equal(tokenURI);
+
+            await erc721OnOriginChain.connect(user).approve(tokenManagerERC721WithMetadata.address, tokenId);
+
+            await messageProxyForSchain.registerExtraContract("Mainnet", tokenManagerERC721WithMetadata.address);
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .exitToMainERC721(erc721OnOriginChain.address, tokenId)
+                .should.be.eventually.rejectedWith("Main chain token could not be transfered to Mainnet");
+
+            await tokenManagerERC721WithMetadata
+                .connect(user)
+                .exitToMainERC721(erc721OnTargetChain.address, tokenId)
+                .should.be.eventually.rejectedWith("ERC721: approved query for nonexistent token");
+
+        });
+
+    });
+
+    describe("tests for `postMessage` function", async () => {
+
+        it("should transfer ERC721 token token with token info", async () => {
+            //  preparation
+            const data = await messages.encodeTransferErc721WithMetadataAndTokenInfoMessage(
+                token.address,
+                to,
+                tokenId,
+                tokenURI,
+                {
+                    name: await token.name(),
+                    symbol: await token.symbol()
+                }
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, mainnetId, fakeDepositBox, data)
+                .should.be.eventually.rejectedWith("Automatic deploy is disabled");
+
+            await tokenManagerERC721WithMetadata.connect(schainOwner).enableAutomaticDeploy();
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, mainnetId, fakeDepositBox, data);
+            const addressERC721OnSchain = await tokenManagerERC721WithMetadata.clonesErc721(mainnetId, token.address);
+            const erc721OnChain = await (await ethers.getContractFactory("ERC721OnChain")).attach(addressERC721OnSchain) as ERC721OnChain;
+            expect((await erc721OnChain.functions.ownerOf(tokenId))[0]).to.be.equal(to);
+            expect((await erc721OnChain.functions.tokenURI(tokenId))[0]).to.be.equal(tokenURI);
+        });
+
+        it("should transfer ERC721 token on schain", async () => {
+            //  preparation
+            await tokenManagerERC721WithMetadata.connect(schainOwner).addERC721TokenByOwner(mainnetName,  token.address, tokenClone.address);
+            await tokenClone.connect(deployer).grantRole(await tokenClone.MINTER_ROLE(), tokenManagerERC721WithMetadata.address);
+
+            const data = await messages.encodeTransferErc721MessageWithMetadata(
+                token.address,
+                to,
+                tokenId,
+                tokenURI
+            );
+
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, mainnetId, fakeDepositBox, data);
+            const addressERC721OnSchain = await tokenManagerERC721WithMetadata.clonesErc721(mainnetId, token.address);
+            const erc721OnChain = (await ethers.getContractFactory("ERC721OnChain")).attach(addressERC721OnSchain) as ERC721OnChain;
+            expect((await erc721OnChain.functions.ownerOf(tokenId))[0]).to.be.equal(to);
+            expect((await erc721OnChain.functions.tokenURI(tokenId))[0]).to.be.equal(tokenURI);
+        });
+
+        it("should reject if message type is unknown", async () => {
+            const data = "0x0000000000000000000000000000000000000000000000000000000000000001"+
+            "000000000000000000000000a51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0"+
+            "00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8"+
+            "0000000000000000000000000000000000000000000000000000000000000001";
+            await messageProxyForSchain.postMessage(tokenManagerERC721WithMetadata.address, mainnetId, fakeDepositBox, data)
+                .should.be.eventually.rejectedWith("MessageType is unknown");
+
+        });
+    });
+});
diff --git a/proxy/test/utils/deploy/mainnet/depositBoxERC721WithMetadata.ts b/proxy/test/utils/deploy/mainnet/depositBoxERC721WithMetadata.ts
new file mode 100644
index 000000000..081704a0d
--- /dev/null
+++ b/proxy/test/utils/deploy/mainnet/depositBoxERC721WithMetadata.ts
@@ -0,0 +1,15 @@
+import { ethers } from "hardhat";
+import { ContractManager, MessageProxyForMainnet, Linker, DepositBoxERC721WithMetadata } from "../../../../typechain";
+
+export async function deployDepositBoxERC721WithMetadata(
+    contractManager: ContractManager,
+    linker: Linker,
+    messageProxy: MessageProxyForMainnet
+
+) {
+    const factory = await ethers.getContractFactory("DepositBoxERC721WithMetadata");
+    const instance = await factory.deploy() as DepositBoxERC721WithMetadata;
+    await instance["initialize(address,address,address)"](contractManager.address, linker.address, messageProxy.address);
+    await linker.registerMainnetContract(instance.address);
+    return instance;
+}
\ No newline at end of file
diff --git a/proxy/test/utils/deploy/schain/tokenManagerERC721WithMetadata.ts b/proxy/test/utils/deploy/schain/tokenManagerERC721WithMetadata.ts
new file mode 100644
index 000000000..d6ae140d2
--- /dev/null
+++ b/proxy/test/utils/deploy/schain/tokenManagerERC721WithMetadata.ts
@@ -0,0 +1,23 @@
+import { ethers } from "hardhat";
+import { TokenManagerERC721WithMetadata, MessageProxyForSchain, TokenManagerLinker, CommunityLocker } from "../../../../typechain";
+
+const name = "TokenManagerERC721WithMetadata";
+
+export async function deployTokenManagerERC721WithMetadata(
+    schainName: string,
+    messageProxyForSchain: string,
+    tokenManagerLinker: TokenManagerLinker,
+    communityLocker: CommunityLocker,
+    newDepositBox: string
+) {
+    const factory = await ethers.getContractFactory(name);
+    const instance = await factory.deploy() as TokenManagerERC721WithMetadata;
+    await instance.initialize(
+        schainName,
+        messageProxyForSchain,
+        tokenManagerLinker.address,
+        communityLocker.address,
+        newDepositBox
+    );
+    return instance;
+}
\ No newline at end of file
diff --git a/test/agent-test-data/proxyMainnet.json b/test/agent-test-data/proxyMainnet.json
index d83e9a5e0..a25df61ec 100644
--- a/test/agent-test-data/proxyMainnet.json
+++ b/test/agent-test-data/proxyMainnet.json
@@ -4155,5 +4155,645 @@
                 }
             ]
         }
+    ],
+    "deposit_box_erc721_with_metadata_address": "0xA0f10D670E9BE483D6b9d60FE44921E979D11AF1",
+    "deposit_box_erc721_with_metadata_abi": [
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "ERC721TokenAdded",
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName",
+                    "indexed": false
+                },
+                {
+                    "type": "address",
+                    "name": "contractOnMainnet",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "ERC721TokenReady",
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "contractOnMainnet",
+                    "indexed": true
+                },
+                {
+                    "type": "uint256",
+                    "name": "tokenId",
+                    "indexed": false
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "RoleAdminChanged",
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role",
+                    "indexed": true
+                },
+                {
+                    "type": "bytes32",
+                    "name": "previousAdminRole",
+                    "indexed": true
+                },
+                {
+                    "type": "bytes32",
+                    "name": "newAdminRole",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "RoleGranted",
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "account",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "sender",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "RoleRevoked",
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "account",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "sender",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "DEFAULT_ADMIN_ROLE",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "DEPOSIT_BOX_MANAGER_ROLE",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "addERC721TokenByOwner",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "addSchainContract",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                },
+                {
+                    "type": "address",
+                    "name": "newTokenManagerERC721Address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "contractManagerOfSkaleManager",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "depositERC721",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                },
+                {
+                    "type": "address",
+                    "name": "contractOnMainnet"
+                },
+                {
+                    "type": "address",
+                    "name": "to"
+                },
+                {
+                    "type": "uint256",
+                    "name": "tokenId"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "disableWhitelist",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "enableWhitelist",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "getFunds",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet"
+                },
+                {
+                    "type": "address",
+                    "name": "receiver"
+                },
+                {
+                    "type": "uint256",
+                    "name": "tokenId"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "getRoleAdmin",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getRoleMember",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "uint256",
+                    "name": "index"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getRoleMemberCount",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "uint256",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getSchainToERC721",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "grantRole",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "hasRole",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "hasSchainContract",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "initialize",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "contractManagerOfSkaleManager"
+                },
+                {
+                    "type": "address",
+                    "name": "linker"
+                },
+                {
+                    "type": "address",
+                    "name": "messageProxy"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "initialize",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "newContractManagerOfSkaleManager"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "isSchainOwner",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "sender"
+                },
+                {
+                    "type": "bytes32",
+                    "name": "schainHash"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "linker",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "messageProxy",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "postMessage",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "schainHash"
+                },
+                {
+                    "type": "address",
+                    "name": "sender"
+                },
+                {
+                    "type": "bytes",
+                    "name": "data"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "removeSchainContract",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "renounceRole",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "revokeRole",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "schainToERC721",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                },
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "tokenManagerERC721Addresses",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "transferredAmount",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                },
+                {
+                    "type": "uint256",
+                    "name": ""
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "withoutWhitelist",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        }
     ]
 }
\ No newline at end of file
diff --git a/test/agent-test-data/proxySchain_Bob.json b/test/agent-test-data/proxySchain_Bob.json
index 1f979e43a..88762a1ce 100644
--- a/test/agent-test-data/proxySchain_Bob.json
+++ b/test/agent-test-data/proxySchain_Bob.json
@@ -4616,6 +4616,748 @@
             "outputs": []
         }
     ],
+    "token_manager_erc721_with_metadata_address": "0xe0911b815F3df0AEf4259FC2F6ad211c76F65180",
+    "token_manager_erc721_with_metadata_abi": [
+        {
+            "type": "constructor",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "newChainName"
+                },
+                {
+                    "type": "address",
+                    "name": "newMessageProxy"
+                },
+                {
+                    "type": "address",
+                    "name": "newIMALinker"
+                },
+                {
+                    "type": "address",
+                    "name": "newCommunityLocker"
+                },
+                {
+                    "type": "address",
+                    "name": "newDepositBox"
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "ERC721TokenAdded",
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnSchain",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "ERC721TokenCreated",
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnSchain",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "ERC721TokenReceived",
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnSchain",
+                    "indexed": true
+                },
+                {
+                    "type": "uint256",
+                    "name": "tokenId",
+                    "indexed": false
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "RoleAdminChanged",
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role",
+                    "indexed": true
+                },
+                {
+                    "type": "bytes32",
+                    "name": "previousAdminRole",
+                    "indexed": true
+                },
+                {
+                    "type": "bytes32",
+                    "name": "newAdminRole",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "RoleGranted",
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "account",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "sender",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "event",
+            "anonymous": false,
+            "name": "RoleRevoked",
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "account",
+                    "indexed": true
+                },
+                {
+                    "type": "address",
+                    "name": "sender",
+                    "indexed": true
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "DEFAULT_ADMIN_ROLE",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "MAINNET_HASH",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "MAINNET_NAME",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "string",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "SKALE_FEATURES_SETTER_ROLE",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "addERC721TokenByOwner",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "erc721OnMainnet"
+                },
+                {
+                    "type": "address",
+                    "name": "erc721OnSchain"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "addTokenManager",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                },
+                {
+                    "type": "address",
+                    "name": "newTokenManager"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "automaticDeploy",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "changeDepositBoxAddress",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "newDepositBox"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "clonesErc721",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "communityLocker",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "depositBox",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "disableAutomaticDeploy",
+            "constant": false,
+            "payable": false,
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "enableAutomaticDeploy",
+            "constant": false,
+            "payable": false,
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "exitToMainERC721",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "contractOnMainnet"
+                },
+                {
+                    "type": "address",
+                    "name": "to"
+                },
+                {
+                    "type": "uint256",
+                    "name": "tokenId"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "getCommunityLocker",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getDepositBoxERC721Address",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getMessageProxy",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getRoleAdmin",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getRoleMember",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "uint256",
+                    "name": "index"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getRoleMemberCount",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "uint256",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getSchainHash",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getSkaleFeatures",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "getTokenManagerLinker",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "grantRole",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "hasRole",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "hasTokenManager",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "messageProxy",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "postMessage",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "fromChainHash"
+                },
+                {
+                    "type": "address",
+                    "name": "sender"
+                },
+                {
+                    "type": "bytes",
+                    "name": "data"
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "bool",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "removeTokenManager",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "schainName"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "renounceRole",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "revokeRole",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": "role"
+                },
+                {
+                    "type": "address",
+                    "name": "account"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "schainHash",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "setSkaleFeaturesAddress",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "address",
+                    "name": "newSkaleFeaturesAddress"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "type": "function",
+            "name": "skaleFeaturesAddress",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "tokenManagerLinker",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "tokenManagers",
+            "constant": true,
+            "stateMutability": "view",
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "bytes32",
+                    "name": ""
+                }
+            ],
+            "outputs": [
+                {
+                    "type": "address",
+                    "name": ""
+                }
+            ]
+        },
+        {
+            "type": "function",
+            "name": "transferToSchainERC721",
+            "constant": false,
+            "payable": false,
+            "inputs": [
+                {
+                    "type": "string",
+                    "name": "targetSchainName"
+                },
+                {
+                    "type": "address",
+                    "name": "contractOnMainnet"
+                },
+                {
+                    "type": "address",
+                    "name": "to"
+                },
+                {
+                    "type": "uint256",
+                    "name": "tokenId"
+                }
+            ],
+            "outputs": []
+        }
+    ],
     "eth_erc20_address": "0xC46846f9a913709d66C1f54C85F0100919e00CCC",
     "eth_erc20_abi": [
         {
diff --git a/test/proxy/deployer.py b/test/proxy/deployer.py
index 158bb40a9..b99b5d82f 100644
--- a/test/proxy/deployer.py
+++ b/test/proxy/deployer.py
@@ -37,21 +37,27 @@ def deploy_mainnet(self):
         execute('yarn deploy-skale-manager-components')
         execute('yarn deploy-to-mainnet')
 
-    def deploy_schain(self):
+    def deploy_schain(self, schain_name):
         chdir(self.config.proxy_root)
-        self._prepare_env_file()
+        self._prepare_env_file(schain_name)
         execute('yarn deploy-to-schain')
 
+    def deploy_second_schain(self):
+        self.deploy()
+        self.deploy_schain(self.config.schain_name_2)
+
+
     # private
 
-    def _prepare_env_file(self):
+    def _prepare_env_file(self, schain_name=''):
+        if schain_name == '' : schain_name = self.config.schain_name
         env_file = [f'NETWORK_FOR_ETHEREUM="{self.config.network_for_mainnet}"',
                     f'NETWORK_FOR_SCHAIN="{self.config.network_for_schain}"',
                     f'PRIVATE_KEY_FOR_ETHEREUM="{self.config.mainnet_key}"',
                     f'URL_W3_ETHEREUM="{self.config.mainnet_rpc_url}"',
                     f'PRIVATE_KEY_FOR_SCHAIN="{self.config.schain_key}"',
                     f'URL_W3_S_CHAIN="{self.config.schain_rpc_url}"',
-                    f'CHAIN_NAME_SCHAIN="{self.config.schain_name}"',
+                    f'CHAIN_NAME_SCHAIN="{schain_name}"',
                     'NO_SIGNATURES=true']
 
         with open('.env', 'w') as dot_env:
diff --git a/test/tools/config.py b/test/tools/config.py
index 33b3b74cd..763e2e795 100644
--- a/test/tools/config.py
+++ b/test/tools/config.py
@@ -33,8 +33,10 @@ class Config:
     schain_key = ''
     schain_rpc_url = 'http://localhost:8545'
     schain_name = 'd2'
+    schain_name_2 = 'd3'
     abi_mainnet = 'data/proxyMainnet.json'
     abi_schain = 'data/proxySchain_'
+    abi_schain_2 = 'data/proxySchain_'
     user_key = ''
 
     def __init__(self, src_root, config_json):
@@ -63,5 +65,6 @@ def __init__(self, src_root, config_json):
 
         self.abi_mainnet = self.proxy_root + '/' + self.abi_mainnet
         self.abi_schain = self.proxy_root + '/' + self.abi_schain + self.schain_name + '.json'
+        self.abi_schain_2 = self.proxy_root + '/' + self.abi_schain_2 + self.schain_name_2 + '.json'