git.delta.rocks / unique-network / refs/commits / 75d8b9094b27

difftreelog

Merge pull request #277 from UniqueNetwork/feature/create-collection-ex

kozyrevdev2022-02-17parents: #c5b0654 #0660dd1.patch.diff
in: master
Add createCollectionEx call

18 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,7 +14,10 @@
 use up_data_structs::{
 	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
 	MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,
-	TokenId, Weight, WithdrawReasons, CollectionStats, CustomDataLimit,
+	TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
+	CustomDataLimit, CreateCollectionData, SponsorshipState,
 };
 pub use pallet::*;
 use sp_core::H160;
@@ -285,6 +288,10 @@
 		TokenVariableDataLimitExceeded,
 		/// Exceeded max admin count
 		CollectionAdminCountExceeded,
+		/// Collection limit bounds per collection exceeded
+		CollectionLimitBoundsExceeded,
+		/// Tried to enable permissions which are only permitted to be disabled
+		OwnerPermissionsCantBeReverted,
 
 		/// Collection settings not allowing items transferring
 		TransferNotAllowed,
@@ -395,7 +402,10 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
 				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,
@@ -418,6 +428,29 @@
 
 		// =========
 
+		let collection = Collection {
+			owner: owner.clone(),
+			name: data.name,
+			mode: data.mode.clone(),
+			mint_mode: false,
+			access: data.access.unwrap_or_default(),
+			description: data.description,
+			token_prefix: data.token_prefix,
+			offchain_schema: data.offchain_schema,
+			schema_version: data.schema_version.unwrap_or_default(),
+			sponsorship: data
+				.pending_sponsor
+				.map(SponsorshipState::Unconfirmed)
+				.unwrap_or_default(),
+			variable_on_chain_schema: data.variable_on_chain_schema,
+			const_on_chain_schema: data.const_on_chain_schema,
+			limits: data
+				.limits
+				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
+				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,
+			meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+		};
+
 		// Take a (non-refundable) deposit of collection creation
 		{
 			let mut imbalance =
@@ -429,7 +462,7 @@
 				),
 			);
 			<T as Config>::Currency::settle(
-				&data.owner,
+				&owner,
 				imbalance,
 				WithdrawReasons::TRANSFER,
 				ExistenceRequirement::KeepAlive,
@@ -438,12 +471,8 @@
 		}
 
 		<CreatedCollectionCount<T>>::put(created_count);
-		<Pallet<T>>::deposit_event(Event::CollectionCreated(
-			id,
-			data.mode.id(),
-			data.owner.clone(),
-		));
-		<CollectionById<T>>::insert(id, data);
+		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));
+		<CollectionById<T>>::insert(id, collection);
 		Ok(id)
 	}
 
@@ -527,6 +556,61 @@
 
 		Ok(())
 	}
+
+	pub fn clamp_limits(
+		mode: CollectionMode,
+		old_limit: &CollectionLimits,
+		mut new_limit: CollectionLimits,
+	) -> Result<CollectionLimits, DispatchError> {
+		macro_rules! limit_default {
+				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
+					$(
+						if let Some($new) = $new.$field {
+							let $old = $old.$field($($arg)?);
+							let _ = $new;
+							let _ = $old;
+							$check
+						} else {
+							$new.$field = $old.$field
+						}
+					)*
+				}};
+			}
+
+		limit_default!(old_limit, new_limit,
+			account_token_ownership_limit => ensure!(
+				new_limit <= MAX_TOKEN_OWNERSHIP,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsor_transfer_timeout(match mode {
+				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			}) => ensure!(
+				new_limit <= MAX_SPONSOR_TIMEOUT,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsored_data_size => ensure!(
+				new_limit <= CUSTOM_DATA_LIMIT,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			token_limit => ensure!(
+				old_limit >= new_limit && new_limit > 0,
+				<Error<T>>::CollectionTokenLimitExceeded
+			),
+			owner_can_transfer => ensure!(
+				old_limit || !new_limit,
+				<Error<T>>::OwnerPermissionsCantBeReverted,
+			),
+			owner_can_destroy => ensure!(
+				old_limit || !new_limit,
+				<Error<T>>::OwnerPermissionsCantBeReverted,
+			),
+			sponsored_data_rate_limit => {},
+			transfers_enabled => {},
+		);
+		Ok(new_limit)
+	}
 }
 
 #[macro_export]
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use core::ops::Deref;
 use frame_support::{ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};
+use up_data_structs::{AccessMode, Collection, CollectionId, TokenId, CreateCollectionData};
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
 };
