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
before · tests/src/eth/fungibleAbi.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": "spender",15        "type": "address"16      },17      {18        "indexed": false,19        "internalType": "uint256",20        "name": "value",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": "from",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "to",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "uint256",45        "name": "value",46        "type": "uint256"47      }48    ],49    "name": "Transfer",50    "type": "event"51  },52  {53    "inputs": [54      { "internalType": "address", "name": "newAdmin", "type": "address" }55    ],56    "name": "addCollectionAdmin",57    "outputs": [],58    "stateMutability": "nonpayable",59    "type": "function"60  },61  {62    "inputs": [63      { "internalType": "address", "name": "user", "type": "address" }64    ],65    "name": "addToCollectionAllowList",66    "outputs": [],67    "stateMutability": "nonpayable",68    "type": "function"69  },70  {71    "inputs": [72      { "internalType": "address", "name": "owner", "type": "address" },73      { "internalType": "address", "name": "spender", "type": "address" }74    ],75    "name": "allowance",76    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],77    "stateMutability": "view",78    "type": "function"79  },80  {81    "inputs": [82      { "internalType": "address", "name": "user", "type": "address" }83    ],84    "name": "allowed",85    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],86    "stateMutability": "view",87    "type": "function"88  },89  {90    "inputs": [91      { "internalType": "address", "name": "spender", "type": "address" },92      { "internalType": "uint256", "name": "amount", "type": "uint256" }93    ],94    "name": "approve",95    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],96    "stateMutability": "nonpayable",97    "type": "function"98  },99  {100    "inputs": [101      { "internalType": "address", "name": "owner", "type": "address" }102    ],103    "name": "balanceOf",104    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],105    "stateMutability": "view",106    "type": "function"107  },108  {109    "inputs": [110      { "internalType": "address", "name": "from", "type": "address" },111      { "internalType": "uint256", "name": "amount", "type": "uint256" }112    ],113    "name": "burnFrom",114    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],115    "stateMutability": "nonpayable",116    "type": "function"117  },118  {119    "inputs": [120      { "internalType": "address", "name": "newOwner", "type": "address" }121    ],122    "name": "changeCollectionOwner",123    "outputs": [],124    "stateMutability": "nonpayable",125    "type": "function"126  },127  {128    "inputs": [],129    "name": "collectionOwner",130    "outputs": [131      {132        "components": [133          { "internalType": "address", "name": "field_0", "type": "address" },134          { "internalType": "uint256", "name": "field_1", "type": "uint256" }135        ],136        "internalType": "struct Tuple6",137        "name": "",138        "type": "tuple"139      }140    ],141    "stateMutability": "view",142    "type": "function"143  },144  {145    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],146    "name": "collectionProperty",147    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],148    "stateMutability": "view",149    "type": "function"150  },151  {152    "inputs": [],153    "name": "collectionSponsor",154    "outputs": [155      {156        "components": [157          { "internalType": "address", "name": "field_0", "type": "address" },158          { "internalType": "uint256", "name": "field_1", "type": "uint256" }159        ],160        "internalType": "struct Tuple6",161        "name": "",162        "type": "tuple"163      }164    ],165    "stateMutability": "view",166    "type": "function"167  },168  {169    "inputs": [],170    "name": "confirmCollectionSponsorship",171    "outputs": [],172    "stateMutability": "nonpayable",173    "type": "function"174  },175  {176    "inputs": [],177    "name": "contractAddress",178    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],179    "stateMutability": "view",180    "type": "function"181  },182  {183    "inputs": [],184    "name": "decimals",185    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],186    "stateMutability": "view",187    "type": "function"188  },189  {190    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],191    "name": "deleteCollectionProperty",192    "outputs": [],193    "stateMutability": "nonpayable",194    "type": "function"195  },196  {197    "inputs": [],198    "name": "hasCollectionPendingSponsor",199    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],200    "stateMutability": "view",201    "type": "function"202  },203  {204    "inputs": [205      { "internalType": "address", "name": "user", "type": "address" }206    ],207    "name": "isOwnerOrAdmin",208    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],209    "stateMutability": "view",210    "type": "function"211  },212  {213    "inputs": [214      { "internalType": "address", "name": "to", "type": "address" },215      { "internalType": "uint256", "name": "amount", "type": "uint256" }216    ],217    "name": "mint",218    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],219    "stateMutability": "nonpayable",220    "type": "function"221  },222  {223    "inputs": [224      {225        "components": [226          { "internalType": "address", "name": "field_0", "type": "address" },227          { "internalType": "uint256", "name": "field_1", "type": "uint256" }228        ],229        "internalType": "struct Tuple6[]",230        "name": "amounts",231        "type": "tuple[]"232      }233    ],234    "name": "mintBulk",235    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],236    "stateMutability": "nonpayable",237    "type": "function"238  },239  {240    "inputs": [],241    "name": "name",242    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],243    "stateMutability": "view",244    "type": "function"245  },246  {247    "inputs": [248      { "internalType": "address", "name": "admin", "type": "address" }249    ],250    "name": "removeCollectionAdmin",251    "outputs": [],252    "stateMutability": "nonpayable",253    "type": "function"254  },255  {256    "inputs": [],257    "name": "removeCollectionSponsor",258    "outputs": [],259    "stateMutability": "nonpayable",260    "type": "function"261  },262  {263    "inputs": [264      { "internalType": "address", "name": "user", "type": "address" }265    ],266    "name": "removeFromCollectionAllowList",267    "outputs": [],268    "stateMutability": "nonpayable",269    "type": "function"270  },271  {272    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],273    "name": "setCollectionAccess",274    "outputs": [],275    "stateMutability": "nonpayable",276    "type": "function"277  },278  {279    "inputs": [280      { "internalType": "string", "name": "limit", "type": "string" },281      { "internalType": "uint32", "name": "value", "type": "uint32" }282    ],283    "name": "setCollectionLimit",284    "outputs": [],285    "stateMutability": "nonpayable",286    "type": "function"287  },288  {289    "inputs": [290      { "internalType": "string", "name": "limit", "type": "string" },291      { "internalType": "bool", "name": "value", "type": "bool" }292    ],293    "name": "setCollectionLimit",294    "outputs": [],295    "stateMutability": "nonpayable",296    "type": "function"297  },298  {299    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],300    "name": "setCollectionMintMode",301    "outputs": [],302    "stateMutability": "nonpayable",303    "type": "function"304  },305  {306    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],307    "name": "setCollectionNesting",308    "outputs": [],309    "stateMutability": "nonpayable",310    "type": "function"311  },312  {313    "inputs": [314      { "internalType": "bool", "name": "enable", "type": "bool" },315      {316        "internalType": "address[]",317        "name": "collections",318        "type": "address[]"319      }320    ],321    "name": "setCollectionNesting",322    "outputs": [],323    "stateMutability": "nonpayable",324    "type": "function"325  },326  {327    "inputs": [328      { "internalType": "string", "name": "key", "type": "string" },329      { "internalType": "bytes", "name": "value", "type": "bytes" }330    ],331    "name": "setCollectionProperty",332    "outputs": [],333    "stateMutability": "nonpayable",334    "type": "function"335  },336  {337    "inputs": [338      { "internalType": "address", "name": "sponsor", "type": "address" }339    ],340    "name": "setCollectionSponsor",341    "outputs": [],342    "stateMutability": "nonpayable",343    "type": "function"344  },345  {346    "inputs": [347      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }348    ],349    "name": "supportsInterface",350    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],351    "stateMutability": "view",352    "type": "function"353  },354  {355    "inputs": [],356    "name": "symbol",357    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],358    "stateMutability": "view",359    "type": "function"360  },361  {362    "inputs": [],363    "name": "totalSupply",364    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],365    "stateMutability": "view",366    "type": "function"367  },368  {369    "inputs": [370      { "internalType": "address", "name": "to", "type": "address" },371      { "internalType": "uint256", "name": "amount", "type": "uint256" }372    ],373    "name": "transfer",374    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],375    "stateMutability": "nonpayable",376    "type": "function"377  },378  {379    "inputs": [380      { "internalType": "address", "name": "from", "type": "address" },381      { "internalType": "address", "name": "to", "type": "address" },382      { "internalType": "uint256", "name": "amount", "type": "uint256" }383    ],384    "name": "transferFrom",385    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],386    "stateMutability": "nonpayable",387    "type": "function"388  },389  {390    "inputs": [],391    "name": "uniqueCollectionType",392    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],393    "stateMutability": "view",394    "type": "function"395  }396]
after · tests/src/eth/fungibleAbi.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": "spender",15        "type": "address"16      },17      {18        "indexed": false,19        "internalType": "uint256",20        "name": "value",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": "from",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "to",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "uint256",45        "name": "value",46        "type": "uint256"47      }48    ],49    "name": "Transfer",50    "type": "event"51  },52  {53    "inputs": [54      { "internalType": "address", "name": "newAdmin", "type": "address" }55    ],56    "name": "addCollectionAdmin",57    "outputs": [],58    "stateMutability": "nonpayable",59    "type": "function"60  },61  {62    "inputs": [63      { "internalType": "address", "name": "user", "type": "address" }64    ],65    "name": "addToCollectionAllowList",66    "outputs": [],67    "stateMutability": "nonpayable",68    "type": "function"69  },70  {71    "inputs": [72      { "internalType": "address", "name": "owner", "type": "address" },73      { "internalType": "address", "name": "spender", "type": "address" }74    ],75    "name": "allowance",76    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],77    "stateMutability": "view",78    "type": "function"79  },80  {81    "inputs": [82      { "internalType": "address", "name": "user", "type": "address" }83    ],84    "name": "allowed",85    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],86    "stateMutability": "view",87    "type": "function"88  },89  {90    "inputs": [91      { "internalType": "address", "name": "spender", "type": "address" },92      { "internalType": "uint256", "name": "amount", "type": "uint256" }93    ],94    "name": "approve",95    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],96    "stateMutability": "nonpayable",97    "type": "function"98  },99  {100    "inputs": [101      { "internalType": "address", "name": "owner", "type": "address" }102    ],103    "name": "balanceOf",104    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],105    "stateMutability": "view",106    "type": "function"107  },108  {109    "inputs": [110      { "internalType": "address", "name": "from", "type": "address" },111      { "internalType": "uint256", "name": "amount", "type": "uint256" }112    ],113    "name": "burnFrom",114    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],115    "stateMutability": "nonpayable",116    "type": "function"117  },118  {119    "inputs": [120      { "internalType": "address", "name": "newOwner", "type": "address" }121    ],122    "name": "changeCollectionOwner",123    "outputs": [],124    "stateMutability": "nonpayable",125    "type": "function"126  },127  {128    "inputs": [],129    "name": "collectionAdmins",130    "outputs": [131      {132        "components": [133          { "internalType": "address", "name": "field_0", "type": "address" },134          { "internalType": "uint256", "name": "field_1", "type": "uint256" }135        ],136        "internalType": "struct Tuple6[]",137        "name": "",138        "type": "tuple[]"139      }140    ],141    "stateMutability": "view",142    "type": "function"143  },144  {145    "inputs": [],146    "name": "collectionOwner",147    "outputs": [148      {149        "components": [150          { "internalType": "address", "name": "field_0", "type": "address" },151          { "internalType": "uint256", "name": "field_1", "type": "uint256" }152        ],153        "internalType": "struct Tuple6",154        "name": "",155        "type": "tuple"156      }157    ],158    "stateMutability": "view",159    "type": "function"160  },161  {162    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],163    "name": "collectionProperty",164    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],165    "stateMutability": "view",166    "type": "function"167  },168  {169    "inputs": [],170    "name": "collectionSponsor",171    "outputs": [172      {173        "components": [174          { "internalType": "address", "name": "field_0", "type": "address" },175          { "internalType": "uint256", "name": "field_1", "type": "uint256" }176        ],177        "internalType": "struct Tuple6",178        "name": "",179        "type": "tuple"180      }181    ],182    "stateMutability": "view",183    "type": "function"184  },185  {186    "inputs": [],187    "name": "confirmCollectionSponsorship",188    "outputs": [],189    "stateMutability": "nonpayable",190    "type": "function"191  },192  {193    "inputs": [],194    "name": "contractAddress",195    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],196    "stateMutability": "view",197    "type": "function"198  },199  {200    "inputs": [],201    "name": "decimals",202    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],203    "stateMutability": "view",204    "type": "function"205  },206  {207    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],208    "name": "deleteCollectionProperty",209    "outputs": [],210    "stateMutability": "nonpayable",211    "type": "function"212  },213  {214    "inputs": [],215    "name": "hasCollectionPendingSponsor",216    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],217    "stateMutability": "view",218    "type": "function"219  },220  {221    "inputs": [222      { "internalType": "address", "name": "user", "type": "address" }223    ],224    "name": "isOwnerOrAdmin",225    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],226    "stateMutability": "view",227    "type": "function"228  },229  {230    "inputs": [231      { "internalType": "address", "name": "to", "type": "address" },232      { "internalType": "uint256", "name": "amount", "type": "uint256" }233    ],234    "name": "mint",235    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],236    "stateMutability": "nonpayable",237    "type": "function"238  },239  {240    "inputs": [241      {242        "components": [243          { "internalType": "address", "name": "field_0", "type": "address" },244          { "internalType": "uint256", "name": "field_1", "type": "uint256" }245        ],246        "internalType": "struct Tuple6[]",247        "name": "amounts",248        "type": "tuple[]"249      }250    ],251    "name": "mintBulk",252    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],253    "stateMutability": "nonpayable",254    "type": "function"255  },256  {257    "inputs": [],258    "name": "name",259    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],260    "stateMutability": "view",261    "type": "function"262  },263  {264    "inputs": [265      { "internalType": "address", "name": "admin", "type": "address" }266    ],267    "name": "removeCollectionAdmin",268    "outputs": [],269    "stateMutability": "nonpayable",270    "type": "function"271  },272  {273    "inputs": [],274    "name": "removeCollectionSponsor",275    "outputs": [],276    "stateMutability": "nonpayable",277    "type": "function"278  },279  {280    "inputs": [281      { "internalType": "address", "name": "user", "type": "address" }282    ],283    "name": "removeFromCollectionAllowList",284    "outputs": [],285    "stateMutability": "nonpayable",286    "type": "function"287  },288  {289    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],290    "name": "setCollectionAccess",291    "outputs": [],292    "stateMutability": "nonpayable",293    "type": "function"294  },295  {296    "inputs": [297      { "internalType": "string", "name": "limit", "type": "string" },298      { "internalType": "uint32", "name": "value", "type": "uint32" }299    ],300    "name": "setCollectionLimit",301    "outputs": [],302    "stateMutability": "nonpayable",303    "type": "function"304  },305  {306    "inputs": [307      { "internalType": "string", "name": "limit", "type": "string" },308      { "internalType": "bool", "name": "value", "type": "bool" }309    ],310    "name": "setCollectionLimit",311    "outputs": [],312    "stateMutability": "nonpayable",313    "type": "function"314  },315  {316    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],317    "name": "setCollectionMintMode",318    "outputs": [],319    "stateMutability": "nonpayable",320    "type": "function"321  },322  {323    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],324    "name": "setCollectionNesting",325    "outputs": [],326    "stateMutability": "nonpayable",327    "type": "function"328  },329  {330    "inputs": [331      { "internalType": "bool", "name": "enable", "type": "bool" },332      {333        "internalType": "address[]",334        "name": "collections",335        "type": "address[]"336      }337    ],338    "name": "setCollectionNesting",339    "outputs": [],340    "stateMutability": "nonpayable",341    "type": "function"342  },343  {344    "inputs": [345      { "internalType": "string", "name": "key", "type": "string" },346      { "internalType": "bytes", "name": "value", "type": "bytes" }347    ],348    "name": "setCollectionProperty",349    "outputs": [],350    "stateMutability": "nonpayable",351    "type": "function"352  },353  {354    "inputs": [355      { "internalType": "address", "name": "sponsor", "type": "address" }356    ],357    "name": "setCollectionSponsor",358    "outputs": [],359    "stateMutability": "nonpayable",360    "type": "function"361  },362  {363    "inputs": [364      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }365    ],366    "name": "supportsInterface",367    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],368    "stateMutability": "view",369    "type": "function"370  },371  {372    "inputs": [],373    "name": "symbol",374    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],375    "stateMutability": "view",376    "type": "function"377  },378  {379    "inputs": [],380    "name": "totalSupply",381    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],382    "stateMutability": "view",383    "type": "function"384  },385  {386    "inputs": [387      { "internalType": "address", "name": "to", "type": "address" },388      { "internalType": "uint256", "name": "amount", "type": "uint256" }389    ],390    "name": "transfer",391    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],392    "stateMutability": "nonpayable",393    "type": "function"394  },395  {396    "inputs": [397      { "internalType": "address", "name": "from", "type": "address" },398      { "internalType": "address", "name": "to", "type": "address" },399      { "internalType": "uint256", "name": "amount", "type": "uint256" }400    ],401    "name": "transferFrom",402    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],403    "stateMutability": "nonpayable",404    "type": "function"405  },406  {407    "inputs": [],408    "name": "uniqueCollectionType",409    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],410    "stateMutability": "view",411    "type": "function"412  }413]
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
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.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/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> {