git.delta.rocks / unique-network / refs/commits / 7f98069c7b60

difftreelog

feat Add `collection_admins` method to eth collection.

Trubnikov Sergey2022-10-24parent: #ab2e6a7.patch.diff
in: master

19 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -580,7 +580,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	fn collection_owner(&self) -> Result<(address, uint256)> {
 		Ok(convert_cross_account_to_tuple::<T>(
@@ -616,13 +616,16 @@
 	// 		.map_err(dispatch_to_evm::<T>)
 	// }
 
-	// TODO: need implement AbiWriter for &Vec<T>
-	// fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {
-	// 	let result = pallet_common::IsAdmin::<T>::iter_prefix((self.id,))
-	// 		.map(|(admin, _)| pallet_common::eth::convert_cross_account_to_tuple::<T>(&admin))
-	// 		.collect();
-	// 	Ok(result)
-	// }
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {
+		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))
+			.map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))
+			.collect();
+		Ok(result)
+	}
 }
 
 /// ### Note
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -18,7 +18,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -282,7 +282,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -303,6 +303,18 @@
 		newOwner;
 		dummy = 0;
 	}
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() public view returns (Tuple6[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple6[](0);
+	}
 }
 
 /// @dev the ERC-165 identifier for this interface is 0x63034ac5
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -355,7 +355,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -376,6 +376,18 @@
 		newOwner;
 		dummy = 0;
 	}
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() public view returns (Tuple17[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple17[](0);
+	}
 }
 
 /// @dev anonymous struct
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -355,7 +355,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -376,6 +376,18 @@
 		newOwner;
 		dummy = 0;
 	}
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() public view returns (Tuple17[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple17[](0);
+	}
 }
 
 /// @dev anonymous struct
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -13,7 +13,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -184,7 +184,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -197,6 +197,14 @@
 	/// @dev EVM selector for this function is: 0x4f53e226,
 	///  or in textual repr: changeCollectionOwner(address)
 	function changeCollectionOwner(address newOwner) external;
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() external view returns (Tuple6[] memory);
 }
 
 /// @dev the ERC-165 identifier for this interface is 0x63034ac5
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -233,7 +233,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -246,6 +246,14 @@
 	/// @dev EVM selector for this function is: 0x4f53e226,
 	///  or in textual repr: changeCollectionOwner(address)
 	function changeCollectionOwner(address newOwner) external;
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() external view returns (Tuple17[] memory);
 }
 
 /// @dev anonymous struct
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -233,7 +233,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -246,6 +246,14 @@
 	/// @dev EVM selector for this function is: 0x4f53e226,
 	///  or in textual repr: changeCollectionOwner(address)
 	function changeCollectionOwner(address newOwner) external;
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() external view returns (Tuple17[] memory);
 }
 
 /// @dev anonymous struct
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -14,6 +14,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
+import {IEthCrossAccountId} from '../util/playgrounds/types';
 import {usingEthPlaygrounds, itEth, expect, EthUniqueHelper} from './util';
 
 async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise<any>) {
@@ -68,11 +69,31 @@
 
     const newAdmin = helper.eth.createAccount();
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+  
     expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.false;
     await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
     expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.true;
   });