@@ -100,8 +100,11 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use up_data_structs::{AccessMode, Collection, CollectionId, CustomDataLimit, TokenId};6use pallet_common::{7	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,8};9use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};10use sp_core::H160;11use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};12use sp_std::{vec::Vec, vec};13use core::ops::Deref;14use sp_std::collections::btree_map::BTreeMap;15use codec::{Encode, Decode, MaxEncodedLen};16use scale_info::TypeInfo;1718pub use pallet::*;19#[cfg(feature = "runtime-benchmarks")]20pub mod benchmarking;21pub mod common;22pub mod erc;23pub mod weights;2425pub struct CreateItemData<T: Config> {26	pub const_data: BoundedVec<u8, CustomDataLimit>,27	pub variable_data: BoundedVec<u8, CustomDataLimit>,28	pub owner: T::CrossAccountId,29}30pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3132#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]33pub struct ItemData<CrossAccountId> {34	pub const_data: BoundedVec<u8, CustomDataLimit>,35	pub variable_data: BoundedVec<u8, CustomDataLimit>,36	pub owner: CrossAccountId,37}3839#[frame_support::pallet]40pub mod pallet {41	use super::*;42	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};43	use up_data_structs::{CollectionId, TokenId};44	use super::weights::WeightInfo;4546	#[pallet::error]47	pub enum Error<T> {48		/// Not Nonfungible item data used to mint in Nonfungible collection.49		NotNonfungibleDataUsedToMintFungibleCollectionToken,50		/// Used amount > 1 with NFT51		NonfungibleItemsHaveNoAmount,52	}5354	#[pallet::config]55	pub trait Config: frame_system::Config + pallet_common::Config {56		type WeightInfo: WeightInfo;57	}5859	#[pallet::pallet]60	#[pallet::generate_store(pub(super) trait Store)]61	pub struct Pallet<T>(_);6263	#[pallet::storage]64	pub type TokensMinted<T: Config> =65		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;66	#[pallet::storage]67	pub type TokensBurnt<T: Config> =68		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6970	#[pallet::storage]71	pub type TokenData<T: Config> = StorageNMap<72		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),73		Value = ItemData<T::CrossAccountId>,74		QueryKind = OptionQuery,75	>;7677	/// Used to enumerate tokens owned by account78	#[pallet::storage]79	pub type Owned<T: Config> = StorageNMap<80		Key = (81			Key<Twox64Concat, CollectionId>,82			Key<Blake2_128Concat, T::CrossAccountId>,83			Key<Twox64Concat, TokenId>,84		),85		Value = bool,86		QueryKind = ValueQuery,87	>;8889	#[pallet::storage]90	pub type AccountBalance<T: Config> = StorageNMap<91		Key = (92			Key<Twox64Concat, CollectionId>,93			Key<Blake2_128Concat, T::CrossAccountId>,94		),95		Value = u32,96		QueryKind = ValueQuery,97	>;9899	#[pallet::storage]100	pub type Allowance<T: Config> = StorageNMap<101		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),102		Value = T::CrossAccountId,103		QueryKind = OptionQuery,104	>;105}106107pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);108impl<T: Config> NonfungibleHandle<T> {109	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {110		Self(inner)111	}112	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {113		self.0114	}115}116impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {117	fn recorder(&self) -> &SubstrateRecorder<T> {118		self.0.recorder()119	}120	fn into_recorder(self) -> SubstrateRecorder<T> {121		self.0.into_recorder()122	}123}124impl<T: Config> Deref for NonfungibleHandle<T> {125	type Target = pallet_common::CollectionHandle<T>;126127	fn deref(&self) -> &Self::Target {128		&self.0129	}130}131132impl<T: Config> Pallet<T> {133	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {134		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)135	}136	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {137		<TokenData<T>>::contains_key((collection.id, token))138	}139}140141// unchecked calls skips any permission checks142impl<T: Config> Pallet<T> {143	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {144		<PalletCommon<T>>::init_collection(data)145	}146	pub fn destroy_collection(147		collection: NonfungibleHandle<T>,148		sender: &T::CrossAccountId,149	) -> DispatchResult {150		let id = collection.id;151152		// =========153154		PalletCommon::destroy_collection(collection.0, sender)?;155156		<TokenData<T>>::remove_prefix((id,), None);157		<Owned<T>>::remove_prefix((id,), None);158		<TokensMinted<T>>::remove(id);159		<TokensBurnt<T>>::remove(id);160		<Allowance<T>>::remove_prefix((id,), None);161		<AccountBalance<T>>::remove_prefix((id,), None);162		Ok(())163	}164165	pub fn burn(166		collection: &NonfungibleHandle<T>,167		sender: &T::CrossAccountId,168		token: TokenId,169	) -> DispatchResult {170		let token_data =171			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;172		ensure!(173			&token_data.owner == sender174				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),175			<CommonError<T>>::NoPermission176		);177178		if collection.access == AccessMode::AllowList {179			collection.check_allowlist(sender)?;180		}181182		let burnt = <TokensBurnt<T>>::get(collection.id)183			.checked_add(1)184			.ok_or(ArithmeticError::Overflow)?;185186		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))187			.checked_sub(1)188			.ok_or(ArithmeticError::Overflow)?;189190		if balance == 0 {191			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));192		} else {193			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);194		}195		// =========196197		<Owned<T>>::remove((collection.id, &token_data.owner, token));198		<TokensBurnt<T>>::insert(collection.id, burnt);199		<TokenData<T>>::remove((collection.id, token));200		let old_spender = <Allowance<T>>::take((collection.id, token));201202		if let Some(old_spender) = old_spender {203			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(204				collection.id,205				token,206				sender.clone(),207				old_spender,208				0,209			));210		}211212		collection.log_mirrored(ERC721Events::Transfer {213			from: *token_data.owner.as_eth(),214			to: H160::default(),215			token_id: token.into(),216		});217		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(218			collection.id,219			token,220			token_data.owner,221			1,222		));223		Ok(())224	}225226	pub fn transfer(227		collection: &NonfungibleHandle<T>,228		from: &T::CrossAccountId,229		to: &T::CrossAccountId,230		token: TokenId,231	) -> DispatchResult {232		ensure!(233			collection.limits.transfers_enabled(),234			<CommonError<T>>::TransferNotAllowed235		);236237		let token_data =238			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;239		ensure!(240			&token_data.owner == from241				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),242			<CommonError<T>>::NoPermission243		);244245		if collection.access == AccessMode::AllowList {246			collection.check_allowlist(from)?;247			collection.check_allowlist(to)?;248		}249		<PalletCommon<T>>::ensure_correct_receiver(to)?;250251		let balance_from = <AccountBalance<T>>::get((collection.id, from))252			.checked_sub(1)253			.ok_or(<CommonError<T>>::TokenValueTooLow)?;254		let balance_to = if from != to {255			let balance_to = <AccountBalance<T>>::get((collection.id, to))256				.checked_add(1)257				.ok_or(ArithmeticError::Overflow)?;258259			ensure!(260				balance_to < collection.limits.account_token_ownership_limit(),261				<CommonError<T>>::AccountTokenLimitExceeded,262			);263264			Some(balance_to)265		} else {266			None267		};268269		// =========270271		<TokenData<T>>::insert(272			(collection.id, token),273			ItemData {274				owner: to.clone(),275				..token_data276			},277		);278279		if let Some(balance_to) = balance_to {280			// from != to281			if balance_from == 0 {282				<AccountBalance<T>>::remove((collection.id, from));283			} else {284				<AccountBalance<T>>::insert((collection.id, from), balance_from);285			}286			<AccountBalance<T>>::insert((collection.id, to), balance_to);287			<Owned<T>>::remove((collection.id, from, token));288			<Owned<T>>::insert((collection.id, to, token), true);289		}290		Self::set_allowance_unchecked(collection, from, token, None, true);291292		collection.log_mirrored(ERC721Events::Transfer {293			from: *from.as_eth(),294			to: *to.as_eth(),295			token_id: token.into(),296		});297		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(298			collection.id,299			token,300			from.clone(),301			to.clone(),302			1,303		));304		Ok(())305	}306307	pub fn create_multiple_items(308		collection: &NonfungibleHandle<T>,309		sender: &T::CrossAccountId,310		data: Vec<CreateItemData<T>>,311	) -> DispatchResult {312		if !collection.is_owner_or_admin(sender) {313			ensure!(314				collection.mint_mode,315				<CommonError<T>>::PublicMintingNotAllowed316			);317			collection.check_allowlist(sender)?;318319			for item in data.iter() {320				collection.check_allowlist(&item.owner)?;321			}322		}323324		for data in data.iter() {325			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;326		}327328		let first_token = <TokensMinted<T>>::get(collection.id);329		let tokens_minted = first_token330			.checked_add(data.len() as u32)331			.ok_or(ArithmeticError::Overflow)?;332		ensure!(333			tokens_minted <= collection.limits.token_limit(),334			<CommonError<T>>::CollectionTokenLimitExceeded335		);336337		let mut balances = BTreeMap::new();338		for data in &data {339			let balance = balances340				.entry(&data.owner)341				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));342			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;343344			ensure!(345				*balance <= collection.limits.account_token_ownership_limit(),346				<CommonError<T>>::AccountTokenLimitExceeded,347			);348		}349350		// =========351352		<TokensMinted<T>>::insert(collection.id, tokens_minted);353		for (account, balance) in balances {354			<AccountBalance<T>>::insert((collection.id, account), balance);355		}356		for (i, data) in data.into_iter().enumerate() {357			let token = first_token + i as u32 + 1;358359			<TokenData<T>>::insert(360				(collection.id, token),361				ItemData {362					const_data: data.const_data,363					variable_data: data.variable_data,364					owner: data.owner.clone(),365				},366			);367			<Owned<T>>::insert((collection.id, &data.owner, token), true);368369			collection.log_mirrored(ERC721Events::Transfer {370				from: H160::default(),371				to: *data.owner.as_eth(),372				token_id: token.into(),373			});374			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(375				collection.id,376				TokenId(token),377				data.owner.clone(),378				1,379			));380		}381		Ok(())382	}383384	pub fn set_allowance_unchecked(385		collection: &NonfungibleHandle<T>,386		sender: &T::CrossAccountId,387		token: TokenId,388		spender: Option<&T::CrossAccountId>,389		assume_implicit_eth: bool,390	) {391		if let Some(spender) = spender {392			let old_spender = <Allowance<T>>::get((collection.id, token));393			<Allowance<T>>::insert((collection.id, token), spender);394			// In ERC721 there is only one possible approved user of token, so we set395			// approved user to spender396			collection.log_mirrored(ERC721Events::Approval {397				owner: *sender.as_eth(),398				approved: *spender.as_eth(),399				token_id: token.into(),400			});401			// In Unique chain, any token can have any amount of approved users, so we need to402			// set allowance of old owner to 0, and allowance of new owner to 1403			if old_spender.as_ref() != Some(spender) {404				if let Some(old_owner) = old_spender {405					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(406						collection.id,407						token,408						sender.clone(),409						old_owner,410						0,411					));412				}413				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(414					collection.id,415					token,416					sender.clone(),417					spender.clone(),418					1,419				));420			}421		} else {422			let old_spender = <Allowance<T>>::take((collection.id, token));423			if !assume_implicit_eth {424				// In ERC721 there is only one possible approved user of token, so we set425				// approved user to zero address426				collection.log_mirrored(ERC721Events::Approval {427					owner: *sender.as_eth(),428					approved: H160::default(),429					token_id: token.into(),430				});431			}432			// In Unique chain, any token can have any amount of approved users, so we need to433			// set allowance of old owner to 0434			if let Some(old_spender) = old_spender {435				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(436					collection.id,437					token,438					sender.clone(),439					old_spender,440					0,441				));442			}443		}444	}445446	pub fn set_allowance(447		collection: &NonfungibleHandle<T>,448		sender: &T::CrossAccountId,449		token: TokenId,450		spender: Option<&T::CrossAccountId>,451	) -> DispatchResult {452		if collection.access == AccessMode::AllowList {453			collection.check_allowlist(sender)?;454			if let Some(spender) = spender {455				collection.check_allowlist(spender)?;456			}457		}458459		if let Some(spender) = spender {460			<PalletCommon<T>>::ensure_correct_receiver(spender)?;461		}462		let token_data =463			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;464		if &token_data.owner != sender {465			ensure!(466				collection.ignores_owned_amount(sender),467				<CommonError<T>>::CantApproveMoreThanOwned468			);469		}470471		// =========472473		Self::set_allowance_unchecked(collection, sender, token, spender, false);474		Ok(())475	}476477	pub fn transfer_from(478		collection: &NonfungibleHandle<T>,479		spender: &T::CrossAccountId,480		from: &T::CrossAccountId,481		to: &T::CrossAccountId,482		token: TokenId,483	) -> DispatchResult {484		if spender.conv_eq(from) {485			return Self::transfer(collection, from, to, token);486		}487		if collection.access == AccessMode::AllowList {488			// `from`, `to` checked in [`transfer`]489			collection.check_allowlist(spender)?;490		}491492		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {493			ensure!(494				collection.ignores_allowance(spender),495				<CommonError<T>>::TokenValueNotEnough496			);497		}498499		// =========500501		Self::transfer(collection, from, to, token)?;502		// Allowance is reset in [`transfer`]503		Ok(())504	}505506	pub fn burn_from(507		collection: &NonfungibleHandle<T>,508		spender: &T::CrossAccountId,509		from: &T::CrossAccountId,510		token: TokenId,511	) -> DispatchResult {512		if spender.conv_eq(from) {513			return Self::burn(collection, from, token);514		}515		if collection.access == AccessMode::AllowList {516			// `from` checked in [`burn`]517			collection.check_allowlist(spender)?;518		}519520		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {521			ensure!(522				collection.ignores_allowance(spender),523				<CommonError<T>>::TokenValueNotEnough524			);525		}526527		// =========528529		Self::burn(collection, from, token)530	}531532	pub fn set_variable_metadata(533		collection: &NonfungibleHandle<T>,534		sender: &T::CrossAccountId,535		token: TokenId,536		data: BoundedVec<u8, CustomDataLimit>,537	) -> DispatchResult {538		let token_data =539			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;540		collection.check_can_update_meta(sender, &token_data.owner)?;541542		// =========543544		<TokenData<T>>::insert(545			(collection.id, token),546			ItemData {547				variable_data: data,548				..token_data549			},550		);551		Ok(())552	}553554	/// Delegated to `create_multiple_items`555	pub fn create_item(556		collection: &NonfungibleHandle<T>,557		sender: &T::CrossAccountId,558		data: CreateItemData<T>,559	) -> DispatchResult {560		Self::create_multiple_items(collection, sender, vec![data])561	}562}
after · pallets/nonfungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use up_data_structs::{6	AccessMode, Collection, CollectionId, CustomDataLimit, TokenId, CreateCollectionData,7};8use pallet_common::{9	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};12use sp_core::H160;13use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};14use sp_std::{vec::Vec, vec};15use core::ops::Deref;16use sp_std::collections::btree_map::BTreeMap;17use codec::{Encode, Decode, MaxEncodedLen};18use scale_info::TypeInfo;1920pub use pallet::*;21#[cfg(feature = "runtime-benchmarks")]22pub mod benchmarking;23pub mod common;24pub mod erc;25pub mod weights;2627pub struct CreateItemData<T: Config> {28	pub const_data: BoundedVec<u8, CustomDataLimit>,29	pub variable_data: BoundedVec<u8, CustomDataLimit>,30	pub owner: T::CrossAccountId,31}32pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3334#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]35pub struct ItemData<CrossAccountId> {36	pub const_data: BoundedVec<u8, CustomDataLimit>,37	pub variable_data: BoundedVec<u8, CustomDataLimit>,38	pub owner: CrossAccountId,39}4041#[frame_support::pallet]42pub mod pallet {43	use super::*;44	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};45	use up_data_structs::{CollectionId, TokenId};46	use super::weights::WeightInfo;4748	#[pallet::error]49	pub enum Error<T> {50		/// Not Nonfungible item data used to mint in Nonfungible collection.51		NotNonfungibleDataUsedToMintFungibleCollectionToken,52		/// Used amount > 1 with NFT53		NonfungibleItemsHaveNoAmount,54	}5556	#[pallet::config]57	pub trait Config: frame_system::Config + pallet_common::Config {58		type WeightInfo: WeightInfo;59	}6061	#[pallet::pallet]62	#[pallet::generate_store(pub(super) trait Store)]63	pub struct Pallet<T>(_);6465	#[pallet::storage]66	pub type TokensMinted<T: Config> =67		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;68	#[pallet::storage]69	pub type TokensBurnt<T: Config> =70		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7172	#[pallet::storage]73	pub type TokenData<T: Config> = StorageNMap<74		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),75		Value = ItemData<T::CrossAccountId>,76		QueryKind = OptionQuery,77	>;7879	/// Used to enumerate tokens owned by account80	#[pallet::storage]81	pub type Owned<T: Config> = StorageNMap<82		Key = (83			Key<Twox64Concat, CollectionId>,84			Key<Blake2_128Concat, T::CrossAccountId>,85			Key<Twox64Concat, TokenId>,86		),87		Value = bool,88		QueryKind = ValueQuery,89	>;9091	#[pallet::storage]92	pub type AccountBalance<T: Config> = StorageNMap<93		Key = (94			Key<Twox64Concat, CollectionId>,95			Key<Blake2_128Concat, T::CrossAccountId>,96		),97		Value = u32,98		QueryKind = ValueQuery,99	>;100101	#[pallet::storage]102	pub type Allowance<T: Config> = StorageNMap<103		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),104		Value = T::CrossAccountId,105		QueryKind = OptionQuery,106	>;107}108109pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);110impl<T: Config> NonfungibleHandle<T> {111	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {112		Self(inner)113	}114	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {115		self.0116	}117}118impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {119	fn recorder(&self) -> &SubstrateRecorder<T> {120		self.0.recorder()121	}122	fn into_recorder(self) -> SubstrateRecorder<T> {123		self.0.into_recorder()124	}125}126impl<T: Config> Deref for NonfungibleHandle<T> {127	type Target = pallet_common::CollectionHandle<T>;128129	fn deref(&self) -> &Self::Target {130		&self.0131	}132}133134impl<T: Config> Pallet<T> {135	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {136		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)137	}138	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {139		<TokenData<T>>::contains_key((collection.id, token))140	}141}142143// unchecked calls skips any permission checks144impl<T: Config> Pallet<T> {145	pub fn init_collection(146		owner: T::AccountId,147		data: CreateCollectionData<T::AccountId>,148	) -> Result<CollectionId, DispatchError> {149		<PalletCommon<T>>::init_collection(owner, data)150	}151	pub fn destroy_collection(152		collection: NonfungibleHandle<T>,153		sender: &T::CrossAccountId,154	) -> DispatchResult {155		let id = collection.id;156157		// =========158159		PalletCommon::destroy_collection(collection.0, sender)?;160161		<TokenData<T>>::remove_prefix((id,), None);162		<Owned<T>>::remove_prefix((id,), None);163		<TokensMinted<T>>::remove(id);164		<TokensBurnt<T>>::remove(id);165		<Allowance<T>>::remove_prefix((id,), None);166		<AccountBalance<T>>::remove_prefix((id,), None);167		Ok(())168	}169170	pub fn burn(171		collection: &NonfungibleHandle<T>,172		sender: &T::CrossAccountId,173		token: TokenId,174	) -> DispatchResult {175		let token_data =176			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;177		ensure!(178			&token_data.owner == sender179				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),180			<CommonError<T>>::NoPermission181		);182183		if collection.access == AccessMode::AllowList {184			collection.check_allowlist(sender)?;185		}186187		let burnt = <TokensBurnt<T>>::get(collection.id)188			.checked_add(1)189			.ok_or(ArithmeticError::Overflow)?;190191		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))192			.checked_sub(1)193			.ok_or(ArithmeticError::Overflow)?;194195		if balance == 0 {196			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));197		} else {198			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);199		}200		// =========201202		<Owned<T>>::remove((collection.id, &token_data.owner, token));203		<TokensBurnt<T>>::insert(collection.id, burnt);204		<TokenData<T>>::remove((collection.id, token));205		let old_spender = <Allowance<T>>::take((collection.id, token));206207		if let Some(old_spender) = old_spender {208			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(209				collection.id,210				token,211				sender.clone(),212				old_spender,213				0,214			));215		}216217		collection.log_mirrored(ERC721Events::Transfer {218			from: *token_data.owner.as_eth(),219			to: H160::default(),220			token_id: token.into(),221		});222		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(223			collection.id,224			token,225			token_data.owner,226			1,227		));228		Ok(())229	}230231	pub fn transfer(232		collection: &NonfungibleHandle<T>,233		from: &T::CrossAccountId,234		to: &T::CrossAccountId,235		token: TokenId,236	) -> DispatchResult {237		ensure!(238			collection.limits.transfers_enabled(),239			<CommonError<T>>::TransferNotAllowed240		);241242		let token_data =243			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;244		ensure!(245			&token_data.owner == from246				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),247			<CommonError<T>>::NoPermission248		);249250		if collection.access == AccessMode::AllowList {251			collection.check_allowlist(from)?;252			collection.check_allowlist(to)?;253		}254		<PalletCommon<T>>::ensure_correct_receiver(to)?;255256		let balance_from = <AccountBalance<T>>::get((collection.id, from))257			.checked_sub(1)258			.ok_or(<CommonError<T>>::TokenValueTooLow)?;259		let balance_to = if from != to {260			let balance_to = <AccountBalance<T>>::get((collection.id, to))261				.checked_add(1)262				.ok_or(ArithmeticError::Overflow)?;263264			ensure!(265				balance_to < collection.limits.account_token_ownership_limit(),266				<CommonError<T>>::AccountTokenLimitExceeded,267			);268269			Some(balance_to)270		} else {271			None272		};273274		// =========275276		<TokenData<T>>::insert(277			(collection.id, token),278			ItemData {279				owner: to.clone(),280				..token_data281			},282		);283284		if let Some(balance_to) = balance_to {285			// from != to286			if balance_from == 0 {287				<AccountBalance<T>>::remove((collection.id, from));288			} else {289				<AccountBalance<T>>::insert((collection.id, from), balance_from);290			}291			<AccountBalance<T>>::insert((collection.id, to), balance_to);292			<Owned<T>>::remove((collection.id, from, token));293			<Owned<T>>::insert((collection.id, to, token), true);294		}295		Self::set_allowance_unchecked(collection, from, token, None, true);296297		collection.log_mirrored(ERC721Events::Transfer {298			from: *from.as_eth(),299			to: *to.as_eth(),300			token_id: token.into(),301		});302		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(303			collection.id,304			token,305			from.clone(),306			to.clone(),307			1,308		));309		Ok(())310	}311312	pub fn create_multiple_items(313		collection: &NonfungibleHandle<T>,314		sender: &T::CrossAccountId,315		data: Vec<CreateItemData<T>>,316	) -> DispatchResult {317		if !collection.is_owner_or_admin(sender) {318			ensure!(319				collection.mint_mode,320				<CommonError<T>>::PublicMintingNotAllowed321			);322			collection.check_allowlist(sender)?;323324			for item in data.iter() {325				collection.check_allowlist(&item.owner)?;326			}327		}328329		for data in data.iter() {330			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;331		}332333		let first_token = <TokensMinted<T>>::get(collection.id);334		let tokens_minted = first_token335			.checked_add(data.len() as u32)336			.ok_or(ArithmeticError::Overflow)?;337		ensure!(338			tokens_minted <= collection.limits.token_limit(),339			<CommonError<T>>::CollectionTokenLimitExceeded340		);341342		let mut balances = BTreeMap::new();343		for data in &data {344			let balance = balances345				.entry(&data.owner)346				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));347			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;348349			ensure!(350				*balance <= collection.limits.account_token_ownership_limit(),351				<CommonError<T>>::AccountTokenLimitExceeded,352			);353		}354355		// =========356357		<TokensMinted<T>>::insert(collection.id, tokens_minted);358		for (account, balance) in balances {359			<AccountBalance<T>>::insert((collection.id, account), balance);360		}361		for (i, data) in data.into_iter().enumerate() {362			let token = first_token + i as u32 + 1;363364			<TokenData<T>>::insert(365				(collection.id, token),366				ItemData {367					const_data: data.const_data,368					variable_data: data.variable_data,369					owner: data.owner.clone(),370				},371			);372			<Owned<T>>::insert((collection.id, &data.owner, token), true);373374			collection.log_mirrored(ERC721Events::Transfer {375				from: H160::default(),376				to: *data.owner.as_eth(),377				token_id: token.into(),378			});379			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(380				collection.id,381				TokenId(token),382				data.owner.clone(),383				1,384			));385		}386		Ok(())387	}388389	pub fn set_allowance_unchecked(390		collection: &NonfungibleHandle<T>,391		sender: &T::CrossAccountId,392		token: TokenId,393		spender: Option<&T::CrossAccountId>,394		assume_implicit_eth: bool,395	) {396		if let Some(spender) = spender {397			let old_spender = <Allowance<T>>::get((collection.id, token));398			<Allowance<T>>::insert((collection.id, token), spender);399			// In ERC721 there is only one possible approved user of token, so we set400			// approved user to spender401			collection.log_mirrored(ERC721Events::Approval {402				owner: *sender.as_eth(),403				approved: *spender.as_eth(),404				token_id: token.into(),405			});406			// In Unique chain, any token can have any amount of approved users, so we need to407			// set allowance of old owner to 0, and allowance of new owner to 1408			if old_spender.as_ref() != Some(spender) {409				if let Some(old_owner) = old_spender {410					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(411						collection.id,412						token,413						sender.clone(),414						old_owner,415						0,416					));417				}418				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(419					collection.id,420					token,421					sender.clone(),422					spender.clone(),423					1,424				));425			}426		} else {427			let old_spender = <Allowance<T>>::take((collection.id, token));428			if !assume_implicit_eth {429				// In ERC721 there is only one possible approved user of token, so we set430				// approved user to zero address431				collection.log_mirrored(ERC721Events::Approval {432					owner: *sender.as_eth(),433					approved: H160::default(),434					token_id: token.into(),435				});436			}437			// In Unique chain, any token can have any amount of approved users, so we need to438			// set allowance of old owner to 0439			if let Some(old_spender) = old_spender {440				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(441					collection.id,442					token,443					sender.clone(),444					old_spender,445					0,446				));447			}448		}449	}450451	pub fn set_allowance(452		collection: &NonfungibleHandle<T>,453		sender: &T::CrossAccountId,454		token: TokenId,455		spender: Option<&T::CrossAccountId>,456	) -> DispatchResult {457		if collection.access == AccessMode::AllowList {458			collection.check_allowlist(sender)?;459			if let Some(spender) = spender {460				collection.check_allowlist(spender)?;461			}462		}463464		if let Some(spender) = spender {465			<PalletCommon<T>>::ensure_correct_receiver(spender)?;466		}467		let token_data =468			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;469		if &token_data.owner != sender {470			ensure!(471				collection.ignores_owned_amount(sender),472				<CommonError<T>>::CantApproveMoreThanOwned473			);474		}475476		// =========477478		Self::set_allowance_unchecked(collection, sender, token, spender, false);479		Ok(())480	}481482	pub fn transfer_from(483		collection: &NonfungibleHandle<T>,484		spender: &T::CrossAccountId,485		from: &T::CrossAccountId,486		to: &T::CrossAccountId,487		token: TokenId,488	) -> DispatchResult {489		if spender.conv_eq(from) {490			return Self::transfer(collection, from, to, token);491		}492		if collection.access == AccessMode::AllowList {493			// `from`, `to` checked in [`transfer`]494			collection.check_allowlist(spender)?;495		}496497		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {498			ensure!(499				collection.ignores_allowance(spender),500				<CommonError<T>>::TokenValueNotEnough501			);502		}503504		// =========505506		Self::transfer(collection, from, to, token)?;507		// Allowance is reset in [`transfer`]508		Ok(())509	}510511	pub fn burn_from(512		collection: &NonfungibleHandle<T>,513		spender: &T::CrossAccountId,514		from: &T::CrossAccountId,515		token: TokenId,516	) -> DispatchResult {517		if spender.conv_eq(from) {518			return Self::burn(collection, from, token);519		}520		if collection.access == AccessMode::AllowList {521			// `from` checked in [`burn`]522			collection.check_allowlist(spender)?;523		}524525		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {526			ensure!(527				collection.ignores_allowance(spender),528				<CommonError<T>>::TokenValueNotEnough529			);530		}531532		// =========533534		Self::burn(collection, from, token)535	}536537	pub fn set_variable_metadata(538		collection: &NonfungibleHandle<T>,539		sender: &T::CrossAccountId,540		token: TokenId,541		data: BoundedVec<u8, CustomDataLimit>,542	) -> DispatchResult {543		let token_data =544			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;545		collection.check_can_update_meta(sender, &token_data.owner)?;546547		// =========548549		<TokenData<T>>::insert(550			(collection.id, token),551			ItemData {552				variable_data: data,553				..token_data554			},555		);556		Ok(())557	}558559	/// Delegated to `create_multiple_items`560	pub fn create_item(561		collection: &NonfungibleHandle<T>,562		sender: &T::CrossAccountId,563		data: CreateItemData<T>,564	) -> DispatchResult {565		Self::create_multiple_items(collection, sender, vec![data])566	}567}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -3,6 +3,7 @@
 use frame_support::{ensure, BoundedVec};
 use up_data_structs::{
 	AccessMode, Collection, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
+	CreateCollectionData,
 };
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -155,8 +156,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -36,13 +36,11 @@
 use frame_system::{self as system, ensure_signed};
 use sp_runtime::{sp_std::prelude::Vec};
 use up_data_structs::{
-	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
-	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-	NFT_SPONSOR_TRANSFER_TIMEOUT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
+	MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
+	OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
 	MAX_TOKEN_PREFIX_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,
 	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
-	CustomDataLimit,
+	CreateCollectionData, CustomDataLimit,
 };
 use pallet_common::{
 	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,
@@ -84,10 +82,6 @@
 		ConfirmUnsetSponsorFail,
 		/// Length of items properties must be greater than 0.
 		EmptyArgument,
-		/// Collection limit bounds per collection exceeded
-		CollectionLimitBoundsExceeded,
-		/// Tried to enable permissions which are only permitted to be disabled
-		OwnerPermissionsCantBeReverted,
 	}
 }
 
