git.delta.rocks / unique-network / refs/commits / 876ef621bd1e

difftreelog

feat add conditional supportInterface for ERC721Metadata

Grigoriy Simonov2022-09-13parent: #ba7ab8a.patch.diff
in: master

29 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -684,6 +684,11 @@
 		pub fn parent_nft() -> up_data_structs::PropertyKey {
 			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)
 		}
+
+		/// Key "parentNft".
+		pub fn erc721_metadata() -> up_data_structs::PropertyKey {
+			property_key_from_bytes(b"ERC721Metadata").expect(EXPECT_CONVERT_ERROR)
+		}
 	}
 
 	/// Values.
@@ -693,10 +698,21 @@
 		/// Value "ERC721Metadata".
 		pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";
 
+		/// Value "1" ERC721 metadata supported.
+		pub const ERC721_METADATA_SUPPORTED: &[u8] = b"1";
+
+		/// Value "0" ERC721 metadata supported.
+		pub const ERC721_METADATA_UNSUPPORTED: &[u8] = b"0";
+
 		/// Value for [`ERC721_METADATA`].
 		pub fn erc721() -> up_data_structs::PropertyValue {
 			property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)
 		}
+
+		/// Value for [`ERC721_METADATA`].
+		pub fn erc721_metadata_supported() -> up_data_structs::PropertyValue {
+			property_value_from_bytes(ERC721_METADATA_SUPPORTED).expect(EXPECT_CONVERT_ERROR)
+		}
 	}
 
 	/// Convert `byte` to [`PropertyKey`].
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -232,7 +232,7 @@
 			if !url.is_empty() {
 				return Ok(url);
 			}
-		} else if !is_erc721_metadata_compatible::<T>(self.id) {
+		} else if !self.supports_metadata() {
 			return Err("tokenURI not set".into());
 		}
 
@@ -548,17 +548,6 @@
 	}
 
 	Err("Property tokenURI not found".into())
-}
-
-fn is_erc721_metadata_compatible<T: Config>(collection_id: CollectionId) -> bool {
-	if let Some(shema_name) =
-		pallet_common::Pallet::<T>::get_collection_property(collection_id, &key::schema_name())
-	{
-		let shema_name = shema_name.into_inner();
-		shema_name == property_value::ERC721_METADATA
-	} else {
-		false
-	}
 }
 
 fn get_token_permission<T: Config>(
@@ -577,16 +566,6 @@
 	Ok(a)
 }
 
-fn has_token_permission<T: Config>(collection_id: CollectionId, key: &PropertyKey) -> bool {
-	if let Ok(token_property_permissions) =
-		CollectionPropertyPermissions::<T>::try_get(collection_id)
-	{
-		return token_property_permissions.contains_key(key);
-	}
-
-	false
-}
-
 /// @title Unique extensions for ERC721.
 #[solidity_interface(name = ERC721UniqueExtensions)]
 impl<T: Config> NonfungibleHandle<T> {
@@ -731,7 +710,7 @@
 	name = UniqueNFT,
 	is(
 		ERC721,
-		ERC721Metadata,
+		ERC721Metadata(if(this.supports_metadata())),
 		ERC721Enumerable,
 		ERC721UniqueExtensions,
 		ERC721Mintable,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -108,6 +108,7 @@
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
+	erc::static_property::{key, value},
 	eth::collection_id_to_address,
 };
 use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
@@ -295,6 +296,19 @@
 		&mut self.0
 	}
 }
+
+impl<T: Config> NonfungibleHandle<T> {
+	pub fn supports_metadata(&self) -> bool {
+		if let Some(erc721_metadata) =
+			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
+		{
+			*erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
+		} else {
+			false
+		}
+	}
+}
+
 impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {
 	fn recorder(&self) -> &SubstrateRecorder<T> {
 		self.0.recorder()
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -228,7 +228,7 @@
 			if !url.is_empty() {
 				return Ok(url);
 			}
-		} else if !is_erc721_metadata_compatible::<T>(self.id) {
+		} else if !self.supports_metadata() {
 			return Err("tokenURI not set".into());
 		}
 
@@ -578,17 +578,6 @@
 	Err("Property tokenURI not found".into())
 }
 
-fn is_erc721_metadata_compatible<T: Config>(collection_id: CollectionId) -> bool {
-	if let Some(shema_name) =
-		pallet_common::Pallet::<T>::get_collection_property(collection_id, &key::schema_name())
-	{
-		let shema_name = shema_name.into_inner();
-		shema_name == property_value::ERC721_METADATA
-	} else {
-		false
-	}
-}
-
 fn get_token_permission<T: Config>(
 	collection_id: CollectionId,
 	key: &PropertyKey,
@@ -780,7 +769,7 @@
 	name = UniqueRefungible,
 	is(
 		ERC721,
-		ERC721Metadata,
+		ERC721Metadata(if(this.supports_metadata())),
 		ERC721Enumerable,
 		ERC721UniqueExtensions,
 		ERC721Mintable,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -92,14 +92,19 @@
 
 use codec::{Encode, Decode, MaxEncodedLen};
 use core::ops::Deref;
+use derivative::Derivative;
 use evm_coder::ToLog;
 use frame_support::{
-	BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,
+	BoundedBTreeMap, BoundedVec, ensure, fail, storage::with_transaction, transactional,
+	pallet_prelude::ConstU32,
 };
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_evm_coder_substrate::WithRecorder;
 use pallet_common::{
-	CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,
+	CommonCollectionOperations,
+	erc::static_property::{key, value},
+	Error as CommonError,
+	eth::collection_id_to_address,
 	Event as CommonEvent, Pallet as PalletCommon,
 };
 use pallet_structure::Pallet as PalletStructure;
@@ -113,8 +118,6 @@
 	MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,
 	PropertyScope, PropertyValue, TokenId, TrySetProperty,
 };
-use frame_support::BoundedBTreeMap;
-use derivative::Derivative;
 
 pub use pallet::*;
 #[cfg(feature = "runtime-benchmarks")]
@@ -301,6 +304,18 @@
 	}
 }
 
+impl<T: Config> RefungibleHandle<T> {
+	pub fn supports_metadata(&self) -> bool {
+		if let Some(erc721_metadata) =
+			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
+		{
+			*erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
+		} else {
+			false
+		}
+	}
+}
+
 impl<T: Config> Deref for RefungibleHandle<T> {
 	type Target = pallet_common::CollectionHandle<T>;
 
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -130,6 +130,13 @@
 			})
 			.map_err(|e| Error::Revert(format!("{:?}", e)))?;
 