+  
+  itEth.skip('Check adminlist', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+        
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
+    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+    
+    const admin1 = helper.eth.createAccount();
+    const [admin2] = await helper.arrange.createAccounts([10n], donor);
+    await collectionEvm.methods.addCollectionAdmin(admin1).send();
+    await collectionEvm.methods.addCollectionAdminSubstrate(admin2.addressRaw).send();
 
+    const adminListRpc = await helper.collection.getAdmins(collectionId);
+    let adminListEth = await collectionEvm.methods.collectionAdmins().call();
+    adminListEth = adminListEth.map((element: IEthCrossAccountId) => {
+      return helper.address.convertCrossAccountFromEthCrossAcoount(element);
+    });
+    expect(adminListRpc).to.be.like(adminListEth);
+  });  
+    
   itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -126,6 +126,23 @@
   },
   {
     "inputs": [],
+    "name": "collectionAdmins",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6[]",
+        "name": "",
+        "type": "tuple[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -156,6 +156,23 @@
   },
   {
     "inputs": [],
+    "name": "collectionAdmins",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple17[]",
+        "name": "",
+        "type": "tuple[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
before · tests/src/eth/reFungibleAbi.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "approved",15        "type": "address"16      },17      {18        "indexed": true,19        "internalType": "uint256",20        "name": "tokenId",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "owner",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "operator",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "bool",45        "name": "approved",46        "type": "bool"47      }48    ],49    "name": "ApprovalForAll",50    "type": "event"51  },52  {53    "anonymous": false,54    "inputs": [],55    "name": "MintingFinished",56    "type": "event"57  },58  {59    "anonymous": false,60    "inputs": [61      {62        "indexed": true,63        "internalType": "address",64        "name": "from",65        "type": "address"66      },67      {68        "indexed": true,69        "internalType": "address",70        "name": "to",71        "type": "address"72      },73      {74        "indexed": true,75        "internalType": "uint256",76        "name": "tokenId",77        "type": "uint256"78      }79    ],80    "name": "Transfer",81    "type": "event"82  },83  {84    "inputs": [85      { "internalType": "address", "name": "newAdmin", "type": "address" }86    ],87    "name": "addCollectionAdmin",88    "outputs": [],89    "stateMutability": "nonpayable",90    "type": "function"91  },92  {93    "inputs": [94      { "internalType": "address", "name": "user", "type": "address" }95    ],96    "name": "addToCollectionAllowList",97    "outputs": [],98    "stateMutability": "nonpayable",99    "type": "function"100  },101  {102    "inputs": [103      { "internalType": "address", "name": "user", "type": "address" }104    ],105    "name": "allowed",106    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],107    "stateMutability": "view",108    "type": "function"109  },110  {111    "inputs": [112      { "internalType": "address", "name": "approved", "type": "address" },113      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }114    ],115    "name": "approve",116    "outputs": [],117    "stateMutability": "nonpayable",118    "type": "function"119  },120  {121    "inputs": [122      { "internalType": "address", "name": "owner", "type": "address" }123    ],124    "name": "balanceOf",125    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],126    "stateMutability": "view",127    "type": "function"128  },129  {130    "inputs": [131      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }132    ],133    "name": "burn",134    "outputs": [],135    "stateMutability": "nonpayable",136    "type": "function"137  },138  {139    "inputs": [140      { "internalType": "address", "name": "from", "type": "address" },141      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }142    ],143    "name": "burnFrom",144    "outputs": [],145    "stateMutability": "nonpayable",146    "type": "function"147  },148  {149    "inputs": [150      { "internalType": "address", "name": "newOwner", "type": "address" }151    ],152    "name": "changeCollectionOwner",153    "outputs": [],154    "stateMutability": "nonpayable",155    "type": "function"156  },157  {158    "inputs": [],159    "name": "collectionOwner",160    "outputs": [161      {162        "components": [163          { "internalType": "address", "name": "field_0", "type": "address" },164          { "internalType": "uint256", "name": "field_1", "type": "uint256" }165        ],166        "internalType": "struct Tuple17",167        "name": "",168        "type": "tuple"169      }170    ],171    "stateMutability": "view",172    "type": "function"173  },174  {175    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],176    "name": "collectionProperty",177    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],178    "stateMutability": "view",179    "type": "function"180  },181  {182    "inputs": [],183    "name": "collectionSponsor",184    "outputs": [185      {186        "components": [187          { "internalType": "address", "name": "field_0", "type": "address" },188          { "internalType": "uint256", "name": "field_1", "type": "uint256" }189        ],190        "internalType": "struct Tuple17",191        "name": "",192        "type": "tuple"193      }194    ],195    "stateMutability": "view",196    "type": "function"197  },198  {199    "inputs": [],200    "name": "confirmCollectionSponsorship",201    "outputs": [],202    "stateMutability": "nonpayable",203    "type": "function"204  },205  {206    "inputs": [],207    "name": "contractAddress",208    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],209    "stateMutability": "view",210    "type": "function"211  },212  {213    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],214    "name": "deleteCollectionProperty",215    "outputs": [],216    "stateMutability": "nonpayable",217    "type": "function"218  },219  {220    "inputs": [221      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },222      { "internalType": "string", "name": "key", "type": "string" }223    ],224    "name": "deleteProperty",225    "outputs": [],226    "stateMutability": "nonpayable",227    "type": "function"228  },229  {230    "inputs": [],231    "name": "finishMinting",232    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],233    "stateMutability": "nonpayable",234    "type": "function"235  },236  {237    "inputs": [238      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }239    ],240    "name": "getApproved",241    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],242    "stateMutability": "view",243    "type": "function"244  },245  {246    "inputs": [],247    "name": "hasCollectionPendingSponsor",248    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],249    "stateMutability": "view",250    "type": "function"251  },252  {253    "inputs": [254      { "internalType": "address", "name": "owner", "type": "address" },255      { "internalType": "address", "name": "operator", "type": "address" }256    ],257    "name": "isApprovedForAll",258    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],259    "stateMutability": "view",260    "type": "function"261  },262  {263    "inputs": [264      { "internalType": "address", "name": "user", "type": "address" }265    ],266    "name": "isOwnerOrAdmin",267    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],268    "stateMutability": "view",269    "type": "function"270  },271  {272    "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],273    "name": "mint",274    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],275    "stateMutability": "nonpayable",276    "type": "function"277  },278  {279    "inputs": [280      { "internalType": "address", "name": "to", "type": "address" },281      { "internalType": "string", "name": "tokenUri", "type": "string" }282    ],283    "name": "mintWithTokenURI",284    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],285    "stateMutability": "nonpayable",286    "type": "function"287  },288  {289    "inputs": [],290    "name": "mintingFinished",291    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],292    "stateMutability": "view",293    "type": "function"294  },295  {296    "inputs": [],297    "name": "name",298    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],299    "stateMutability": "view",300    "type": "function"301  },302  {303    "inputs": [],304    "name": "nextTokenId",305    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],306    "stateMutability": "view",307    "type": "function"308  },309  {310    "inputs": [311      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }312    ],313    "name": "ownerOf",314    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],315    "stateMutability": "view",316    "type": "function"317  },318  {319    "inputs": [320      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },321      { "internalType": "string", "name": "key", "type": "string" }322    ],323    "name": "property",324    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],325    "stateMutability": "view",326    "type": "function"327  },328  {329    "inputs": [330      { "internalType": "address", "name": "admin", "type": "address" }331    ],332    "name": "removeCollectionAdmin",333    "outputs": [],334    "stateMutability": "nonpayable",335    "type": "function"336  },337  {338    "inputs": [],339    "name": "removeCollectionSponsor",340    "outputs": [],341    "stateMutability": "nonpayable",342    "type": "function"343  },344  {345    "inputs": [346      { "internalType": "address", "name": "user", "type": "address" }347    ],348    "name": "removeFromCollectionAllowList",349    "outputs": [],350    "stateMutability": "nonpayable",351    "type": "function"352  },353  {354    "inputs": [355      { "internalType": "address", "name": "from", "type": "address" },356      { "internalType": "address", "name": "to", "type": "address" },357      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }358    ],359    "name": "safeTransferFrom",360    "outputs": [],361    "stateMutability": "nonpayable",362    "type": "function"363  },364  {365    "inputs": [366      { "internalType": "address", "name": "from", "type": "address" },367      { "internalType": "address", "name": "to", "type": "address" },368      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },369      { "internalType": "bytes", "name": "data", "type": "bytes" }370    ],371    "name": "safeTransferFromWithData",372    "outputs": [],373    "stateMutability": "nonpayable",374    "type": "function"375  },376  {377    "inputs": [378      { "internalType": "address", "name": "operator", "type": "address" },379      { "internalType": "bool", "name": "approved", "type": "bool" }380    ],381    "name": "setApprovalForAll",382    "outputs": [],383    "stateMutability": "nonpayable",384    "type": "function"385  },386  {387    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],388    "name": "setCollectionAccess",389    "outputs": [],390    "stateMutability": "nonpayable",391    "type": "function"392  },393  {394    "inputs": [395      { "internalType": "string", "name": "limit", "type": "string" },396      { "internalType": "uint32", "name": "value", "type": "uint32" }397    ],398    "name": "setCollectionLimit",399    "outputs": [],400    "stateMutability": "nonpayable",401    "type": "function"402  },403  {404    "inputs": [405      { "internalType": "string", "name": "limit", "type": "string" },406      { "internalType": "bool", "name": "value", "type": "bool" }407    ],408    "name": "setCollectionLimit",409    "outputs": [],410    "stateMutability": "nonpayable",411    "type": "function"412  },413  {414    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],415    "name": "setCollectionMintMode",416    "outputs": [],417    "stateMutability": "nonpayable",418    "type": "function"419  },420  {421    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],422    "name": "setCollectionNesting",423    "outputs": [],424    "stateMutability": "nonpayable",425    "type": "function"426  },427  {428    "inputs": [429      { "internalType": "bool", "name": "enable", "type": "bool" },430      {431        "internalType": "address[]",432        "name": "collections",433        "type": "address[]"434      }435    ],436    "name": "setCollectionNesting",437    "outputs": [],438    "stateMutability": "nonpayable",439    "type": "function"440  },441  {442    "inputs": [443      { "internalType": "string", "name": "key", "type": "string" },444      { "internalType": "bytes", "name": "value", "type": "bytes" }445    ],446    "name": "setCollectionProperty",447    "outputs": [],448    "stateMutability": "nonpayable",449    "type": "function"450  },451  {452    "inputs": [453      { "internalType": "address", "name": "sponsor", "type": "address" }454    ],455    "name": "setCollectionSponsor",456    "outputs": [],457    "stateMutability": "nonpayable",458    "type": "function"459  },460  {461    "inputs": [462      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },463      { "internalType": "string", "name": "key", "type": "string" },464      { "internalType": "bytes", "name": "value", "type": "bytes" }465    ],466    "name": "setProperty",467    "outputs": [],468    "stateMutability": "nonpayable",469    "type": "function"470  },471  {472    "inputs": [473      { "internalType": "string", "name": "key", "type": "string" },474      { "internalType": "bool", "name": "isMutable", "type": "bool" },475      { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },476      { "internalType": "bool", "name": "tokenOwner", "type": "bool" }477    ],478    "name": "setTokenPropertyPermission",479    "outputs": [],480    "stateMutability": "nonpayable",481    "type": "function"482  },483  {484    "inputs": [485      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }486    ],487    "name": "supportsInterface",488    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],489    "stateMutability": "view",490    "type": "function"491  },492  {493    "inputs": [],494    "name": "symbol",495    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],496    "stateMutability": "view",497    "type": "function"498  },499  {500    "inputs": [501      { "internalType": "uint256", "name": "index", "type": "uint256" }502    ],503    "name": "tokenByIndex",504    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],505    "stateMutability": "view",506    "type": "function"507  },508  {509    "inputs": [510      { "internalType": "uint256", "name": "token", "type": "uint256" }511    ],512    "name": "tokenContractAddress",513    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],514    "stateMutability": "view",515    "type": "function"516  },517  {518    "inputs": [519      { "internalType": "address", "name": "owner", "type": "address" },520      { "internalType": "uint256", "name": "index", "type": "uint256" }521    ],522    "name": "tokenOfOwnerByIndex",523    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],524    "stateMutability": "view",525    "type": "function"526  },527  {528    "inputs": [529      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }530    ],531    "name": "tokenURI",532    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],533    "stateMutability": "view",534    "type": "function"535  },536  {537    "inputs": [],538    "name": "totalSupply",539    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],540    "stateMutability": "view",541    "type": "function"542  },543  {544    "inputs": [545      { "internalType": "address", "name": "to", "type": "address" },546      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }547    ],548    "name": "transfer",549    "outputs": [],550    "stateMutability": "nonpayable",551    "type": "function"552  },553  {554    "inputs": [555      { "internalType": "address", "name": "from", "type": "address" },556      { "internalType": "address", "name": "to", "type": "address" },557      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }558    ],559    "name": "transferFrom",560    "outputs": [],561    "stateMutability": "nonpayable",562    "type": "function"563  },564  {565    "inputs": [],566    "name": "uniqueCollectionType",567    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],568    "stateMutability": "view",569    "type": "function"570  }571]
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -70,6 +70,13 @@
   ethereum?: TEthereumAccount;
 }
 