@@ -321,42 +315,39 @@
 		// returns collection ID
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
+		#[deprecated]
 		pub fn create_collection(origin,
 								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
 								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
 								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
-								 mode: CollectionMode) -> DispatchResult {
-
-			// Anyone can create a collection
-			let who = ensure_signed(origin)?;
-
-			// Create new collection
-			let new_collection = Collection {
-				owner: who,
+								 mode: CollectionMode) -> DispatchResult  {
+			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {
 				name: collection_name,
-				mode: mode.clone(),
-				mint_mode: false,
-				access: AccessMode::Normal,
 				description: collection_description,
 				token_prefix,
-				offchain_schema: BoundedVec::default(),
-				schema_version: SchemaVersion::ImageURL,
-				sponsorship: SponsorshipState::Disabled,
-				variable_on_chain_schema: BoundedVec::default(),
-				const_on_chain_schema: BoundedVec::default(),
-				limits: Default::default(),
-				meta_update_permission: Default::default(),
+				mode,
+				..Default::default()
 			};
+			Self::create_collection_ex(origin, data)
+		}
+
+		/// This method creates a collection
+		///
+		/// Prefer it to deprecated [`created_collection`] method
+		#[weight = <SelfWeightOf<T>>::create_collection()]
+		#[transactional]
+		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
+			let owner = ensure_signed(origin)?;
 
-			let _id = match mode {
-				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},
+			let _id = match data.mode {
+				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},
 				CollectionMode::Fungible(decimal_points) => {
 					// check params
 					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);
-					<PalletFungible<T>>::init_collection(new_collection)?
+					<PalletFungible<T>>::init_collection(owner, data)?
 				}
 				CollectionMode::ReFungible => {
-					<PalletRefungible<T>>::init_collection(new_collection)?
+					<PalletRefungible<T>>::init_collection(owner, data)?
 				}
 			};
 