+		properties
+			.try_push(up_data_structs::Property {
+				key: key::erc721_metadata(),
+				value: property_value::erc721_metadata_supported(),
+			})
+			.map_err(|e| Error::Revert(format!("{:?}", e)))?;
+
 		if !base_uri_value.is_empty() {
 			properties
 				.try_push(up_data_structs::Property {
@@ -212,7 +219,8 @@
 	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
 	/// @return address Address of the newly created collection
 	#[weight(<SelfWeightOf<T>>::create_collection())]
-	fn create_nonfungible_collection(
+	#[solidity(rename_selector = "createNFTCollection")]
+	fn create_nft_collection(
 		&mut self,
 		caller: caller,
 		value: value,
@@ -239,9 +247,26 @@
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
 	}
+	/// Create an NFT collection
+	/// @param name Name of the collection
+	/// @param description Informative description of the collection
+	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+	/// @return address Address of the newly created collection
+	#[weight(<SelfWeightOf<T>>::create_collection())]
+	#[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]
+	fn create_nonfungible_collection(
+		&mut self,
+		caller: caller,
+		value: value,
+		name: string,
+		description: string,
+		token_prefix: string,
+	) -> Result<address> {
+		self.create_nft_collection(caller, value, name, description, token_prefix)
+	}
 
 	#[weight(<SelfWeightOf<T>>::create_collection())]
-	#[solidity(rename_selector = "createERC721MetadataCompatibleCollection")]
+	#[solidity(rename_selector = "createERC721MetadataNFTCollection")]
 	fn create_nonfungible_collection_with_properties(
 		&mut self,
 		caller: caller,
@@ -273,6 +298,27 @@
 
 	#[weight(<SelfWeightOf<T>>::create_collection())]
 	#[solidity(rename_selector = "createRFTCollection")]
+	fn create_rft_collection(
+		&mut self,
+		caller: caller,
+		value: value,
+		name: string,
+		description: string,
+		token_prefix: string,
+	) -> Result<address> {
+		create_refungible_collection_internal::<T>(
+			caller,
+			value,
+			name,
+			description,
+			token_prefix,
+			Default::default(),
+			false,
+		)
+	}
+
+	#[weight(<SelfWeightOf<T>>::create_collection())]
+	#[deprecated(note = "mathod was renamed to `create_rft_collection`, prefer it instead")]
 	fn create_refungible_collection(
 		&mut self,
 		caller: caller,
@@ -293,7 +339,7 @@
 	}
 
 	#[weight(<SelfWeightOf<T>>::create_collection())]
-	#[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]
+	#[solidity(rename_selector = "createERC721MetadataRFTCollection")]
 	fn create_refungible_collection_with_properties(
 		&mut self,
 		caller: caller,
modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -23,13 +23,33 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x5ad4f440
+/// @dev the ERC-165 identifier for this interface is 0xf62c7aa9
 contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create an NFT collection
 	/// @param name Name of the collection
 	/// @param description Informative description of the collection
 	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
 	/// @return address Address of the newly created collection
+	/// @dev EVM selector for this function is: 0x844af658,
+	///  or in textual repr: createNFTCollection(string,string,string)
+	function createNFTCollection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) public payable returns (address) {
+		require(false, stub_error);
+		name;
+		description;
+		tokenPrefix;
+		dummy = 0;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	/// Create an NFT collection
+	/// @param name Name of the collection
+	/// @param description Informative description of the collection
+	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+	/// @return address Address of the newly created collection
 	/// @dev EVM selector for this function is: 0xe34a6844,
 	///  or in textual repr: createNonfungibleCollection(string,string,string)
 	function createNonfungibleCollection(
@@ -45,9 +65,9 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	/// @dev EVM selector for this function is: 0xa634a5f9,
-	///  or in textual repr: createERC721MetadataCompatibleCollection(string,string,string,string)
-	function createERC721MetadataCompatibleCollection(
+	/// @dev EVM selector for this function is: 0xd1df968c,
+	///  or in textual repr: createERC721MetadataNFTCollection(string,string,string,string)
+	function createERC721MetadataNFTCollection(
 		string memory name,
 		string memory description,
 		string memory tokenPrefix,
@@ -77,9 +97,24 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	/// @dev EVM selector for this function is: 0xa5596388,
-	///  or in textual repr: createERC721MetadataCompatibleRFTCollection(string,string,string,string)
-	function createERC721MetadataCompatibleRFTCollection(
+	/// @dev EVM selector for this function is: 0x44a68ad5,
+	///  or in textual repr: createRefungibleCollection(string,string,string)
+	function createRefungibleCollection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) public payable returns (address) {
+		require(false, stub_error);
+		name;
+		description;
+		tokenPrefix;
+		dummy = 0;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	/// @dev EVM selector for this function is: 0xbea6a299,
+	///  or in textual repr: createERC721MetadataRFTCollection(string,string,string,string)
+	function createERC721MetadataRFTCollection(
 		string memory name,
 		string memory description,
 		string memory tokenPrefix,
modifiedtests/src/deprecated-helpers/eth/helpers.tsdiffbeforeafterboth
before · tests/src/deprecated-helpers/eth/helpers.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// eslint-disable-next-line @typescript-eslint/triple-slash-reference18/// <reference path="helpers.d.ts" />1920import {ApiPromise} from '@polkadot/api';21import {IKeyringPair} from '@polkadot/types/types';22import {addressToEvm, evmToAddress} from '@polkadot/util-crypto';23import {expect} from 'chai';24import * as solc from 'solc';25import Web3 from 'web3';26import config from '../../config';27import getBalance from '../../substrate/get-balance';28import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api';29import waitNewBlocks from '../../substrate/wait-new-blocks';30import {CollectionMode, CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../helpers';31import collectionHelpersAbi from '../../eth/collectionHelpersAbi.json';32import fungibleAbi from '../../eth/fungibleAbi.json';33import nonFungibleAbi from '../../eth/nonFungibleAbi.json';34import refungibleAbi from '../../eth/reFungibleAbi.json';35import refungibleTokenAbi from '../../eth/reFungibleTokenAbi.json';36import contractHelpersAbi from '../../eth/util/contractHelpersAbi.json';3738export const GAS_ARGS = {gas: 2500000};3940export enum SponsoringMode {41  Disabled = 0,42  Allowlisted = 1,43  Generous = 2,44}4546let web3Connected = false;47export async function usingWeb3<T>(cb: (web3: Web3) => Promise<T> | T): Promise<T> {48  if (web3Connected) throw new Error('do not nest usingWeb3 calls');49  web3Connected = true;5051  const provider = new Web3.providers.WebsocketProvider(config.substrateUrl);52  const web3 = new Web3(provider);5354  try {55    return await cb(web3);56  } finally {57    // provider.disconnect(3000, 'normal disconnect');58    provider.connection.close();59    web3Connected = false;60  }61}6263function encodeIntBE(v: number): number[] {64  if (v >= 0xffffffff || v < 0) throw new Error('id overflow');65  return [66    v >> 24,67    (v >> 16) & 0xff,68    (v >> 8) & 0xff,69    v & 0xff,70  ];71}7273export async function getCollectionAddressFromResult(api: ApiPromise, result: any) {74  const collectionIdAddress = normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);75  const collectionId = collectionIdFromAddress(collectionIdAddress);  76  const collection = (await getDetailedCollectionInfo(api, collectionId))!;77  return {collectionIdAddress, collectionId, collection};78}7980export function collectionIdToAddress(collection: number): string {81  const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,82    ...encodeIntBE(collection),83  ]);84  return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));85}86export function collectionIdFromAddress(address: string): number {87  if (!address.startsWith('0x'))88    throw 'address not starts with "0x"';89  if (address.length > 42)90    throw 'address length is more than 20 bytes';91  return Number('0x' + address.substring(address.length - 8));92}93  94export function normalizeAddress(address: string): string {95  return '0x' + address.substring(address.length - 40);96}9798export function tokenIdToAddress(collection: number, token: number): string {99  const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,100    ...encodeIntBE(collection),101    ...encodeIntBE(token),102  ]);103  return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));104}105106export function tokenIdFromAddress(address: string) {107  if (!address.startsWith('0x'))108    throw 'address not starts with "0x"';109  if (address.length > 42)110    throw 'address length is more than 20 bytes';111  return {112    collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)),113    tokenId: Number('0x' + address.substring(address.length - 8)),114  };115}116117export function tokenIdToCross(collection: number, token: number): CrossAccountId {118  return {119    Ethereum: tokenIdToAddress(collection, token),120  };121}122123export function createEthAccount(web3: Web3) {124  const account = web3.eth.accounts.create();125  web3.eth.accounts.wallet.add(account.privateKey);126  return account.address;127}128129export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) {130  const alice = privateKeyWrapper('//Alice');131  const account = createEthAccount(web3);132  await transferBalanceToEth(api, alice, account);133134  return account;135}136137export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) {138  const tx = api.tx.balances.transfer(evmToAddress(target), amount);139  const events = await submitTransactionAsync(source, tx);140  const result = getGenericResult(events);141  expect(result.success).to.be.true;142}143144export async function createRFTCollection(api: ApiPromise, web3: Web3, owner: string) {145  const collectionHelper = evmCollectionHelpers(web3, owner);146  const result = await collectionHelper.methods147    .createRFTCollection('A', 'B', 'C')148    .send({value: Number(2n * UNIQUE)});149  return await getCollectionAddressFromResult(api, result);150}151152153export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) {154  const collectionHelper = evmCollectionHelpers(web3, owner);155  const result = await collectionHelper.methods156    .createNonfungibleCollection('A', 'B', 'C')157    .send({value: Number(2n * UNIQUE)});158  return await getCollectionAddressFromResult(api, result);159}160161export function uniqueNFT(web3: Web3, address: string, owner: string) {162  return new web3.eth.Contract(nonFungibleAbi as any, address, {163    from: owner,164    ...GAS_ARGS,165  });166}167168export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) {169  return new web3.eth.Contract(refungibleAbi as any, collectionAddress, {170    from: owner,171    ...GAS_ARGS,172  });173}174175export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) {176  return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, {177    from: owner,178    ...GAS_ARGS,179  });180}181182export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) {183  let i: any = it;184  if (opts.only) i = i.only;185  else if (opts.skip) i = i.skip;186  i(name, async () => {187    await usingApi(async (api, privateKeyWrapper) => {188      await usingWeb3(async web3 => {189        await cb({api, web3, privateKeyWrapper});190      });191    });192  });193}194itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {only: true});195itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {skip: true});196197export async function generateSubstrateEthPair(web3: Web3) {198  const account = web3.eth.accounts.create();199  evmToAddress(account.address);200}201202type NormalizedEvent = {203    address: string,204    event: string,205    args: { [key: string]: string }206};207208export function normalizeEvents(events: any): NormalizedEvent[] {209  const output = [];210  for (const key of Object.keys(events)) {211    if (key.match(/^[0-9]+$/)) {212      output.push(events[key]);213    } else if (Array.isArray(events[key])) {214      output.push(...events[key]);215    } else {216      output.push(events[key]);217    }218  }219  output.sort((a, b) => a.logIndex - b.logIndex);220  return output.map(({address, event, returnValues}) => {221    const args: { [key: string]: string } = {};222    for (const key of Object.keys(returnValues)) {223      if (!key.match(/^[0-9]+$/)) {224        args[key] = returnValues[key];225      }226    }227    return {228      address,229      event,230      args,231    };232  });233}234235export async function recordEvents(contract: any, action: () => Promise<void>): Promise<NormalizedEvent[]> {236  const out: any = [];237  contract.events.allEvents((_: any, event: any) => {238    out.push(event);239  });240  await action();241  return normalizeEvents(out);242}243244export function subToEthLowercase(eth: string): string {245  const bytes = addressToEvm(eth);246  return '0x' + Buffer.from(bytes).toString('hex');247}248249export function subToEth(eth: string): string {250  return Web3.utils.toChecksumAddress(subToEthLowercase(eth));251}252253export interface CompiledContract {254  abi: any,255  object: string,256}257258export function compileContract(name: string, src: string) : CompiledContract {259  const out = JSON.parse(solc.compile(JSON.stringify({260    language: 'Solidity',261    sources: {262      [`${name}.sol`]: {263        content: `264          // SPDX-License-Identifier: UNLICENSED265          pragma solidity ^0.8.6;266267          ${src}268        `,269      },270    },271    settings: {272      outputSelection: {273        '*': {274          '*': ['*'],275        },276      },277    },278  }))).contracts[`${name}.sol`][name];279280  return {281    abi: out.abi,282    object: '0x' + out.evm.bytecode.object,283  };284}285286export async function deployFlipper(web3: Web3, deployer: string) {287  const compiled = compileContract('Flipper', `288    contract Flipper {289      bool value = false;290      function flip() public {291        value = !value;292      }293      function getValue() public view returns (bool) {294        return value;295      }296    }297  `);298  const flipperContract = new web3.eth.Contract(compiled.abi, undefined, {299    data: compiled.object,300    from: deployer,301    ...GAS_ARGS,302  });303  const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer});304305  return flipper;306}307308export async function deployCollector(web3: Web3, deployer: string) {309  const compiled = compileContract('Collector', `310    contract Collector {311      uint256 collected;312      fallback() external payable {313        giveMoney();314      }315      function giveMoney() public payable {316        collected += msg.value;317      }318      function getCollected() public view returns (uint256) {319        return collected;320      }321      function getUnaccounted() public view returns (uint256) {322        return address(this).balance - collected;323      }324325      function withdraw(address payable target) public {326        target.transfer(collected);327        collected = 0;328      }329    }330  `);331  const collectorContract = new web3.eth.Contract(compiled.abi, undefined, {332    data: compiled.object,333    from: deployer,334    ...GAS_ARGS,335  });336  const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer});337338  return collector;339}340341/** 342 * pallet evm_contract_helpers343 * @param web3 344 * @param caller - eth address345 * @returns 346 */347export function contractHelpers(web3: Web3, caller: string) {348  return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});349}350351/** 352 * evm collection helper353 * @param web3 354 * @param caller - eth address355 * @returns 356 */357export function evmCollectionHelpers(web3: Web3, caller: string) {358  return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS});359}360361/** 362 * evm collection363 * @param web3 364 * @param caller - eth address365 * @returns 366 */367export function evmCollection(web3: Web3, caller: string, collection: string, mode: CollectionMode = {type: 'NFT'}) {368  let abi;369  switch (mode.type) {370    case 'Fungible':371      abi = fungibleAbi;372      break;373    374    case 'NFT':375      abi = nonFungibleAbi;376      break;377    378    case 'ReFungible':379      abi = refungibleAbi;380      break;381382    default:383      throw 'Bad collection mode';384  }385  const contract = new web3.eth.Contract(abi as any, collection, {from: caller, ...GAS_ARGS});386  return contract;387}388389/**390 * Execute ethereum method call using substrate account391 * @param to target contract392 * @param mkTx - closure, receiving `contract.methods`, and returning method call,393 * to be used as following (assuming `to` = erc20 contract):394 * `m => m.transfer(to, amount)`395 *396 * # Example397 * ```ts398 * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount));399 * ```400 */401export async function executeEthTxOnSub(web3: Web3, api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) {402  const tx = api.tx.evm.call(403    subToEth(from.address),404    to.options.address,405    mkTx(to.methods).encodeABI(),406    value,407    GAS_ARGS.gas,408    await web3.eth.getGasPrice(),409    null,410    null,411    [],412  );413  const events = await submitTransactionAsync(from, tx);414  expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true;415}416417export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise<bigint> {418  return (await getBalance(api, [evmToAddress(address)]))[0];419}420421/**422 * Measure how much gas given closure consumes423 *424 * @param user which user balance will be checked425 */426export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise<any>): Promise<bigint> {427  const before = await ethBalanceViaSub(api, user);428429  await call();430431  // In dev mode, the transaction might not finish processing in time432  await waitNewBlocks(api, 1);433  const after = await ethBalanceViaSub(api, user);434435  // Can't use .to.be.less, because chai doesn't supports bigint436  expect(after < before).to.be.true;437438  return before - after;439}440441type ElementOf<A> = A extends readonly (infer T)[] ? T : never;442// I want a fancier api, not a memory efficiency443export function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {444  if(args.length === 0) {445    yield internalRest as any;446    return;447  }448  for(const value of args[0]) {449    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;450  }451}
modifiedtests/src/deprecated-helpers/helpers.tsdiffbeforeafterboth
--- a/tests/src/deprecated-helpers/helpers.ts
+++ b/tests/src/deprecated-helpers/helpers.ts
@@ -433,6 +433,7 @@
   mode: {type: 'NFT'},
   name: 'name',
   tokenPrefix: 'prefix',
+  properties: [{key: 'ERC721Metadata', value: '1'}],
 };
 
 export async function
@@ -441,7 +442,7 @@
   sender: IKeyringPair,
   params: Partial<CreateCollectionParams> = {},
 ): Promise<CreateCollectionResult> {
-  const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};
+  const {name, description, mode, tokenPrefix, properties} = {...defaultCreateCollectionParams, ...params};
 
   let modeprm = {};
   if (mode.type === 'NFT') {
@@ -457,6 +458,7 @@
     description: strToUTF16(description),
     tokenPrefix: strToUTF16(tokenPrefix),
     mode: modeprm as any,
+    properties,
   });
   const events = await executeTransaction(api, sender, tx);
   return getCreateCollectionResult(events);
modifiedtests/src/eth/allowlist.test.tsdiffbeforeafterboth
--- a/tests/src/eth/allowlist.test.ts
+++ b/tests/src/eth/allowlist.test.ts
@@ -78,7 +78,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const user = helper.eth.createAccount();
 
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
@@ -94,7 +94,7 @@
   //   const owner = await helper.eth.createAccountWithBalance(donor);
   //   const user = donor;
 
-  //   const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+  //   const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
   //   const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
   //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
@@ -110,7 +110,7 @@
     const notOwner = await helper.eth.createAccountWithBalance(donor);
     const user = helper.eth.createAccount();
 
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
@@ -129,7 +129,7 @@
   //   const notOwner = await helper.eth.createAccountWithBalance(donor);
   //   const user = donor;
 
-  //   const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+  //   const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
   //   const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
   //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
modifiedtests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -18,13 +18,26 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x5ad4f440
+/// @dev the ERC-165 identifier for this interface is 0xf62c7aa9
 interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create an NFT collection
 	/// @param name Name of the collection
 	/// @param description Informative description of the collection
 	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
 	/// @return address Address of the newly created collection
+	/// @dev EVM selector for this function is: 0x844af658,
+	///  or in textual repr: createNFTCollection(string,string,string)
+	function createNFTCollection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) external payable returns (address);
+
+	/// Create an NFT collection
+	/// @param name Name of the collection
+	/// @param description Informative description of the collection
+	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+	/// @return address Address of the newly created collection
 	/// @dev EVM selector for this function is: 0xe34a6844,
 	///  or in textual repr: createNonfungibleCollection(string,string,string)
 	function createNonfungibleCollection(
@@ -33,9 +46,9 @@
 		string memory tokenPrefix
 	) external payable returns (address);
 
-	/// @dev EVM selector for this function is: 0xa634a5f9,
-	///  or in textual repr: createERC721MetadataCompatibleCollection(string,string,string,string)
-	function createERC721MetadataCompatibleCollection(
+	/// @dev EVM selector for this function is: 0xd1df968c,
+	///  or in textual repr: createERC721MetadataNFTCollection(string,string,string,string)
+	function createERC721MetadataNFTCollection(
 		string memory name,
 		string memory description,
 		string memory tokenPrefix,
@@ -50,9 +63,17 @@
 		string memory tokenPrefix
 	) external payable returns (address);
 
-	/// @dev EVM selector for this function is: 0xa5596388,
-	///  or in textual repr: createERC721MetadataCompatibleRFTCollection(string,string,string,string)
-	function createERC721MetadataCompatibleRFTCollection(
+	/// @dev EVM selector for this function is: 0x44a68ad5,
+	///  or in textual repr: createRefungibleCollection(string,string,string)
+	function createRefungibleCollection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) external payable returns (address);
+
+	/// @dev EVM selector for this function is: 0xbea6a299,
+	///  or in textual repr: createERC721MetadataRFTCollection(string,string,string,string)
+	function createERC721MetadataRFTCollection(
 		string memory name,
 		string memory description,
 		string memory tokenPrefix,
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -38,7 +38,7 @@
 
   itEth('Add admin by owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     const newAdmin = helper.eth.createAccount();
@@ -51,7 +51,7 @@
 
   itEth.skip('Add substrate admin by owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     const [newAdmin] = await helper.arrange.createAccounts([10n], donor);
@@ -64,7 +64,7 @@
 
   itEth('Verify owner or admin', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const newAdmin = helper.eth.createAccount();
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -75,7 +75,7 @@
 
   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.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const admin = await helper.eth.createAccountWithBalance(donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -93,7 +93,7 @@
 
   itEth('(!negative tests!) Add admin by USER is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const notAdmin = await helper.eth.createAccountWithBalance(donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -108,7 +108,7 @@
 
   itEth.skip('(!negative tests!) Add substrate admin by ADMIN is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const admin = await helper.eth.createAccountWithBalance(donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -126,7 +126,7 @@
 
   itEth.skip('(!negative tests!) Add substrate admin by USER is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const notAdmin0 = await helper.eth.createAccountWithBalance(donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -150,7 +150,7 @@
 
   itEth('Remove admin by owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const newAdmin = helper.eth.createAccount();
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -170,7 +170,7 @@
 
   itEth.skip('Remove substrate admin by owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const [newAdmin] = await helper.arrange.createAccounts([10n], donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -188,7 +188,7 @@
 
   itEth('(!negative tests!) Remove admin by ADMIN is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
@@ -210,7 +210,7 @@
 
   itEth('(!negative tests!) Remove admin by USER is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
@@ -230,7 +230,7 @@
 
   itEth.skip('(!negative tests!) Remove substrate admin by ADMIN is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const [adminSub] = await helper.arrange.createAccounts([10n], donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -250,7 +250,7 @@
 
   itEth.skip('(!negative tests!) Remove substrate admin by USER is not allowed', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
     const [adminSub] = await helper.arrange.createAccounts([10n], donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -279,7 +279,7 @@
   itEth('Change owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const newOwner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     await collectionEvm.methods.setOwner(newOwner).send();
@@ -291,7 +291,7 @@
   itEth('change owner call fee', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const newOwner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
     const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwner(newOwner).send());
     expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));
@@ -301,7 +301,7 @@
   itEth('(!negative tests!) call setOwner by non owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const newOwner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     await expect(collectionEvm.methods.setOwner(newOwner).send({from: newOwner})).to.be.rejected;
@@ -321,7 +321,7 @@
   itEth.skip('Change owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const [newOwner] = await helper.arrange.createAccounts([10n], donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.true;
@@ -336,7 +336,7 @@
   itEth.skip('change owner call fee', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const [newOwner] = await helper.arrange.createAccounts([10n], donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send());
@@ -348,7 +348,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const otherReceiver = await helper.eth.createAccountWithBalance(donor);
     const [newOwner] = await helper.arrange.createAccounts([10n], donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
 
     await expect(collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send({from: otherReceiver})).to.be.rejected;
modifiedtests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -32,7 +32,7 @@
       { "internalType": "string", "name": "tokenPrefix", "type": "string" },
       { "internalType": "string", "name": "baseUri", "type": "string" }
     ],
-    "name": "createERC721MetadataCompatibleCollection",
+    "name": "createERC721MetadataNFTCollection",
     "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
     "stateMutability": "payable",
     "type": "function"
@@ -44,7 +44,18 @@
       { "internalType": "string", "name": "tokenPrefix", "type": "string" },
       { "internalType": "string", "name": "baseUri", "type": "string" }
     ],
-    "name": "createERC721MetadataCompatibleRFTCollection",
+    "name": "createERC721MetadataRFTCollection",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "name", "type": "string" },
+      { "internalType": "string", "name": "description", "type": "string" },
+      { "internalType": "string", "name": "tokenPrefix", "type": "string" }
+    ],
+    "name": "createNFTCollection",
     "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
     "stateMutability": "payable",
     "type": "function"
@@ -73,6 +84,17 @@
   },
   {
     "inputs": [
+      { "internalType": "string", "name": "name", "type": "string" },
+      { "internalType": "string", "name": "description", "type": "string" },
+      { "internalType": "string", "name": "tokenPrefix", "type": "string" }
+    ],
+    "name": "createRefungibleCollection",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       {
         "internalType": "address",
         "name": "collectionAddress",
modifiedtests/src/eth/collectionProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -24,7 +24,7 @@
 
     const raw = (await collection.getData())?.raw;
 
-    expect(raw.properties[0].value).to.equal('testValue');
+    expect(raw.properties[1].value).to.equal('testValue');
   });
 
   itEth('Can be deleted', async({helper}) => {
@@ -54,3 +54,46 @@
     expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));
   });
 });
+
+describe('Supports ERC721Metadata', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (_helper, privateKey) => {
+      donor = privateKey('//Alice');
+    });
+  });
+
+  itEth('ERC721Metadata property can be set for NFT collection', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const collection = await helper.nft.mintCollection(donor, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+
+    await collection.addAdmin(donor, {Ethereum: caller});
+    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
+
+    await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('1')).send({from: caller});
+
+    expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.true;
+
+    await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('0')).send({from: caller});
+
+    expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.false;
+  });
+
+  itEth('ERC721Metadata property can be set for RFT collection', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const collection = await helper.rft.mintCollection(donor, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+
+    await collection.addAdmin(donor, {Ethereum: caller});
+
+    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
+
+    await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('1')).send({from: caller});
+
+    expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.true;
+
+    await contract.methods.setCollectionProperty('ERC721Metadata', Buffer.from('0')).send({from: caller});
+
+    expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.false;
+  });
+});
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -54,7 +54,7 @@
   // itWeb3('Set substrate sponsor', async ({api, web3, privateKeyWrapper}) => {
   //   const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
   //   const collectionHelpers = evmCollectionHelpers(web3, owner);
-  //   let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+  //   let result = await collectionHelpers.methods.createNFTCollection('Sponsor collection', '1', '1').send();
   //   const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
   //   const sponsor = privateKeyWrapper('//Alice');
   //   const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
@@ -75,7 +75,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
-    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
+    let result = await collectionHelpers.methods.createNFTCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
     const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const sponsor = await helper.eth.createAccountWithBalance(donor);
     const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'nft', owner);
@@ -97,7 +97,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
-    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
+    let result = await collectionHelpers.methods.createERC721MetadataNFTCollection('Sponsor collection', '1', '1', '').send({value: Number(2n * nominal)});
     const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const collectionId = helper.ethAddress.extractCollectionId(collectionIdAddress);
     const collection = helper.nft.getCollectionObject(collectionId);
@@ -167,7 +167,7 @@
   // itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => {
   //   const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
   //   const collectionHelpers = evmCollectionHelpers(web3, owner);
-  //   const result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+  //   const result = await collectionHelpers.methods.createERC721MetadataNFTCollection('Sponsor collection', '1', '1', '').send();
   //   const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
   //   const sponsor = privateKeyWrapper('//Alice');
   //   const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
@@ -223,7 +223,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
-    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)});
+    let result = await collectionHelpers.methods.createERC721MetadataNFTCollection('Sponsor collection', '1', '1', '').send({value: Number(2n * nominal)});
     const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const collectionId = helper.ethAddress.extractCollectionId(collectionIdAddress);
     const collection = helper.nft.getCollectionObject(collectionId);
modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -37,7 +37,7 @@
 
     // todo:playgrounds this might fail when in async environment.
     const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
-    const {collectionId} = await helper.eth.createNonfungibleCollection(owner, name, description, prefix);
+    const {collectionId} = await helper.eth.createNFTCollection(owner, name, description, prefix);
     const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
 
     const collection = helper.nft.getCollectionObject(collectionId);
@@ -64,7 +64,7 @@
       .call()).to.be.false;
 
     await collectionHelpers.methods
-      .createNonfungibleCollection('A', 'A', 'A')
+      .createNFTCollection('A', 'A', 'A')
       .send({value: Number(2n * helper.balance.getOneTokenNominal())});
     
     expect(await collectionHelpers.methods
@@ -76,7 +76,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const sponsor = await helper.eth.createAccountWithBalance(donor);
     const ss58Format = helper.chain.getChainProperties().ss58Format;
-    const {collectionId, collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
+    const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
 
     const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
     await collection.methods.setCollectionSponsor(sponsor).send();
@@ -95,7 +95,7 @@
 
   itEth('Set limits', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionId, collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Limits', 'absolutely anything', 'FLO');
+    const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'FLO');
     const limits = {
       accountTokenOwnershipLimit: 1000,
       sponsoredDataSize: 1024,
@@ -138,7 +138,7 @@
       .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
       .to.be.false;
     
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Exister', 'absolutely anything', 'EVC');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Exister', 'absolutely anything', 'EVC');
     expect(await helper.ethNativeContract.collectionHelpers(collectionAddress)
       .methods.isCollectionExist(collectionAddress).call())
       .to.be.true;
@@ -166,7 +166,7 @@
       const tokenPrefix = 'A';
 
       await expect(collectionHelper.methods
-        .createNonfungibleCollection(collectionName, description, tokenPrefix)
+        .createNFTCollection(collectionName, description, tokenPrefix)
         .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);
       
     }
@@ -176,7 +176,7 @@
       const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);
       const tokenPrefix = 'A';
       await expect(collectionHelper.methods
-        .createNonfungibleCollection(collectionName, description, tokenPrefix)
+        .createNFTCollection(collectionName, description, tokenPrefix)
         .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);
     }
     {
@@ -185,7 +185,7 @@
       const description = 'A';
       const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);
       await expect(collectionHelper.methods
-        .createNonfungibleCollection(collectionName, description, tokenPrefix)
+        .createNFTCollection(collectionName, description, tokenPrefix)
         .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);
     }
   });
@@ -194,14 +194,14 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     await expect(collectionHelper.methods
-      .createNonfungibleCollection('Peasantry', 'absolutely anything', 'CVE')
+      .createNFTCollection('Peasantry', 'absolutely anything', 'CVE')
       .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
   });
 
   itEth('(!negative test!) Check owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const malfeasant = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Transgressed', 'absolutely anything', 'COR');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Transgressed', 'absolutely anything', 'COR');
     const malfeasantCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', malfeasant);
     const EXPECTED_ERROR = 'NoPermission';
     {
@@ -224,7 +224,7 @@
 
   itEth('(!negative test!) Set limits', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Limits', 'absolutely anything', 'OLF');
+    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
     await expect(collectionEvm.methods
       .setCollectionLimit('badLimit', 'true')
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -39,7 +39,7 @@
   
     // todo:playgrounds this might fail when in async environment.
     const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
-    const {collectionId} = await helper.eth.createRefungibleCollection(owner, name, description, prefix);
+    const {collectionId} = await helper.eth.createRFTCollection(owner, name, description, prefix);
     const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
   
     const data = (await helper.rft.getData(collectionId))!;
@@ -77,7 +77,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const sponsor = await helper.eth.createAccountWithBalance(donor);
     const ss58Format = helper.chain.getChainProperties().ss58Format;
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
 
     const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     await collection.methods.setCollectionSponsor(sponsor).send();
@@ -96,7 +96,7 @@
 
   itEth('Set limits', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Limits', 'absolutely anything', 'INSI');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'INSI');
     const limits = {
       accountTokenOwnershipLimit: 1000,
       sponsoredDataSize: 1024,
@@ -139,7 +139,7 @@
       .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
       .to.be.false;
     
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Exister', 'absolutely anything', 'WIWT');
+    const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Exister', 'absolutely anything', 'WIWT');
     expect(await helper.ethNativeContract.collectionHelpers(collectionAddress)
       .methods.isCollectionExist(collectionAddress).call())
       .to.be.true;
@@ -202,7 +202,7 @@
   itEth('(!negative test!) Check owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const peasant = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
+    const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
     const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', peasant);
     const EXPECTED_ERROR = 'NoPermission';
     {
@@ -225,7 +225,7 @@
 
   itEth('(!negative test!) Set limits', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Limits', 'absolutely anything', 'ISNI');
+    const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'ISNI');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     await expect(collectionEvm.methods
       .setCollectionLimit('badLimit', 'true')
modifiedtests/src/eth/evmCoder.test.tsdiffbeforeafterboth
--- a/tests/src/eth/evmCoder.test.ts
+++ b/tests/src/eth/evmCoder.test.ts
@@ -65,7 +65,7 @@
   
   itEth('Call non-existing function', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collection = await helper.eth.createNonfungibleCollection(owner, 'EVMCODER', '', 'TEST');
+    const collection = await helper.eth.createNFTCollection(owner, 'EVMCODER', '', 'TEST');
     const contract = await helper.ethContract.deployByCode(owner, 'Test', getContractSource(collection.collectionAddress, '0x1bfed5D614b886b9Ab2eA4CBAc22A96B7EC29c9c'));
     const testContract = await helper.ethContract.deployByCode(owner, 'Test', getContractSource(collection.collectionAddress, contract.options.address));
     {
modifiedtests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -62,7 +62,7 @@
 const mintRFTToken = async (helper: EthUniqueHelper, owner: string, fractionalizer: Contract, amount: bigint): Promise<{
   nftCollectionAddress: string, nftTokenId: number, rftTokenAddress: string
 }> => {
-  const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+  const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
   const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
   const nftTokenId = await nftContract.methods.nextTokenId().call();
   await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -92,7 +92,7 @@
   itEth('Set RFT collection', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 10n);
     const fractionalizer = await deployContract(helper, owner);
-    const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+    const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
     const rftContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
 
     await rftContract.methods.addCollectionAdmin(fractionalizer.options.address).send({from: owner});
@@ -121,7 +121,7 @@
   itEth('Set Allowlist', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
     const {contract: fractionalizer} = await initContract(helper, owner);
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
 
     const result1 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});
     expect(result1.events).to.be.like({
@@ -146,7 +146,7 @@
   itEth('NFT to RFT', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
 
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
     const nftTokenId = await nftContract.methods.nextTokenId().call();
     await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -231,7 +231,7 @@
 
   itEth('call setRFTCollection twice', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
-    const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+    const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
     const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
 
     const fractionalizer = await deployContract(helper, owner);
@@ -244,7 +244,7 @@
 
   itEth('call setRFTCollection with NFT collection', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
 
     const fractionalizer = await deployContract(helper, owner);
@@ -257,7 +257,7 @@
   itEth('call setRFTCollection while not collection admin', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
     const fractionalizer = await deployContract(helper, owner);
-    const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+    const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
 
     await expect(fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).call())
       .to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g);
@@ -278,7 +278,7 @@
   itEth('call nft2rft without setting RFT collection for contract', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
 
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
     const nftTokenId = await nftContract.methods.nextTokenId().call();
     await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -293,7 +293,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
     const nftOwner = await helper.eth.createAccountWithBalance(donor, 10n);
 
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
     const nftTokenId = await nftContract.methods.nextTokenId().call();
     await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -310,7 +310,7 @@
   itEth('call nft2rft while not in list of allowed accounts', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
 
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
     const nftTokenId = await nftContract.methods.nextTokenId().call();
     await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -325,7 +325,7 @@
   itEth('call nft2rft while fractionalizer doesnt have approval for nft token', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
 
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
     const nftTokenId = await nftContract.methods.nextTokenId().call();
     await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
@@ -341,7 +341,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
 
     const fractionalizer = await deployContract(helper, owner);
-    const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+    const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
     const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
     const rftTokenId = await refungibleContract.methods.nextTokenId().call();
     await refungibleContract.methods.mint(owner, rftTokenId).send({from: owner});
@@ -354,7 +354,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
 
     const {contract: fractionalizer} = await initContract(helper, owner);
-    const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+    const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
     const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
     const rftTokenId = await refungibleContract.methods.nextTokenId().call();
     await refungibleContract.methods.mint(owner, rftTokenId).send({from: owner});
@@ -365,7 +365,7 @@
 
   itEth('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor, 20n);
-    const rftCollection = await helper.eth.createRefungibleCollection(owner, 'rft', 'RFT collection', 'RFT');
+    const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');
     const refungibleContract = helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);
 
     const fractionalizer = await deployContract(helper, owner);
@@ -432,7 +432,7 @@
     await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send({from: owner});
     await helper.executeExtrinsic(donor, 'api.tx.unique.setTransfersEnabledFlag', [rftCollection.collectionId, false], true);
 
-    const nftCollection = await helper.eth.createNonfungibleCollection(owner, 'nft', 'NFT collection', 'NFT');
+    const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');
     const nftContract = helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);
     const nftTokenId = await nftContract.methods.nextTokenId().call();
     await nftContract.methods.mint(owner, nftTokenId).send({from: owner});
modifiedtests/src/eth/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -7,7 +7,7 @@
   helper: EthUniqueHelper,
   owner: string,
 ): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {
-  const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
+  const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
 
   const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
   await contract.methods.setCollectionNesting(true).send({from: owner});
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -84,7 +84,7 @@
     const receiver = helper.eth.createAccount();
 
     const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
-    let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
+    let result = await collectionHelper.methods.createERC721MetadataNFTCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
     const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
     
@@ -146,7 +146,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
 
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Minty', '6', '6');
+    const {collectionAddress} = await helper.eth.createERC721MetadataNFTCollection(owner, 'Mint collection', '6', '6', '');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
     const nextTokenId = await contract.methods.nextTokenId().call();
 
modifiedtests/src/eth/payable.test.tsdiffbeforeafterboth
--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -146,7 +146,7 @@
     const caller = await helper.eth.createAccountWithBalance(donor);
     const contract = await deployProxyContract(helper, deployer);
 
-    const collectionAddress = (await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;
+    const collectionAddress = (await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;
     const initialCallerBalance = await helper.balance.getEthereum(caller);
     const initialContractBalance = await helper.balance.getEthereum(contract.options.address);
     await contract.methods.mintNftToken(collectionAddress).send({from: caller});
@@ -164,7 +164,7 @@
 
     const initialCallerBalance = await helper.balance.getEthereum(caller);
     const initialContractBalance = await helper.balance.getEthereum(contract.options.address);
-    await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});
+    await contract.methods.createNFTCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});
     const finalCallerBalance = await helper.balance.getEthereum(caller);
     const finalContractBalance = await helper.balance.getEthereum(contract.options.address);
     expect(finalCallerBalance < initialCallerBalance).to.be.true;
@@ -177,8 +177,8 @@
     const caller = await helper.eth.createAccountWithBalance(donor);
     const collectionHelper = helper.ethNativeContract.collectionHelpers(caller);
         
-    await expect(collectionHelper.methods.createNonfungibleCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
-    await expect(collectionHelper.methods.createNonfungibleCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
+    await expect(collectionHelper.methods.createNFTCollection('A', 'B', 'C').call({value: Number(SMALL_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
+    await expect(collectionHelper.methods.createNFTCollection('A', 'B', 'C').call({value: Number(BIG_FEE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
   });
 
   itEth('Negative test: call createRFTCollection with wrong fee', async({helper}) => {
@@ -227,9 +227,9 @@
           InnerContract(innerContract).flip();
         }
 
-        function createNonfungibleCollection() external payable {
+        function createNFTCollection() external payable {
           address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;
-		      address nftCollection = CollectionHelpers(collectionHelpers).createNonfungibleCollection{value: msg.value}("A", "B", "C");
+		      address nftCollection = CollectionHelpers(collectionHelpers).createNFTCollection{value: msg.value}("A", "B", "C");
           emit CollectionCreated(nftCollection);
         }
 
modifiedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -101,7 +101,7 @@
 
   itEth('Can perform mint()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'A', 'A', 'A');
+    const {collectionAddress} = await helper.eth.createERC721MetadataNFTCollection(owner, 'A', 'A', 'A', '');
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
 
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -31,7 +31,7 @@
 
   itEth('totalSupply', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'TotalSupply', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
     const nextTokenId = await contract.methods.nextTokenId().call();
     await contract.methods.mint(caller, nextTokenId).send();
@@ -41,7 +41,7 @@
 
   itEth('balanceOf', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'BalanceOf', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     {
@@ -63,7 +63,7 @@
 
   itEth('ownerOf', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'OwnerOf', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -76,7 +76,7 @@
   itEth('ownerOf after burn', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'OwnerOf-AfterBurn', '6', '6');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -95,7 +95,7 @@
   itEth('ownerOf for partial ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Partial-OwnerOf', '6', '6');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -124,7 +124,7 @@
   itEth('Can perform mint()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Minty', '6', '6');
+    const {collectionAddress} = await helper.eth.createERC721MetadataRFTCollection(owner, 'Minty', '6', '6', '');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     
     const nextTokenId = await contract.methods.nextTokenId().call();
@@ -147,7 +147,7 @@
   itEth('Can perform mintBulk()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'MintBulky', '6', '6');
+    const {collectionAddress} = await helper.eth.createERC721MetadataRFTCollection(owner, 'MintBulky', '6', '6', '');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
 
     {
@@ -179,7 +179,7 @@
 
   itEth('Can perform burn()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Burny', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -197,7 +197,7 @@
   itEth('Can perform transferFrom()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'TransferFromy', '6', '6');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -241,7 +241,7 @@
   itEth('Can perform transfer()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -271,7 +271,7 @@
   itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry-Partial-to-Full', '6', '6');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -298,7 +298,7 @@
   itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry-Full-to-Partial', '6', '6');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -336,7 +336,7 @@
   itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Feeful-Transfer-From', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -350,7 +350,7 @@
   itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Feeful-Transfer', '6', '6');
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -386,8 +386,8 @@
 
   itEth('Returns symbol name', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Leviathan', '', '12');
-    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+    const {collectionId} = await helper.rft.mintCollection(alice, {name: 'Leviathan', tokenPrefix: '12'});
+    const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);
     const symbol = await contract.methods.symbol().call();
     expect(symbol).to.equal('12');
   });
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -81,7 +81,7 @@
     const receiver = helper.eth.createAccount();
 
     const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
-    let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
+    let result = await collectionHelper.methods.createERC721MetadataNFTCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())});
     const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     
@@ -294,7 +294,7 @@
   itEth('Receiving Transfer event on burning into full ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = await helper.eth.createAccountWithBalance(donor);
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Devastation', '6', '6');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Devastation', '6', '6');
     const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
 
     const tokenId = await contract.methods.nextTokenId().call();
@@ -479,7 +479,7 @@
   itEth('Default parent token address and id', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
 
-    const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Sands', '', 'GRAIN');
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Sands', '', 'GRAIN');
     const collectionContract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     
     const tokenId = await collectionContract.methods.nextTokenId().call();
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -174,11 +174,23 @@
     return await this.helper.callRpc('api.rpc.eth.call', [{from: signer, to: contractAddress, data: abi}]);
   }
 
-  async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
+  async createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
     const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
     const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
         
-    const result = await collectionHelper.methods.createNonfungibleCollection(name, description, tokenPrefix).send({value: Number(collectionCreationPrice)});
+    const result = await collectionHelper.methods.createNFTCollection(name, description, tokenPrefix).send({value: Number(collectionCreationPrice)});
+
+    const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
+    const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
+
+    return {collectionId, collectionAddress};
+  }
+
+  async createERC721MetadataNFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{collectionId: number, collectionAddress: string}> {
+    const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
+    const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+        
+    const result = await collectionHelper.methods.createERC721MetadataNFTCollection(name, description, tokenPrefix, baseUri).send({value: Number(collectionCreationPrice)});
 
     const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
@@ -186,7 +198,7 @@
     return {collectionId, collectionAddress};
   }
 
-  async createRefungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
+  async createRFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
     const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
     const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
         
@@ -198,6 +210,18 @@
     return {collectionId, collectionAddress};
   }
 
+  async createERC721MetadataRFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{collectionId: number, collectionAddress: string}> {
+    const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
+    const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+        
+    const result = await collectionHelper.methods.createERC721MetadataRFTCollection(name, description, tokenPrefix, baseUri).send({value: Number(collectionCreationPrice)});
+
+    const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
+    const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
+
+    return {collectionId, collectionAddress};
+  }
+
   async deployCollectorContract(signer: string): Promise<Contract> {
     return await this.helper.ethContract.deployByCode(signer, 'Collector', `
     // SPDX-License-Identifier: UNLICENSED
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -18,6 +18,52 @@
 import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';
 import {UniqueHelper, UniqueBaseCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../util/playgrounds/unique';
 
+
+describe('Composite Properties Test', () => {
+  let alice: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const donor = privateKey('//Alice');
+      [alice] = await helper.arrange.createAccounts([50n], donor);
+    });
+  });
+
+  async function testMakeSureSuppliesRequired(baseCollection: UniqueNFTCollection | UniqueRFTCollection) {
+
+    const collectionOption = await baseCollection.getOptions();
+    expect(collectionOption).is.not.null;
+    let collection = collectionOption;
+    expect(collection.tokenPropertyPermissions).to.be.empty;
+    expect(collection.properties).to.be.deep.equal([{key: 'ERC721Metadata', value: '1'}]);
+
+    const propertyPermissions = [
+      {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},
+      {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},
+    ];
+    await expect(await baseCollection.setTokenPropertyPermissions(alice, propertyPermissions)).to.be.true;
+
+    const collectionProperties = [
+      {key: 'ERC721Metadata', value: '1'}, 
+      {key: 'black_hole', value: 'LIGO'},
+      {key: 'electron', value: 'come bond'}, 
+    ];
+    
+    await expect(await baseCollection.setProperties(alice, collectionProperties)).to.be.true;
+
+    collection = await baseCollection.getOptions();
+    expect(collection.tokenPropertyPermissions).to.be.deep.equal(propertyPermissions);
+    expect(collection.properties).to.be.deep.equal(collectionProperties);
+  }
+
+  itSub('Makes sure collectionById supplies required fields for NFT',  async ({helper}) => {
+    await testMakeSureSuppliesRequired(await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}));
+  });
+
+  itSub.ifWithPallets('Makes sure collectionById supplies required fields for ReFungible', [Pallets.ReFungible],  async ({helper}) => {
+    await testMakeSureSuppliesRequired(await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}));
+  });
+});
 // ---------- COLLECTION PROPERTIES
 
 describe('Integration Test: Collection Properties', () => {
@@ -33,7 +79,11 @@
 
   itSub('Properties are initially empty', async ({helper}) => {
     const collection = await helper.nft.mintCollection(alice);
-    expect(await collection.getProperties()).to.be.empty;
+    const properties = await collection.getProperties();
+    expect(properties).to.be.deep.equal([{
+      'key': 'ERC721Metadata',
+      'value': '1',
+    }]);
   });
 
   async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {
@@ -150,7 +200,11 @@
     await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))
       .to.be.rejectedWith(/common\.NoPermission/);
 
-    expect(await collection.getProperties()).to.be.empty;
+    const properties = await collection.getProperties();
+    expect(properties).to.be.deep.equal([{
+      'key': 'ERC721Metadata',
+      'value': '1',
+    }]);
   }
 
   itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) =>  {
@@ -202,7 +256,11 @@
     await expect(collection.setProperties(alice, propertiesToBeSet)).
       to.be.rejectedWith(/common\.PropertyLimitReached/);
 
-    expect(await collection.getProperties()).to.be.empty;
+    const properties = await collection.getProperties();
+    expect(properties).to.be.deep.equal([{
+      'key': 'ERC721Metadata',
+      'value': '1',
+    }]);
   }
 
   itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) =>  {
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -981,6 +981,10 @@
     return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();
   }
 
+  async getCollectionOptions(collectionId: number) {
+    return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();
+  }
+
   /**
    * Deletes onchain properties from the collection.
    *
@@ -1293,6 +1297,7 @@
   async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {
     collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
     collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};
+    collectionOptions.properties = collectionOptions.properties || [{key: 'ERC721Metadata', value: '1'}];
     for (const key of ['name', 'description', 'tokenPrefix']) {
       if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);
     }
@@ -2476,6 +2481,10 @@
     return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
   }
 
+  async getOptions() {
+    return await this.helper.collection.getCollectionOptions(this.collectionId);
+  }
+
   async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {
     return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);
   }