+export interface IEthCrossAccountId {
+  0: TEthereumAccount;
+  1: TSubstrateAccount;
+  field_0: TEthereumAccount;
+  field_1: TSubstrateAccount;
+}
+
 export interface ICollectionLimits {
   accountTokenOwnershipLimit?: number | null;
   sponsoredDataSize?: number | null;
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -7,9 +7,11 @@
 
 import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
 import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';
-import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
+import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';
 import {IKeyringPair} from '@polkadot/types/types';
-import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types';
+import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata, IEthCrossAccountId} from './types';
+import {hexToU8a} from '@polkadot/util/hex';
+import {u8aConcat} from '@polkadot/util/u8a';
 
 export class CrossAccountId implements ICrossAccountId {
   Substrate?: TSubstrateAccount;
@@ -2309,6 +2311,73 @@
 
     return siblingPrefix + encodedParaId + suffix;
   }
+
+  /**
+   * Encode key to substrate address
+   * @param key key for encoding address
+   * @param ss58Format prefix for encoding to the address of the corresponding network
+   * @returns encoded substrate address
+   */
+  encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {
+    const u8a :Uint8Array = typeof key === 'string'
+      ? hexToU8a(key)
+      : typeof key === 'bigint'
+        ? hexToU8a(key.toString(16))
+        : key;
+  
+    if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {
+      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);
+    }
+  
+    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];
+    if (!allowedDecodedLengths.includes(u8a.length)) {
+      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);
+    }
+  
+    const u8aPrefix = ss58Format < 64
+      ? new Uint8Array([ss58Format])
+      : new Uint8Array([
+        ((ss58Format & 0xfc) >> 2) | 0x40,
+        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),
+      ]);
+
+    const input = u8aConcat(u8aPrefix, u8a);
+  
+    return base58Encode(u8aConcat(
+      input,
+      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),
+    ));
+  }
+
+  /**
+   * Restore substrate address from bigint representation
+   * @param number decimal representation of substrate address
+   * @returns substrate address
+   */
+  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {
+    if (this.helper.api === null) {
+      throw 'Not connected';
+    }
+    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();
+    if (res === undefined || res === null) {
+      throw 'Restore address error';
+    }
+    return res.toString();
+  }
+
+  /**
+   * Convert etherium cross account id to substrate cross account id
+   * @param ethCrossAccount etherium cross account
+   * @returns substrate cross account id
+   */
+  convertCrossAccountFromEthCrossAcoount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {
+    if (ethCrossAccount.field_1 === '0') {
+      return {Ethereum: ethCrossAccount.field_0.toLocaleLowerCase()};
+    }
+    
+    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.field_1));
+    return {Substrate: ss58};
+  }
 }
 
 class StakingGroup extends HelperGroup<UniqueHelper> {