@@ -1093,61 +1084,12 @@
 			collection_id: CollectionId,
 			new_limit: CollectionLimits,
 		) -> DispatchResult {
-			let mut new_limit = new_limit;
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.limits;
 
-			macro_rules! limit_default {
-				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
-					$(
-						if let Some($new) = $new.$field {
-							let $old = $old.$field($($arg)?);
-							let _ = $new;
-							let _ = $old;
-							$check
-						} else {
-							$new.$field = $old.$field
-						}
-					)*
-				}};
-			}
-
-			limit_default!(old_limit, new_limit,
-				account_token_ownership_limit => ensure!(
-					new_limit <= MAX_TOKEN_OWNERSHIP,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				sponsor_transfer_timeout(match target_collection.mode {
-					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
-					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-				}) => ensure!(
-					new_limit <= MAX_SPONSOR_TIMEOUT,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				sponsored_data_size => ensure!(
-					new_limit <= CUSTOM_DATA_LIMIT,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				token_limit => ensure!(
-					old_limit >= new_limit && new_limit > 0,
-					<CommonError<T>>::CollectionTokenLimitExceeded
-				),
-				owner_can_transfer => ensure!(
-					old_limit || !new_limit,
-					<Error<T>>::OwnerPermissionsCantBeReverted,
-				),
-				owner_can_destroy => ensure!(
-					old_limit || !new_limit,
-					<Error<T>>::OwnerPermissionsCantBeReverted,
-				),
-				sponsored_data_rate_limit => {},
-				transfers_enabled => {},
-			);
-
-			target_collection.limits = new_limit;
+			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
 				collection_id
modifiedpallets/unique/src/tests.rsdiffbeforeafterboth
--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -5,7 +5,7 @@
 use up_data_structs::{
 	COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,
 	CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,
-	TokenId,
+	TokenId, MAX_TOKEN_OWNERSHIP,
 };
 use frame_support::{assert_noop, assert_ok};
 use sp_std::convert::TryInto;
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -263,6 +263,31 @@
 	pub meta_update_permission: MetaUpdatePermission,
 }
 
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative, MaxEncodedLen)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default(bound = ""))]
+pub struct CreateCollectionData<AccountId> {
+	#[derivative(Default(value = "CollectionMode::NFT"))]
+	pub mode: CollectionMode,
+	pub access: Option<AccessMode>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
+	pub schema_version: Option<SchemaVersion>,
+	pub pending_sponsor: Option<AccountId>,
+	pub limits: Option<CollectionLimits>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
+	pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct NftItemType<AccountId> {
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -68,9 +68,10 @@
     "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
     "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
+    "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --input src/interfaces/ --package .",
-    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint ws://localhost:9944 --output src/interfaces/ --package .",
-    "polkadot-types": "yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
+    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
+    "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
   },
   "author": "",
   "license": "SEE LICENSE IN ../LICENSE",
modifiedtests/src/check-event/createCollectionEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/createCollectionEvent.test.ts
+++ b/tests/src/check-event/createCollectionEvent.test.ts
@@ -27,7 +27,7 @@
   });
   it('Check event from createCollection(): ', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const tx = api.tx.unique.createCollection([0x31], [0x32], '0x33', 'NFT');
+      const tx = api.tx.unique.createCollectionEx({name: [0x31], description: [0x32], tokenPrefix: '0x33', mode: 'NFT'});
       const events = await submitTransactionAsync(alice, tx);
       const msg = JSON.stringify(uniqueEventMessage(events));
       expect(msg).to.be.contain(checkSection);
modifiedtests/src/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -3,12 +3,11 @@
 // file 'LICENSE', which is part of this source code package.
 //
 
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import {createCollectionExpectFailure, createCollectionExpectSuccess} from './util/helpers';
+import {expect} from 'chai';
+import privateKey from './substrate/privateKey';
+import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';
+import {createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo} from './util/helpers';
 
-chai.use(chaiAsPromised);
-
 describe('integration test: ext. createCollection():', () => {
   it('Create new NFT collection', async () => {
     await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
@@ -28,6 +27,45 @@
   it('Create new ReFungible collection', async () => {
     await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
   });
+  it('Create new collection with extra fields', async () => {
+    await usingApi(async api => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const tx = api.tx.unique.createCollectionEx({
+        mode: {Fungible: 8},
+        access: 'AllowList',
+        name: [1],
+        description: [2],
+        tokenPrefix: '0x000000',
+        offchainSchema: '0x111111',
+        schemaVersion: 'Unique',
+        pendingSponsor: bob.address,
+        limits: {
+          accountTokenOwnershipLimit: 3,
+        },
+        variableOnChainSchema: '0x222222',
+        constOnChainSchema: '0x333333',
+        metaUpdatePermission: 'Admin',
+      });
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getCreateCollectionResult(events);
+
+      const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;
+      expect(collection.owner.toString()).to.equal(alice.address);
+      expect(collection.mode.asFungible.toNumber()).to.equal(8);
+      expect(collection.access.isAllowList).to.be.true;
+      expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]);
+      expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]);
+      expect(collection.tokenPrefix.toString()).to.equal('0x000000');
+      expect(collection.offchainSchema.toString()).to.equal('0x111111');
+      expect(collection.schemaVersion.isUnique).to.be.true;
+      expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address);
+      expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);
+      expect(collection.variableOnChainSchema.toString()).to.equal('0x222222');
+      expect(collection.constOnChainSchema.toString()).to.equal('0x333333');
+      expect(collection.metaUpdatePermission.isAdmin).to.be.true;
+    });
+  });
 });
 
 describe('(!negative test!) integration test: ext. createCollection():', () => {
@@ -40,4 +78,11 @@
   it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {
     await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}});
   });
+  it('fails when bad limits are set', async () => {
+    await usingApi(async api => {
+      const alice = privateKey('//Alice');
+      const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}});
+      await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/);
+    });
+  });
 });
addedtests/src/interfaces/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/tests/src/interfaces/.gitignore
@@ -0,0 +1 @@
+metadata.json
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -69,6 +69,10 @@
        **/
       CollectionDescriptionLimitExceeded: AugmentedError<ApiType>;
       /**
+       * Collection limit bounds per collection exceeded
+       **/
+      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
+      /**
        * Collection name can not be longer than 63 char.
        **/
       CollectionNameLimitExceeded: AugmentedError<ApiType>;
@@ -97,6 +101,10 @@
        **/
       NoPermission: AugmentedError<ApiType>;
       /**
+       * Tried to enable permissions which are only permitted to be disabled
+       **/
+      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
+      /**
        * Collection is not in mint mode.
        **/
       PublicMintingNotAllowed: AugmentedError<ApiType>;
@@ -437,10 +445,6 @@
        **/
       CollectionDecimalPointLimitExceeded: AugmentedError<ApiType>;
       /**
-       * Collection limit bounds per collection exceeded
-       **/
-      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
-      /**
        * This address is not set as sponsor, use setCollectionSponsor first.
        **/
       ConfirmUnsetSponsorFail: AugmentedError<ApiType>;
@@ -448,10 +452,6 @@
        * Length of items properties must be greater than 0.
        **/
       EmptyArgument: AugmentedError<ApiType>;
-      /**
-       * Tried to enable permissions which are only permitted to be disabled
-       **/
-      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
       /**
        * Generic error
        **/
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -2,7 +2,7 @@
 /* eslint-disable */
 
 import type { CumulusPrimitivesParachainInherentParachainInherentData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';
 import type { ApiTypes, SubmittableExtrinsic } from '@polkadot/api/types';
 import type { Bytes, Compact, Option, U256, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types';
 import type { Extrinsic } from '@polkadot/types/interfaces/extrinsics';
@@ -671,6 +671,12 @@
        **/
       createCollection: AugmentedSubmittable<(collectionName: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], collectionDescription: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], tokenPrefix: Bytes | string | Uint8Array, mode: UpDataStructsCollectionMode | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Vec<u16>, Vec<u16>, Bytes, UpDataStructsCollectionMode]>;
       /**
+       * This method creates a collection
+       * 
+       * Prefer it to deprecated [`created_collection`] method
+       **/
+      createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; offchainSchema?: any; schemaVersion?: any; pendingSponsor?: any; limits?: any; variableOnChainSchema?: any; constOnChainSchema?: any; metaUpdatePermission?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [UpDataStructsCreateCollectionData]>;
+      /**
        * This method creates a concrete instance of NFT Collection created with CreateCollection method.
        * 
        * # Permissions
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -3,7 +3,7 @@
 
 import type { EthereumBlock, EthereumLog, EthereumReceipt, EthereumTransactionLegacyTransaction, EvmCoreErrorExitReason, FpRpcTransactionStatus } from './ethereum';
 import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundStatus, CumulusPalletXcmpQueueOutboundStatus, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1PersistedValidationData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
 import type { BitVec, Bool, Bytes, Data, I128, I16, I256, I32, I64, I8, Json, Null, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
 import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations';
@@ -1021,6 +1021,7 @@
     UpDataStructsCollectionLimits: UpDataStructsCollectionLimits;
     UpDataStructsCollectionMode: UpDataStructsCollectionMode;
     UpDataStructsCollectionStats: UpDataStructsCollectionStats;
+    UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData;
     UpDataStructsCreateItemData: UpDataStructsCreateItemData;
     UpDataStructsMetaUpdatePermission: UpDataStructsMetaUpdatePermission;
     UpDataStructsSchemaVersion: UpDataStructsSchemaVersion;
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -67,6 +67,20 @@
       constOnChainSchema: 'Vec<u8>',
       metaUpdatePermission: 'UpDataStructsMetaUpdatePermission',
     },
+    UpDataStructsCreateCollectionData: {
+      mode: 'UpDataStructsCollectionMode',
+      access: 'Option<UpDataStructsAccessMode>',
+      name: 'Vec<u16>',
+      description: 'Vec<u16>',
+      tokenPrefix: 'Vec<u8>',
+      offchainSchema: 'Vec<u8>',
+      schemaVersion: 'Option<UpDataStructsSchemaVersion>',
+      pendingSponsor: 'Option<AccountId>',
+      limits: 'Option<UpDataStructsCollectionLimits>',
+      variableOnChainSchema: 'Vec<u8>',
+      constOnChainSchema: 'Vec<u8>',
+      metaUpdatePermission: 'Option<UpDataStructsMetaUpdatePermission>',
+    },
     UpDataStructsCollectionStats: {
       created: 'u32',
       destroyed: 'u32',
@@ -76,7 +90,13 @@
     UpDataStructsTokenId: 'u32',
     PalletNonfungibleItemData: mkDummy('NftItemData'),
     PalletRefungibleItemData: mkDummy('RftItemData'),
-    UpDataStructsCollectionMode: mkDummy('CollectionMode'),
+    UpDataStructsCollectionMode: {
+      _enum: {
+        NFT: null,
+        Fungible: 'u32',
+        ReFungible: null,
+      },
+    },
     UpDataStructsCreateItemData: mkDummy('CreateItemData'),
     UpDataStructsCollectionLimits: {
       accountTokenOwnershipLimit: 'Option<u32>',
@@ -101,7 +121,9 @@
     UpDataStructsAccessMode: {
       _enum: ['Normal', 'AllowList'],
     },
-    UpDataStructsSchemaVersion: mkDummy('SchemaVersion'),
+    UpDataStructsSchemaVersion: {
+      _enum: ['ImageURL', 'Unique'],
+    },
 
     PalletUnqSchedulerScheduledV2: mkDummy('ScheduledV2'),
     PalletUnqSchedulerCallSpec: mkDummy('CallSpec'),
modifiedtests/src/interfaces/unique/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/types.ts
+++ b/tests/src/interfaces/unique/types.ts
@@ -77,8 +77,11 @@
 }
 
 /** @name UpDataStructsCollectionMode */
-export interface UpDataStructsCollectionMode extends Struct {
-  readonly dummyCollectionMode: u32;
+export interface UpDataStructsCollectionMode extends Enum {
+  readonly isNft: boolean;
+  readonly isFungible: boolean;
+  readonly asFungible: u32;
+  readonly isReFungible: boolean;
 }
 
 /** @name UpDataStructsCollectionStats */
@@ -88,6 +91,22 @@
   readonly alive: u32;
 }
 
+/** @name UpDataStructsCreateCollectionData */
+export interface UpDataStructsCreateCollectionData extends Struct {
+  readonly mode: UpDataStructsCollectionMode;
+  readonly access: Option<UpDataStructsAccessMode>;
+  readonly name: Vec<u16>;
+  readonly description: Vec<u16>;
+  readonly tokenPrefix: Bytes;
+  readonly offchainSchema: Bytes;
+  readonly schemaVersion: Option<UpDataStructsSchemaVersion>;
+  readonly pendingSponsor: Option<AccountId>;
+  readonly limits: Option<UpDataStructsCollectionLimits>;
+  readonly variableOnChainSchema: Bytes;
+  readonly constOnChainSchema: Bytes;
+  readonly metaUpdatePermission: Option<UpDataStructsMetaUpdatePermission>;
+}
+
 /** @name UpDataStructsCreateItemData */
 export interface UpDataStructsCreateItemData extends Struct {
   readonly dummyCreateItemData: u32;
@@ -101,8 +120,9 @@
 }
 
 /** @name UpDataStructsSchemaVersion */
-export interface UpDataStructsSchemaVersion extends Struct {
-  readonly dummySchemaVersion: u32;
+export interface UpDataStructsSchemaVersion extends Enum {
+  readonly isImageUrl: boolean;
+  readonly isUnique: boolean;
 }
 
 /** @name UpDataStructsSponsorshipState */
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -95,6 +95,32 @@
   return TransactionStatus.Fail;
 }
 
+export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise<EventRecord[]> {
+  return new Promise(async (res, rej) => {
+    try {
+      await transaction.signAndSend(sender, ({events, status}) => {
+        if (!status.isInBlock && !status.isFinalized) return;
+        for (const {event} of events) {
+          if (api.events.system.ExtrinsicSuccess.is(event)) {
+            res(events);
+          } else if (api.events.system.ExtrinsicFailed.is(event)) {
+            const {data: [error]} = event;
+            if (error.isModule) {
+              const decoded = api.registry.findMetaError(error.asModule);
+              const {method, section} = decoded;
+              rej(new Error(`${section}.${method}`));
+            } else {
+              rej(new Error(error.toString()));
+            }
+          }
+        }
+      });
+    } catch (e) {
+      rej(e);
+    }
+  });
+}
+
 export function
 submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
   /* eslint no-async-promise-executor: "off" */
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -49,7 +49,7 @@
     };
   } else if ('Substrate' in input) {
     return input;
-  }else if ('substrate' in input) {
+  } else if ('substrate' in input) {
     return {
       Substrate: (input as any).substrate,
     };
@@ -116,15 +116,15 @@
 
 export interface IChainLimits {
   collectionNumbersLimit: number;
-	accountTokenOwnershipLimit: number;
-	collectionsAdminsLimit: number;
-	customDataLimit: number;
-	nftSponsorTransferTimeout: number;
-	fungibleSponsorTransferTimeout: number;
-	refungibleSponsorTransferTimeout: number;
-	offchainSchemaLimit: number;
-	variableOnChainSchemaLimit: number;
-	constOnChainSchemaLimit: number;
+  accountTokenOwnershipLimit: number;
+  collectionsAdminsLimit: number;
+  customDataLimit: number;
+  nftSponsorTransferTimeout: number;
+  fungibleSponsorTransferTimeout: number;
+  refungibleSponsorTransferTimeout: number;
+  offchainSchemaLimit: number;
+  variableOnChainSchemaLimit: number;
+  constOnChainSchemaLimit: number;
 }
 
 export interface IReFungibleTokenDataType {
@@ -283,7 +283,7 @@
       modeprm = {refungible: null};
     }
 
-    const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+    const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
     const events = await submitTransactionAsync(alicePrivateKey, tx);
     const result = getCreateCollectionResult(events);
 
@@ -329,7 +329,7 @@
 
     // Run the CreateCollection transaction
     const alicePrivateKey = privateKey('//Alice');
-    const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+    const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
     const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
     const result = getCreateCollectionResult(events);
 
@@ -557,7 +557,7 @@
 
   await usingApi(async (api) => {
 
-    const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+    const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
     const events = await submitTransactionAsync(sender, tx);
     const result = getGenericResult(events);
 
@@ -569,7 +569,7 @@
 
   await usingApi(async (api) => {
 
-    const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+    const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
     const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
     const result = getGenericResult(events);
 
@@ -811,8 +811,7 @@
 }
 
 export async function
-getFreeBalance(account: IKeyringPair) : Promise<bigint>
-{
+getFreeBalance(account: IKeyringPair): Promise<bigint> {
   let balance = 0n;
   await usingApi(async (api) => {
     balance = BigInt((await api.query.system.account(account.address)).data.free.toString());