git.delta.rocks / unique-network / refs/commits / 32d10d5b642a

difftreelog

Merge branch 'develop' into feature/CORE-386_1

bugrazoid2022-06-10parents: #db8b9ba #bb7c8cc.patch.diff
in: master

54 files changed

modifiedMakefilediffbeforeafterboth
--- a/Makefile
+++ b/Makefile
@@ -93,5 +93,9 @@
 bench-structure:
 	make _bench PALLET=structure
 
+.PHONY: bench-rmrk-core
+bench-rmrk-core:
+	make _bench PALLET=proxy-rmrk-core
+
 .PHONY: bench
-bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible
+bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible bench-rmrk-core
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -109,7 +109,7 @@
 	create_collection_raw(
 		owner,
 		CollectionMode::NFT,
-		|owner, data| <Pallet<T>>::init_collection(owner, data),
+		|owner, data| <Pallet<T>>::init_collection(owner, data, true),
 		|h| h,
 	)
 }
modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -11,7 +11,7 @@
 use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};
 
 // TODO: move to benchmarking
-/// Price of [`dispatch_call`] call with noop `call` argument
+/// Price of [`dispatch_tx`] call with noop `call` argument
 pub fn dispatch_weight<T: Config>() -> Weight {
 	// Read collection
 	<T as frame_system::Config>::DbWeight::get().reads(1)
@@ -21,7 +21,7 @@
 }
 
 /// Helper function to implement substrate calls for common collection methods
-pub fn dispatch_call<
+pub fn dispatch_tx<
 	T: Config,
 	C: FnOnce(&dyn CommonCollectionOperations<T>) -> DispatchResultWithPostInfo,
 >(
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -427,7 +427,7 @@
 		/// Target collection doesn't supports this operation
 		UnsupportedOperation,
 
-		/// Not sufficient founds to perform action
+		/// Not sufficient funds to perform action
 		NotSufficientFounds,
 
 		/// Collection has nesting disabled
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -51,7 +51,7 @@
 	create_collection_raw(
 		owner,
 		CollectionMode::NFT,
-		<Pallet<T>>::init_collection,
+		|owner, data| <Pallet<T>>::init_collection(owner, data, true),
 		NonfungibleHandle::cast,
 	)
 }
@@ -99,7 +99,7 @@
 			sender: cross_from_sub(owner); burner: cross_sub;
 		};
 		let item = create_max_item(&collection, &sender, burner.clone())?;
-	}: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)}
+	}: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)?}
 
 	burn_recursively_breadth_plus_self_plus_self_per_each_raw {
 		let b in 0..200;
@@ -111,7 +111,7 @@
 		for i in 0..b {
 			create_max_item(&collection, &sender, T::CrossTokenAddressMapping::token_to_address(collection.id, item))?;
 		}
-	}: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)}
+	}: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)?}
 
 	transfer {
 		bench_init!{
@@ -183,7 +183,7 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props)?}
+	}: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props, false)?}
 
 	delete_token_properties {
 		let b in 0..MAX_PROPERTIES_PER_ITEM;
@@ -205,7 +205,7 @@
 			value: property_value(),
 		}).collect::<Vec<_>>();
 		let item = create_max_item(&collection, &owner, owner.clone())?;
-		<Pallet<T>>::set_token_properties(&collection, &owner, item, props)?;
+		<Pallet<T>>::set_token_properties(&collection, &owner, item, props, false)?;
 		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
 	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete)?}
 }
addedpallets/proxy-rmrk-core/src/benchmarking.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/proxy-rmrk-core/src/benchmarking.rs
@@ -0,0 +1,26 @@
+use sp_std::vec;
+
+use frame_benchmarking::{benchmarks, account};
+use frame_system::RawOrigin;
+use frame_support::{
+	traits::{Currency, Get},
+	BoundedVec,
+};
+
+use crate::{Config, Pallet, Call};
+
+const SEED: u32 = 1;
+
+fn create_data<S: Get<u32>>() -> BoundedVec<u8, S> {
+	vec![0; S::get() as usize].try_into().expect("size == S")
+}
+
+benchmarks! {
+	create_collection {
+		let caller = account("caller", 0, SEED);
+		<T as pallet_common::Config>::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
+		let metadata = create_data();
+		// TODO: Fix CollectionTokenPrefixLimitExceeded with create_data
+		let symbol = vec![].try_into().expect("0 <= x");
+	}: _(RawOrigin::Signed(caller), metadata, None, symbol)
+}
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceAccepted {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceRemovalAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141		PrioritySet {142			collection_id: RmrkCollectionId,143			nft_id: RmrkNftId,144		},145	}146147	#[pallet::error]148	pub enum Error<T> {149		/* Unique-specific events */150		CorruptedCollectionType,151		NftTypeEncodeError,152		RmrkPropertyKeyIsTooLong,153		RmrkPropertyValueIsTooLong,154155		/* RMRK compatible events */156		CollectionNotEmpty,157		NoAvailableCollectionId,158		NoAvailableNftId,159		CollectionUnknown,160		NoPermission,161		NonTransferable,162		CollectionFullOrLocked,163		ResourceDoesntExist,164		CannotSendToDescendentOrSelf,165		CannotAcceptNonOwnedNft,166		CannotRejectNonOwnedNft,167		ResourceNotPending,168	}169170	#[pallet::call]171	impl<T: Config> Pallet<T> {172		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]173		#[transactional]174		pub fn create_collection(175			origin: OriginFor<T>,176			metadata: RmrkString,177			max: Option<u32>,178			symbol: RmrkCollectionSymbol,179		) -> DispatchResult {180			let sender = ensure_signed(origin)?;181182			let limits = CollectionLimits {183				owner_can_transfer: Some(false),184				token_limit: max,185				..Default::default()186			};187188			let data = CreateCollectionData {189				limits: Some(limits),190				token_prefix: symbol191					.into_inner()192					.try_into()193					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,194				permissions: Some(CollectionPermissions {195					nesting: Some(NestingRule::Owner),196					..Default::default()197				}),198				..Default::default()199			};200201			let unique_collection_id = Self::init_collection(202				T::CrossAccountId::from_sub(sender.clone()),203				data,204				[205					Self::rmrk_property(Metadata, &metadata)?,206					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,207				]208				.into_iter(),209			)?;210			let rmrk_collection_id = <CollectionIndex<T>>::get();211212			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);213			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);214215			<CollectionIndex<T>>::mutate(|n| *n += 1);216217			Self::deposit_event(Event::CollectionCreated {218				issuer: sender,219				collection_id: rmrk_collection_id,220			});221222			Ok(())223		}224225		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]226		#[transactional]227		pub fn destroy_collection(228			origin: OriginFor<T>,229			collection_id: RmrkCollectionId,230		) -> DispatchResult {231			let sender = ensure_signed(origin)?;232			let cross_sender = T::CrossAccountId::from_sub(sender.clone());233234			let collection = Self::get_typed_nft_collection(235				Self::unique_collection_id(collection_id)?,236				misc::CollectionType::Regular,237			)?;238			collection.check_is_external()?;239240			<PalletNft<T>>::destroy_collection(collection, &cross_sender)241				.map_err(Self::map_unique_err_to_proxy)?;242243			Self::deposit_event(Event::CollectionDestroyed {244				issuer: sender,245				collection_id,246			});247248			Ok(())249		}250251		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]252		#[transactional]253		pub fn change_collection_issuer(254			origin: OriginFor<T>,255			collection_id: RmrkCollectionId,256			new_issuer: <T::Lookup as StaticLookup>::Source,257		) -> DispatchResult {258			let sender = ensure_signed(origin)?;259260			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;261			collection.check_is_external()?;262263			let new_issuer = T::Lookup::lookup(new_issuer)?;264265			Self::change_collection_owner(266				Self::unique_collection_id(collection_id)?,267				misc::CollectionType::Regular,268				sender.clone(),269				new_issuer.clone(),270			)?;271272			Self::deposit_event(Event::IssuerChanged {273				old_issuer: sender,274				new_issuer,275				collection_id,276			});277278			Ok(())279		}280281		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]282		#[transactional]283		pub fn lock_collection(284			origin: OriginFor<T>,285			collection_id: RmrkCollectionId,286		) -> DispatchResult {287			let sender = ensure_signed(origin)?;288			let cross_sender = T::CrossAccountId::from_sub(sender.clone());289290			let collection = Self::get_typed_nft_collection(291				Self::unique_collection_id(collection_id)?,292				misc::CollectionType::Regular,293			)?;294			collection.check_is_external()?;295296			Self::check_collection_owner(&collection, &cross_sender)?;297298			let token_count = collection.total_supply();299300			let mut collection = collection.into_inner();301			collection.limits.token_limit = Some(token_count);302			collection.save()?;303304			Self::deposit_event(Event::CollectionLocked {305				issuer: sender,306				collection_id,307			});308309			Ok(())310		}311312		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]313		#[transactional]314		pub fn mint_nft(315			origin: OriginFor<T>,316			owner: T::AccountId,317			collection_id: RmrkCollectionId,318			recipient: Option<T::AccountId>,319			royalty_amount: Option<Permill>,320			metadata: RmrkString,321			transferable: bool,322		) -> DispatchResult {323			let sender = ensure_signed(origin)?;324			let sender = T::CrossAccountId::from_sub(sender);325			let cross_owner = T::CrossAccountId::from_sub(owner.clone());326327			let collection = Self::get_typed_nft_collection(328				Self::unique_collection_id(collection_id)?,329				misc::CollectionType::Regular,330			)?;331			collection.check_is_external()?;332333			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {334				recipient: recipient.unwrap_or_else(|| owner.clone()),335				amount,336			});337338			let nft_id = Self::create_nft(339				&sender,340				&cross_owner,341				&collection,342				[343					Self::rmrk_property(TokenType, &NftType::Regular)?,344					Self::rmrk_property(Transferable, &transferable)?,345					Self::rmrk_property(PendingNftAccept, &false)?,346					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,347					Self::rmrk_property(Metadata, &metadata)?,348					Self::rmrk_property(Equipped, &false)?,349					Self::rmrk_property(350						ResourceCollection,351						&Self::init_collection(352							sender.clone(),353							CreateCollectionData {354								..Default::default()355							},356							[Self::rmrk_property(357								CollectionType,358								&misc::CollectionType::Resource,359							)?]360							.into_iter(),361						)?,362					)?, // todo possibly add limits to the collection if rmrk warrants them363					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,364				]365				.into_iter(),366			)367			.map_err(|err| match err {368				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),369				err => Self::map_unique_err_to_proxy(err),370			})?;371372			Self::deposit_event(Event::NftMinted {373				owner,374				collection_id,375				nft_id: nft_id.0,376			});377378			Ok(())379		}380381		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]382		#[transactional]383		pub fn burn_nft(384			origin: OriginFor<T>,385			collection_id: RmrkCollectionId,386			nft_id: RmrkNftId,387		) -> DispatchResult {388			let sender = ensure_signed(origin)?;389			let cross_sender = T::CrossAccountId::from_sub(sender.clone());390391			let collection = Self::get_typed_nft_collection(392				Self::unique_collection_id(collection_id)?,393				misc::CollectionType::Regular,394			)?;395			collection.check_is_external()?;396397			Self::destroy_nft(398				cross_sender,399				Self::unique_collection_id(collection_id)?,400				nft_id.into(),401			)402			.map_err(Self::map_unique_err_to_proxy)?;403404			Self::deposit_event(Event::NFTBurned {405				owner: sender,406				nft_id,407			});408409			Ok(())410		}411412		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]413		#[transactional]414		pub fn send(415			origin: OriginFor<T>,416			rmrk_collection_id: RmrkCollectionId,417			rmrk_nft_id: RmrkNftId,418			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,419		) -> DispatchResult {420			let sender = ensure_signed(origin.clone())?;421			let cross_sender = T::CrossAccountId::from_sub(sender.clone());422423			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;424			let nft_id = rmrk_nft_id.into();425426			let collection =427				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;428			collection.check_is_external()?;429430			let token_data =431				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;432433			let from = token_data.owner;434435			ensure!(436				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,437				<Error<T>>::NonTransferable438			);439440			ensure!(441				!Self::get_nft_property_decoded(442					collection_id,443					nft_id,444					RmrkProperty::PendingNftAccept445				)?,446				<Error<T>>::NoPermission447			);448449			let target_owner;450			let approval_required;451452			match new_owner {453				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {454					target_owner = T::CrossAccountId::from_sub(account_id.clone());455					approval_required = false;456				}457				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(458					target_collection_id,459					target_nft_id,460				) => {461					let target_collection_id = Self::unique_collection_id(target_collection_id)?;462463					let target_nft_budget = budget::Value::new(NESTING_BUDGET);464465					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(466						target_collection_id,467						target_nft_id.into(),468						Some((collection_id, nft_id)),469						&target_nft_budget,470					)471					.map_err(Self::map_unique_err_to_proxy)?;472473					approval_required = cross_sender != target_nft_owner;474475					if approval_required {476						target_owner = target_nft_owner;477478						<PalletNft<T>>::set_scoped_token_property(479							collection.id,480							nft_id,481							PropertyScope::Rmrk,482							Self::rmrk_property(PendingNftAccept, &approval_required)?,483						)?;484					} else {485						target_owner = T::CrossTokenAddressMapping::token_to_address(486							target_collection_id,487							target_nft_id.into(),488						);489					}490				}491			}492493			let src_nft_budget = budget::Value::new(NESTING_BUDGET);494495			<PalletNft<T>>::transfer_from(496				&collection,497				&cross_sender,498				&from,499				&target_owner,500				nft_id,501				&src_nft_budget,502			)503			.map_err(Self::map_unique_err_to_proxy)?;504505			Self::deposit_event(Event::NFTSent {506				sender,507				recipient: new_owner,508				collection_id: rmrk_collection_id,509				nft_id: rmrk_nft_id,510				approval_required,511			});512513			Ok(())514		}515516		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]517		#[transactional]518		pub fn accept_nft(519			origin: OriginFor<T>,520			rmrk_collection_id: RmrkCollectionId,521			rmrk_nft_id: RmrkNftId,522			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,523		) -> DispatchResult {524			let sender = ensure_signed(origin.clone())?;525			let cross_sender = T::CrossAccountId::from_sub(sender.clone());526527			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;528			let nft_id = rmrk_nft_id.into();529530			let collection =531				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;532			collection.check_is_external()?;533534			let new_cross_owner = match new_owner {535				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {536					T::CrossAccountId::from_sub(account_id.clone())537				}538				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(539					target_collection_id,540					target_nft_id,541				) => {542					let target_collection_id = Self::unique_collection_id(target_collection_id)?;543544					T::CrossTokenAddressMapping::token_to_address(545						target_collection_id,546						TokenId(target_nft_id),547					)548				}549			};550551			let budget = budget::Value::new(NESTING_BUDGET);552553			<PalletNft<T>>::transfer(554				&collection,555				&cross_sender,556				&new_cross_owner,557				nft_id,558				&budget,559			)560			.map_err(|err| {561				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {562					<Error<T>>::CannotAcceptNonOwnedNft.into()563				} else {564					Self::map_unique_err_to_proxy(err)565				}566			})?;567568			<PalletNft<T>>::set_scoped_token_property(569				collection.id,570				nft_id,571				PropertyScope::Rmrk,572				Self::rmrk_property(PendingNftAccept, &false)?,573			)?;574575			Self::deposit_event(Event::NFTAccepted {576				sender,577				recipient: new_owner,578				collection_id: rmrk_collection_id,579				nft_id: rmrk_nft_id,580			});581582			Ok(())583		}584585		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]586		#[transactional]587		pub fn reject_nft(588			origin: OriginFor<T>,589			rmrk_collection_id: RmrkCollectionId,590			rmrk_nft_id: RmrkNftId,591		) -> DispatchResult {592			let sender = ensure_signed(origin)?;593			let cross_sender = T::CrossAccountId::from_sub(sender.clone());594595			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;596			let nft_id = rmrk_nft_id.into();597598			let collection =599				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;600			collection.check_is_external()?;601602			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {603				if err == <CommonError<T>>::NoPermission.into()604					|| err == <CommonError<T>>::ApprovedValueTooLow.into()605				{606					<Error<T>>::CannotRejectNonOwnedNft.into()607				} else {608					Self::map_unique_err_to_proxy(err)609				}610			})?;611612			Self::deposit_event(Event::NFTRejected {613				sender,614				collection_id: rmrk_collection_id,615				nft_id: rmrk_nft_id,616			});617618			Ok(())619		}620621		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]622		#[transactional]623		pub fn accept_resource(624			origin: OriginFor<T>,625			rmrk_collection_id: RmrkCollectionId,626			rmrk_nft_id: RmrkNftId,627			rmrk_resource_id: RmrkResourceId,628		) -> DispatchResult {629			let sender = ensure_signed(origin)?;630			let cross_sender = T::CrossAccountId::from_sub(sender);631632			let collection_id = Self::unique_collection_id(rmrk_collection_id)633				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;634			let collection =635				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;636			collection.check_is_external()?;637638			let nft_id = rmrk_nft_id.into();639			let resource_id = rmrk_resource_id.into();640641			let budget = budget::Value::new(NESTING_BUDGET);642643			let nft_owner =644				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)645					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;646647			let resource_collection_id: CollectionId =648				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)649					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;650651			let is_pending: bool = Self::get_nft_property_decoded(652				resource_collection_id,653				resource_id,654				PendingResourceAccept,655			)656			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;657658			ensure!(is_pending, <Error<T>>::ResourceNotPending);659660			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);661662			<PalletNft<T>>::set_scoped_token_property(663				resource_collection_id,664				rmrk_resource_id.into(),665				PropertyScope::Rmrk,666				Self::rmrk_property(PendingResourceAccept, &false)?,667			)?;668669			Self::deposit_event(Event::<T>::ResourceAccepted {670				nft_id: rmrk_nft_id,671				resource_id: rmrk_resource_id,672			});673674			Ok(())675		}676677		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]678		#[transactional]679		pub fn accept_resource_removal(680			origin: OriginFor<T>,681			rmrk_collection_id: RmrkCollectionId,682			rmrk_nft_id: RmrkNftId,683			rmrk_resource_id: RmrkResourceId,684		) -> DispatchResult {685			let sender = ensure_signed(origin)?;686			let cross_sender = T::CrossAccountId::from_sub(sender);687688			let collection_id = Self::unique_collection_id(rmrk_collection_id)689				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;690			let collection =691				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;692			collection.check_is_external()?;693694			let nft_id = rmrk_nft_id.into();695			let resource_id = rmrk_resource_id.into();696697			let budget = budget::Value::new(NESTING_BUDGET);698699			let nft_owner =700				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)701					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;702703			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);704705			let resource_collection_id: CollectionId =706				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)707					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;708709			let is_pending: bool = Self::get_nft_property_decoded(710				resource_collection_id,711				resource_id,712				PendingResourceRemoval,713			)714			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716			ensure!(is_pending, <Error<T>>::ResourceNotPending);717718			let resource_collection = Self::get_typed_nft_collection(719				resource_collection_id,720				misc::CollectionType::Resource,721			)?;722723			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())724				.map_err(Self::map_unique_err_to_proxy)?;725726			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {727				nft_id: rmrk_nft_id,728				resource_id: rmrk_resource_id,729			});730731			Ok(())732		}733734		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]735		#[transactional]736		pub fn set_property(737			origin: OriginFor<T>,738			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,739			maybe_nft_id: Option<RmrkNftId>,740			key: RmrkKeyString,741			value: RmrkValueString,742		) -> DispatchResult {743			let sender = ensure_signed(origin)?;744			let sender = T::CrossAccountId::from_sub(sender);745746			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;747			let collection =748				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;749			collection.check_is_external()?;750751			let budget = budget::Value::new(NESTING_BUDGET);752753			match maybe_nft_id {754				Some(nft_id) => {755					let token_id: TokenId = nft_id.into();756757					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;758					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;759760					<PalletNft<T>>::set_scoped_token_property(761						collection_id,762						token_id,763						PropertyScope::Rmrk,764						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,765					)?;766				}767				None => {768					let collection = Self::get_typed_nft_collection(769						collection_id,770						misc::CollectionType::Regular,771					)?;772773					Self::check_collection_owner(&collection, &sender)?;774775					<PalletCommon<T>>::set_scoped_collection_property(776						collection_id,777						PropertyScope::Rmrk,778						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,779					)?;780				}781			}782783			Self::deposit_event(Event::PropertySet {784				collection_id: rmrk_collection_id,785				maybe_nft_id,786				key,787				value,788			});789790			Ok(())791		}792793		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]794		#[transactional]795		pub fn set_priority(796			origin: OriginFor<T>,797			rmrk_collection_id: RmrkCollectionId,798			rmrk_nft_id: RmrkNftId,799			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,800		) -> DispatchResult {801			let sender = ensure_signed(origin)?;802			let sender = T::CrossAccountId::from_sub(sender);803804			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;805			let nft_id = rmrk_nft_id.into();806807			let collection =808				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809			collection.check_is_external()?;810811			let budget = budget::Value::new(NESTING_BUDGET);812813			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;814			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;815816			<PalletNft<T>>::set_scoped_token_property(817				collection_id,818				nft_id,819				PropertyScope::Rmrk,820				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,821			)?;822823			Self::deposit_event(Event::<T>::PrioritySet {824				collection_id: rmrk_collection_id,825				nft_id: rmrk_nft_id,826			});827828			Ok(())829		}830831		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]832		#[transactional]833		pub fn add_basic_resource(834			origin: OriginFor<T>,835			rmrk_collection_id: RmrkCollectionId,836			nft_id: RmrkNftId,837			resource: RmrkBasicResource,838		) -> DispatchResult {839			let sender = ensure_signed(origin.clone())?;840841			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;842			let collection =843				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;844			collection.check_is_external()?;845846			let resource_id = Self::resource_add(847				sender,848				collection_id,849				nft_id.into(),850				[851					Self::rmrk_property(TokenType, &NftType::Resource)?,852					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,853					Self::rmrk_property(Src, &resource.src)?,854					Self::rmrk_property(Metadata, &resource.metadata)?,855					Self::rmrk_property(License, &resource.license)?,856					Self::rmrk_property(Thumb, &resource.thumb)?,857				]858				.into_iter(),859			)?;860861			Self::deposit_event(Event::ResourceAdded {862				nft_id,863				resource_id,864			});865			Ok(())866		}867868		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]869		#[transactional]870		pub fn add_composable_resource(871			origin: OriginFor<T>,872			rmrk_collection_id: RmrkCollectionId,873			nft_id: RmrkNftId,874			_resource_id: RmrkBoundedResource,875			resource: RmrkComposableResource,876		) -> DispatchResult {877			let sender = ensure_signed(origin.clone())?;878879			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;880			let collection =881				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;882			collection.check_is_external()?;883884			let resource_id = Self::resource_add(885				sender,886				collection_id,887				nft_id.into(),888				[889					Self::rmrk_property(TokenType, &NftType::Resource)?,890					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,891					Self::rmrk_property(Parts, &resource.parts)?,892					Self::rmrk_property(Base, &resource.base)?,893					Self::rmrk_property(Src, &resource.src)?,894					Self::rmrk_property(Metadata, &resource.metadata)?,895					Self::rmrk_property(License, &resource.license)?,896					Self::rmrk_property(Thumb, &resource.thumb)?,897				]898				.into_iter(),899			)?;900901			Self::deposit_event(Event::ResourceAdded {902				nft_id,903				resource_id,904			});905			Ok(())906		}907908		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]909		#[transactional]910		pub fn add_slot_resource(911			origin: OriginFor<T>,912			rmrk_collection_id: RmrkCollectionId,913			nft_id: RmrkNftId,914			resource: RmrkSlotResource,915		) -> DispatchResult {916			let sender = ensure_signed(origin.clone())?;917918			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;919			let collection =920				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;921			collection.check_is_external()?;922923			let resource_id = Self::resource_add(924				sender,925				collection_id,926				nft_id.into(),927				[928					Self::rmrk_property(TokenType, &NftType::Resource)?,929					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,930					Self::rmrk_property(Base, &resource.base)?,931					Self::rmrk_property(Src, &resource.src)?,932					Self::rmrk_property(Metadata, &resource.metadata)?,933					Self::rmrk_property(Slot, &resource.slot)?,934					Self::rmrk_property(License, &resource.license)?,935					Self::rmrk_property(Thumb, &resource.thumb)?,936				]937				.into_iter(),938			)?;939940			Self::deposit_event(Event::ResourceAdded {941				nft_id,942				resource_id,943			});944			Ok(())945		}946947		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]948		#[transactional]949		pub fn remove_resource(950			origin: OriginFor<T>,951			rmrk_collection_id: RmrkCollectionId,952			nft_id: RmrkNftId,953			resource_id: RmrkResourceId,954		) -> DispatchResult {955			let sender = ensure_signed(origin.clone())?;956957			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;958			let collection =959				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;960			collection.check_is_external()?;961962			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;963964			Self::deposit_event(Event::ResourceRemoval {965				nft_id,966				resource_id,967			});968			Ok(())969		}970	}971}972973impl<T: Config> Pallet<T> {974	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {975		let key = rmrk_key.to_key::<T>()?;976977		let scoped_key = PropertyScope::Rmrk978			.apply(key)979			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;980981		Ok(scoped_key)982	}983984	// todo think about renaming these985	pub fn rmrk_property<E: Encode>(986		rmrk_key: RmrkProperty,987		value: &E,988	) -> Result<Property, DispatchError> {989		let key = rmrk_key.to_key::<T>()?;990991		let value = value992			.encode()993			.try_into()994			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;995996		let property = Property { key, value };997998		Ok(property)999	}10001001	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1002		vec.decode()1003			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1004	}10051006	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1007	where1008		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1009	{1010		vec.rebind()1011			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1012	}10131014	fn init_collection(1015		sender: T::CrossAccountId,1016		data: CreateCollectionData<T::AccountId>,1017		properties: impl Iterator<Item = Property>,1018	) -> Result<CollectionId, DispatchError> {1019		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10201021		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1022			return Err(<Error<T>>::NoAvailableCollectionId.into());1023		}10241025		<PalletCommon<T>>::set_scoped_collection_properties(1026			collection_id?,1027			PropertyScope::Rmrk,1028			properties,1029		)?;10301031		collection_id1032	}10331034	pub fn create_nft(1035		sender: &T::CrossAccountId,1036		owner: &T::CrossAccountId,1037		collection: &NonfungibleHandle<T>,1038		properties: impl Iterator<Item = Property>,1039	) -> Result<TokenId, DispatchError> {1040		let data = CreateNftExData {1041			properties: BoundedVec::default(),1042			owner: owner.clone(),1043		};10441045		let budget = budget::Value::new(NESTING_BUDGET);10461047		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10481049		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10501051		<PalletNft<T>>::set_scoped_token_properties(1052			collection.id,1053			nft_id,1054			PropertyScope::Rmrk,1055			properties,1056		)?;10571058		Ok(nft_id)1059	}10601061	fn destroy_nft(1062		sender: T::CrossAccountId,1063		collection_id: CollectionId,1064		token_id: TokenId,1065	) -> DispatchResult {1066		let collection =1067			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10681069		let token_data =1070			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10711072		let from = token_data.owner;10731074		let budget = budget::Value::new(NESTING_BUDGET);10751076		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1077	}10781079	fn resource_add(1080		sender: T::AccountId,1081		collection_id: CollectionId,1082		token_id: TokenId,1083		resource_properties: impl Iterator<Item = Property>,1084	) -> Result<RmrkResourceId, DispatchError> {1085		let collection =1086			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1087		ensure!(collection.owner == sender, Error::<T>::NoPermission);10881089		let sender = T::CrossAccountId::from_sub(sender);1090		let budget = budget::Value::new(NESTING_BUDGET);10911092		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1093			.map_err(Self::map_unique_err_to_proxy)?;10941095		let pending = sender != nft_owner;10961097		let resource_collection_id: CollectionId =1098			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1099		let resource_collection =1100			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11011102		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11031104		let resource_id = Self::create_nft(1105			&sender,1106			&nft_owner,1107			&resource_collection,1108			resource_properties.chain(1109				[1110					Self::rmrk_property(PendingResourceAccept, &pending)?,1111					Self::rmrk_property(PendingResourceRemoval, &false)?,1112				]1113				.into_iter(),1114			),1115		)1116		.map_err(|err| match err {1117			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1118			err => Self::map_unique_err_to_proxy(err),1119		})?;11201121		Ok(resource_id.0)1122	}11231124	fn resource_remove(1125		sender: T::AccountId,1126		collection_id: CollectionId,1127		nft_id: TokenId,1128		resource_id: TokenId,1129	) -> DispatchResult {1130		let collection =1131			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1132		ensure!(collection.owner == sender, Error::<T>::NoPermission);11331134		let resource_collection_id: CollectionId =1135			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1136		let resource_collection =1137			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1138		ensure!(1139			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1140			Error::<T>::ResourceDoesntExist1141		);11421143		let budget = up_data_structs::budget::Value::new(10);1144		let topmost_owner =1145			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;11461147		let sender = T::CrossAccountId::from_sub(sender);1148		if topmost_owner == sender {1149			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1150				.map_err(Self::map_unique_err_to_proxy)?;1151		} else {1152			<PalletNft<T>>::set_scoped_token_property(1153				resource_collection_id,1154				resource_id,1155				PropertyScope::Rmrk,1156				Self::rmrk_property(PendingResourceRemoval, &true)?,1157			)?;1158		}11591160		Ok(())1161	}11621163	fn change_collection_owner(1164		collection_id: CollectionId,1165		collection_type: misc::CollectionType,1166		sender: T::AccountId,1167		new_owner: T::AccountId,1168	) -> DispatchResult {1169		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1170		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11711172		let mut collection = collection.into_inner();11731174		collection.owner = new_owner;1175		collection.save()1176	}11771178	fn check_collection_owner(1179		collection: &NonfungibleHandle<T>,1180		account: &T::CrossAccountId,1181	) -> DispatchResult {1182		collection1183			.check_is_owner(account)1184			.map_err(Self::map_unique_err_to_proxy)1185	}11861187	pub fn last_collection_idx() -> RmrkCollectionId {1188		<CollectionIndex<T>>::get()1189	}11901191	pub fn unique_collection_id(1192		rmrk_collection_id: RmrkCollectionId,1193	) -> Result<CollectionId, DispatchError> {1194		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1195			.map_err(|_| <Error<T>>::CollectionUnknown.into())1196	}11971198	pub fn rmrk_collection_id(1199		unique_collection_id: CollectionId,1200	) -> Result<RmrkCollectionId, DispatchError> {1201		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1202			.map_err(|_| <Error<T>>::CollectionUnknown.into())1203	}12041205	pub fn get_nft_collection(1206		collection_id: CollectionId,1207	) -> Result<NonfungibleHandle<T>, DispatchError> {1208		let collection = <CollectionHandle<T>>::try_get(collection_id)1209			.map_err(|_| <Error<T>>::CollectionUnknown)?;12101211		match collection.mode {1212			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1213			_ => Err(<Error<T>>::CollectionUnknown.into()),1214		}1215	}12161217	pub fn collection_exists(collection_id: CollectionId) -> bool {1218		<CollectionHandle<T>>::try_get(collection_id).is_ok()1219	}12201221	pub fn get_collection_property(1222		collection_id: CollectionId,1223		key: RmrkProperty,1224	) -> Result<PropertyValue, DispatchError> {1225		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1226			.get(&Self::rmrk_property_key(key)?)1227			.ok_or(<Error<T>>::CollectionUnknown)?1228			.clone();12291230		Ok(collection_property)1231	}12321233	pub fn get_collection_property_decoded<V: Decode>(1234		collection_id: CollectionId,1235		key: RmrkProperty,1236	) -> Result<V, DispatchError> {1237		Self::decode_property(Self::get_collection_property(collection_id, key)?)1238	}12391240	pub fn get_collection_type(1241		collection_id: CollectionId,1242	) -> Result<misc::CollectionType, DispatchError> {1243		Self::get_collection_property_decoded(collection_id, CollectionType)1244			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1245	}12461247	pub fn ensure_collection_type(1248		collection_id: CollectionId,1249		collection_type: misc::CollectionType,1250	) -> DispatchResult {1251		let actual_type = Self::get_collection_type(collection_id)?;1252		ensure!(1253			actual_type == collection_type,1254			<CommonError<T>>::NoPermission1255		);12561257		Ok(())1258	}12591260	pub fn get_typed_nft_collection(1261		collection_id: CollectionId,1262		collection_type: misc::CollectionType,1263	) -> Result<NonfungibleHandle<T>, DispatchError> {1264		Self::ensure_collection_type(collection_id, collection_type)?;12651266		Self::get_nft_collection(collection_id)1267	}12681269	pub fn get_typed_nft_collection_mapped(1270		rmrk_collection_id: RmrkCollectionId,1271		collection_type: misc::CollectionType,1272	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1273		let unique_collection_id = match collection_type {1274			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1275			_ => rmrk_collection_id.into(),1276		};12771278		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12791280		Ok((collection, unique_collection_id))1281	}12821283	pub fn get_nft_property(1284		collection_id: CollectionId,1285		nft_id: TokenId,1286		key: RmrkProperty,1287	) -> Result<PropertyValue, DispatchError> {1288		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1289			.get(&Self::rmrk_property_key(key)?)1290			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1291			.clone();12921293		Ok(nft_property)1294	}12951296	pub fn get_nft_property_decoded<V: Decode>(1297		collection_id: CollectionId,1298		nft_id: TokenId,1299		key: RmrkProperty,1300	) -> Result<V, DispatchError> {1301		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1302	}13031304	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1305		<TokenData<T>>::contains_key((collection_id, nft_id))1306	}13071308	pub fn get_nft_type(1309		collection_id: CollectionId,1310		token_id: TokenId,1311	) -> Result<NftType, DispatchError> {1312		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1313			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1314	}13151316	pub fn ensure_nft_type(1317		collection_id: CollectionId,1318		token_id: TokenId,1319		nft_type: NftType,1320	) -> DispatchResult {1321		let actual_type = Self::get_nft_type(collection_id, token_id)?;1322		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13231324		Ok(())1325	}13261327	pub fn ensure_nft_owner(1328		collection_id: CollectionId,1329		token_id: TokenId,1330		possible_owner: &T::CrossAccountId,1331		nesting_budget: &dyn budget::Budget,1332	) -> DispatchResult {1333		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1334			possible_owner.clone(),1335			collection_id,1336			token_id,1337			None,1338			nesting_budget,1339		)1340		.map_err(Self::map_unique_err_to_proxy)?;13411342		ensure!(is_owned, <Error<T>>::NoPermission);13431344		Ok(())1345	}13461347	pub fn filter_user_properties<Key, Value, R, Mapper>(1348		collection_id: CollectionId,1349		token_id: Option<TokenId>,1350		filter_keys: Option<Vec<RmrkPropertyKey>>,1351		mapper: Mapper,1352	) -> Result<Vec<R>, DispatchError>1353	where1354		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1355		Value: Decode + Default,1356		Mapper: Fn(Key, Value) -> R,1357	{1358		filter_keys1359			.map(|keys| {1360				let properties = keys1361					.into_iter()1362					.filter_map(|key| {1363						let key: Key = key.try_into().ok()?;13641365						let value = match token_id {1366							Some(token_id) => Self::get_nft_property_decoded(1367								collection_id,1368								token_id,1369								UserProperty(key.as_ref()),1370							),1371							None => Self::get_collection_property_decoded(1372								collection_id,1373								UserProperty(key.as_ref()),1374							),1375						}1376						.ok()?;13771378						Some(mapper(key, value))1379					})1380					.collect();13811382				Ok(properties)1383			})1384			.unwrap_or_else(|| {1385				let properties =1386					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13871388				Ok(properties)1389			})1390	}13911392	pub fn iterate_user_properties<Key, Value, R, Mapper>(1393		collection_id: CollectionId,1394		token_id: Option<TokenId>,1395		mapper: Mapper,1396	) -> Result<impl Iterator<Item = R>, DispatchError>1397	where1398		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1399		Value: Decode + Default,1400		Mapper: Fn(Key, Value) -> R,1401	{1402		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14031404		let properties = match token_id {1405			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1406			None => <PalletCommon<T>>::collection_properties(collection_id),1407		};14081409		let properties = properties.into_iter().filter_map(move |(key, value)| {1410			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14111412			let key: Key = key.to_vec().try_into().ok()?;1413			let value: Value = value.decode().ok()?;14141415			Some(mapper(key, value))1416		});14171418		Ok(properties)1419	}14201421	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1422		map_unique_err_to_proxy! {1423			match err {1424				CommonError::NoPermission => NoPermission,1425				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1426				CommonError::PublicMintingNotAllowed => NoPermission,1427				CommonError::TokenNotFound => NoAvailableNftId,1428				CommonError::ApprovedValueTooLow => NoPermission,1429				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1430				StructureError::TokenNotFound => NoAvailableNftId,1431				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1432			}1433		}1434	}1435}
after · pallets/proxy-rmrk-core/src/lib.rs
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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52	use super::*;53	use pallet_evm::account;5455	#[pallet::config]56	pub trait Config:57		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58	{59		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60		type WeightInfo: WeightInfo;61	}6263	#[pallet::storage]64	#[pallet::getter(fn collection_index)]65	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667	#[pallet::storage]68	pub type UniqueCollectionId<T: Config> =69		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071	#[pallet::storage]72	pub type RmrkInernalCollectionId<T: Config> =73		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475	#[pallet::pallet]76	#[pallet::generate_store(pub(super) trait Store)]77	pub struct Pallet<T>(_);7879	#[pallet::event]80	#[pallet::generate_deposit(pub(super) fn deposit_event)]81	pub enum Event<T: Config> {82		CollectionCreated {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		CollectionDestroyed {87			issuer: T::AccountId,88			collection_id: RmrkCollectionId,89		},90		IssuerChanged {91			old_issuer: T::AccountId,92			new_issuer: T::AccountId,93			collection_id: RmrkCollectionId,94		},95		CollectionLocked {96			issuer: T::AccountId,97			collection_id: RmrkCollectionId,98		},99		NftMinted {100			owner: T::AccountId,101			collection_id: RmrkCollectionId,102			nft_id: RmrkNftId,103		},104		NFTBurned {105			owner: T::AccountId,106			nft_id: RmrkNftId,107		},108		NFTSent {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113			approval_required: bool,114		},115		NFTAccepted {116			sender: T::AccountId,117			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118			collection_id: RmrkCollectionId,119			nft_id: RmrkNftId,120		},121		NFTRejected {122			sender: T::AccountId,123			collection_id: RmrkCollectionId,124			nft_id: RmrkNftId,125		},126		PropertySet {127			collection_id: RmrkCollectionId,128			maybe_nft_id: Option<RmrkNftId>,129			key: RmrkKeyString,130			value: RmrkValueString,131		},132		ResourceAdded {133			nft_id: RmrkNftId,134			resource_id: RmrkResourceId,135		},136		ResourceRemoval {137			nft_id: RmrkNftId,138			resource_id: RmrkResourceId,139		},140		ResourceAccepted {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		ResourceRemovalAccepted {145			nft_id: RmrkNftId,146			resource_id: RmrkResourceId,147		},148		PrioritySet {149			collection_id: RmrkCollectionId,150			nft_id: RmrkNftId,151		},152	}153154	#[pallet::error]155	pub enum Error<T> {156		/* Unique-specific events */157		CorruptedCollectionType,158		NftTypeEncodeError,159		RmrkPropertyKeyIsTooLong,160		RmrkPropertyValueIsTooLong,161162		/* RMRK compatible events */163		CollectionNotEmpty,164		NoAvailableCollectionId,165		NoAvailableNftId,166		CollectionUnknown,167		NoPermission,168		NonTransferable,169		CollectionFullOrLocked,170		ResourceDoesntExist,171		CannotSendToDescendentOrSelf,172		CannotAcceptNonOwnedNft,173		CannotRejectNonOwnedNft,174		ResourceNotPending,175	}176177	#[pallet::call]178	impl<T: Config> Pallet<T> {179		/// Create a collection180		#[transactional]181		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]182		pub fn create_collection(183			origin: OriginFor<T>,184			metadata: RmrkString,185			max: Option<u32>,186			symbol: RmrkCollectionSymbol,187		) -> DispatchResult {188			let sender = ensure_signed(origin)?;189190			let limits = CollectionLimits {191				owner_can_transfer: Some(false),192				token_limit: max,193				..Default::default()194			};195196			let data = CreateCollectionData {197				limits: Some(limits),198				token_prefix: symbol199					.into_inner()200					.try_into()201					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202				permissions: Some(CollectionPermissions {203					nesting: Some(NestingRule::Owner),204					..Default::default()205				}),206				..Default::default()207			};208209			let unique_collection_id = Self::init_collection(210				T::CrossAccountId::from_sub(sender.clone()),211				data,212				[213					Self::rmrk_property(Metadata, &metadata)?,214					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,215				]216				.into_iter(),217			)?;218			let rmrk_collection_id = <CollectionIndex<T>>::get();219220			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);221			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);222223			<CollectionIndex<T>>::mutate(|n| *n += 1);224225			Self::deposit_event(Event::CollectionCreated {226				issuer: sender,227				collection_id: rmrk_collection_id,228			});229230			Ok(())231		}232233		/// destroy collection234		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]235		#[transactional]236		pub fn destroy_collection(237			origin: OriginFor<T>,238			collection_id: RmrkCollectionId,239		) -> DispatchResult {240			let sender = ensure_signed(origin)?;241			let cross_sender = T::CrossAccountId::from_sub(sender.clone());242243			let collection = Self::get_typed_nft_collection(244				Self::unique_collection_id(collection_id)?,245				misc::CollectionType::Regular,246			)?;247			collection.check_is_external()?;248249			<PalletNft<T>>::destroy_collection(collection, &cross_sender)250				.map_err(Self::map_unique_err_to_proxy)?;251252			Self::deposit_event(Event::CollectionDestroyed {253				issuer: sender,254				collection_id,255			});256257			Ok(())258		}259260		/// Change the issuer of a collection261		///262		/// Parameters:263		/// - `origin`: sender of the transaction264		/// - `collection_id`: collection id of the nft to change issuer of265		/// - `new_issuer`: Collection's new issuer266		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]267		#[transactional]268		pub fn change_collection_issuer(269			origin: OriginFor<T>,270			collection_id: RmrkCollectionId,271			new_issuer: <T::Lookup as StaticLookup>::Source,272		) -> DispatchResult {273			let sender = ensure_signed(origin)?;274275			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;276			collection.check_is_external()?;277278			let new_issuer = T::Lookup::lookup(new_issuer)?;279280			Self::change_collection_owner(281				Self::unique_collection_id(collection_id)?,282				misc::CollectionType::Regular,283				sender.clone(),284				new_issuer.clone(),285			)?;286287			Self::deposit_event(Event::IssuerChanged {288				old_issuer: sender,289				new_issuer,290				collection_id,291			});292293			Ok(())294		}295296		/// lock collection297		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]298		#[transactional]299		pub fn lock_collection(300			origin: OriginFor<T>,301			collection_id: RmrkCollectionId,302		) -> DispatchResult {303			let sender = ensure_signed(origin)?;304			let cross_sender = T::CrossAccountId::from_sub(sender.clone());305306			let collection = Self::get_typed_nft_collection(307				Self::unique_collection_id(collection_id)?,308				misc::CollectionType::Regular,309			)?;310			collection.check_is_external()?;311312			Self::check_collection_owner(&collection, &cross_sender)?;313314			let token_count = collection.total_supply();315316			let mut collection = collection.into_inner();317			collection.limits.token_limit = Some(token_count);318			collection.save()?;319320			Self::deposit_event(Event::CollectionLocked {321				issuer: sender,322				collection_id,323			});324325			Ok(())326		}327328		/// Mints an NFT in the specified collection329		/// Sets metadata and the royalty attribute330		///331		/// Parameters:332		/// - `collection_id`: The class of the asset to be minted.333		/// - `nft_id`: The nft value of the asset to be minted.334		/// - `recipient`: Receiver of the royalty335		/// - `royalty`: Permillage reward from each trade for the Recipient336		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash337		/// - `transferable`: Ability to transfer this NFT338		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]339		#[transactional]340		pub fn mint_nft(341			origin: OriginFor<T>,342			owner: T::AccountId,343			collection_id: RmrkCollectionId,344			recipient: Option<T::AccountId>,345			royalty_amount: Option<Permill>,346			metadata: RmrkString,347			transferable: bool,348		) -> DispatchResult {349			let sender = ensure_signed(origin)?;350			let sender = T::CrossAccountId::from_sub(sender);351			let cross_owner = T::CrossAccountId::from_sub(owner.clone());352353			let collection = Self::get_typed_nft_collection(354				Self::unique_collection_id(collection_id)?,355				misc::CollectionType::Regular,356			)?;357			collection.check_is_external()?;358359			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {360				recipient: recipient.unwrap_or_else(|| owner.clone()),361				amount,362			});363364			let nft_id = Self::create_nft(365				&sender,366				&cross_owner,367				&collection,368				[369					Self::rmrk_property(TokenType, &NftType::Regular)?,370					Self::rmrk_property(Transferable, &transferable)?,371					Self::rmrk_property(PendingNftAccept, &false)?,372					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,373					Self::rmrk_property(Metadata, &metadata)?,374					Self::rmrk_property(Equipped, &false)?,375					Self::rmrk_property(376						ResourceCollection,377						&Self::init_collection(378							sender.clone(),379							CreateCollectionData {380								..Default::default()381							},382							[Self::rmrk_property(383								CollectionType,384								&misc::CollectionType::Resource,385							)?]386							.into_iter(),387						)?,388					)?, // todo possibly add limits to the collection if rmrk warrants them389					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,390				]391				.into_iter(),392			)393			.map_err(|err| match err {394				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),395				err => Self::map_unique_err_to_proxy(err),396			})?;397398			Self::deposit_event(Event::NftMinted {399				owner,400				collection_id,401				nft_id: nft_id.0,402			});403404			Ok(())405		}406407		/// burn nft408		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]409		#[transactional]410		pub fn burn_nft(411			origin: OriginFor<T>,412			collection_id: RmrkCollectionId,413			nft_id: RmrkNftId,414		) -> DispatchResult {415			let sender = ensure_signed(origin)?;416			let cross_sender = T::CrossAccountId::from_sub(sender.clone());417418			let collection = Self::get_typed_nft_collection(419				Self::unique_collection_id(collection_id)?,420				misc::CollectionType::Regular,421			)?;422			collection.check_is_external()?;423424			Self::destroy_nft(425				cross_sender,426				Self::unique_collection_id(collection_id)?,427				nft_id.into(),428			)429			.map_err(Self::map_unique_err_to_proxy)?;430431			Self::deposit_event(Event::NFTBurned {432				owner: sender,433				nft_id,434			});435436			Ok(())437		}438439		/// Transfers a NFT from an Account or NFT A to another Account or NFT B440		///441		/// Parameters:442		/// - `origin`: sender of the transaction443		/// - `rmrk_collection_id`: collection id of the nft to be transferred444		/// - `rmrk_nft_id`: nft id of the nft to be transferred445		/// - `new_owner`: new owner of the nft which can be either an account or a NFT446		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]447		#[transactional]448		pub fn send(449			origin: OriginFor<T>,450			rmrk_collection_id: RmrkCollectionId,451			rmrk_nft_id: RmrkNftId,452			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,453		) -> DispatchResult {454			let sender = ensure_signed(origin.clone())?;455			let cross_sender = T::CrossAccountId::from_sub(sender.clone());456457			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;458			let nft_id = rmrk_nft_id.into();459460			let collection =461				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;462			collection.check_is_external()?;463464			let token_data =465				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;466467			let from = token_data.owner;468469			ensure!(470				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,471				<Error<T>>::NonTransferable472			);473474			ensure!(475				!Self::get_nft_property_decoded(476					collection_id,477					nft_id,478					RmrkProperty::PendingNftAccept479				)?,480				<Error<T>>::NoPermission481			);482483			let target_owner;484			let approval_required;485486			match new_owner {487				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {488					target_owner = T::CrossAccountId::from_sub(account_id.clone());489					approval_required = false;490				}491				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(492					target_collection_id,493					target_nft_id,494				) => {495					let target_collection_id = Self::unique_collection_id(target_collection_id)?;496497					let target_nft_budget = budget::Value::new(NESTING_BUDGET);498499					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(500						target_collection_id,501						target_nft_id.into(),502						Some((collection_id, nft_id)),503						&target_nft_budget,504					)505					.map_err(Self::map_unique_err_to_proxy)?;506507					approval_required = cross_sender != target_nft_owner;508509					if approval_required {510						target_owner = target_nft_owner;511512						<PalletNft<T>>::set_scoped_token_property(513							collection.id,514							nft_id,515							PropertyScope::Rmrk,516							Self::rmrk_property(PendingNftAccept, &approval_required)?,517						)?;518					} else {519						target_owner = T::CrossTokenAddressMapping::token_to_address(520							target_collection_id,521							target_nft_id.into(),522						);523					}524				}525			}526527			let src_nft_budget = budget::Value::new(NESTING_BUDGET);528529			<PalletNft<T>>::transfer_from(530				&collection,531				&cross_sender,532				&from,533				&target_owner,534				nft_id,535				&src_nft_budget,536			)537			.map_err(Self::map_unique_err_to_proxy)?;538539			Self::deposit_event(Event::NFTSent {540				sender,541				recipient: new_owner,542				collection_id: rmrk_collection_id,543				nft_id: rmrk_nft_id,544				approval_required,545			});546547			Ok(())548		}549550		/// Accepts an NFT sent from another account to self or owned NFT551		///552		/// Parameters:553		/// - `origin`: sender of the transaction554		/// - `rmrk_collection_id`: collection id of the nft to be accepted555		/// - `rmrk_nft_id`: nft id of the nft to be accepted556		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was557		///   sent to558		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]559		#[transactional]560		pub fn accept_nft(561			origin: OriginFor<T>,562			rmrk_collection_id: RmrkCollectionId,563			rmrk_nft_id: RmrkNftId,564			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,565		) -> DispatchResult {566			let sender = ensure_signed(origin.clone())?;567			let cross_sender = T::CrossAccountId::from_sub(sender.clone());568569			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;570			let nft_id = rmrk_nft_id.into();571572			let collection =573				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;574			collection.check_is_external()?;575576			let new_cross_owner = match new_owner {577				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {578					T::CrossAccountId::from_sub(account_id.clone())579				}580				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(581					target_collection_id,582					target_nft_id,583				) => {584					let target_collection_id = Self::unique_collection_id(target_collection_id)?;585586					T::CrossTokenAddressMapping::token_to_address(587						target_collection_id,588						TokenId(target_nft_id),589					)590				}591			};592593			let budget = budget::Value::new(NESTING_BUDGET);594595			<PalletNft<T>>::transfer(596				&collection,597				&cross_sender,598				&new_cross_owner,599				nft_id,600				&budget,601			)602			.map_err(|err| {603				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {604					<Error<T>>::CannotAcceptNonOwnedNft.into()605				} else {606					Self::map_unique_err_to_proxy(err)607				}608			})?;609610			<PalletNft<T>>::set_scoped_token_property(611				collection.id,612				nft_id,613				PropertyScope::Rmrk,614				Self::rmrk_property(PendingNftAccept, &false)?,615			)?;616617			Self::deposit_event(Event::NFTAccepted {618				sender,619				recipient: new_owner,620				collection_id: rmrk_collection_id,621				nft_id: rmrk_nft_id,622			});623624			Ok(())625		}626627		/// Rejects an NFT sent from another account to self or owned NFT628		///629		/// Parameters:630		/// - `origin`: sender of the transaction631		/// - `rmrk_collection_id`: collection id of the nft to be accepted632		/// - `rmrk_nft_id`: nft id of the nft to be accepted633		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]634		#[transactional]635		pub fn reject_nft(636			origin: OriginFor<T>,637			rmrk_collection_id: RmrkCollectionId,638			rmrk_nft_id: RmrkNftId,639		) -> DispatchResult {640			let sender = ensure_signed(origin)?;641			let cross_sender = T::CrossAccountId::from_sub(sender.clone());642643			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;644			let nft_id = rmrk_nft_id.into();645646			let collection =647				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;648			collection.check_is_external()?;649650			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {651				if err == <CommonError<T>>::NoPermission.into()652					|| err == <CommonError<T>>::ApprovedValueTooLow.into()653				{654					<Error<T>>::CannotRejectNonOwnedNft.into()655				} else {656					Self::map_unique_err_to_proxy(err)657				}658			})?;659660			Self::deposit_event(Event::NFTRejected {661				sender,662				collection_id: rmrk_collection_id,663				nft_id: rmrk_nft_id,664			});665666			Ok(())667		}668669		/// accept the addition of a new resource to an existing NFT670		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]671		#[transactional]672		pub fn accept_resource(673			origin: OriginFor<T>,674			rmrk_collection_id: RmrkCollectionId,675			rmrk_nft_id: RmrkNftId,676			rmrk_resource_id: RmrkResourceId,677		) -> DispatchResult {678			let sender = ensure_signed(origin)?;679			let cross_sender = T::CrossAccountId::from_sub(sender);680681			let collection_id = Self::unique_collection_id(rmrk_collection_id)682				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;683			let collection =684				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;685			collection.check_is_external()?;686687			let nft_id = rmrk_nft_id.into();688			let resource_id = rmrk_resource_id.into();689690			let budget = budget::Value::new(NESTING_BUDGET);691692			let nft_owner =693				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)694					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;695696			let resource_collection_id: CollectionId =697				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)698					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;699700			let is_pending: bool = Self::get_nft_property_decoded(701				resource_collection_id,702				resource_id,703				PendingResourceAccept,704			)705			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707			ensure!(is_pending, <Error<T>>::ResourceNotPending);708709			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711			<PalletNft<T>>::set_scoped_token_property(712				resource_collection_id,713				rmrk_resource_id.into(),714				PropertyScope::Rmrk,715				Self::rmrk_property(PendingResourceAccept, &false)?,716			)?;717718			Self::deposit_event(Event::<T>::ResourceAccepted {719				nft_id: rmrk_nft_id,720				resource_id: rmrk_resource_id,721			});722723			Ok(())724		}725726		/// accept the removal of a resource of an existing NFT727		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]728		#[transactional]729		pub fn accept_resource_removal(730			origin: OriginFor<T>,731			rmrk_collection_id: RmrkCollectionId,732			rmrk_nft_id: RmrkNftId,733			rmrk_resource_id: RmrkResourceId,734		) -> DispatchResult {735			let sender = ensure_signed(origin)?;736			let cross_sender = T::CrossAccountId::from_sub(sender);737738			let collection_id = Self::unique_collection_id(rmrk_collection_id)739				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;740			let collection =741				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742			collection.check_is_external()?;743744			let nft_id = rmrk_nft_id.into();745			let resource_id = rmrk_resource_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755			let resource_collection_id: CollectionId =756				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759			let is_pending: bool = Self::get_nft_property_decoded(760				resource_collection_id,761				resource_id,762				PendingResourceRemoval,763			)764			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;765766			ensure!(is_pending, <Error<T>>::ResourceNotPending);767768			let resource_collection = Self::get_typed_nft_collection(769				resource_collection_id,770				misc::CollectionType::Resource,771			)?;772773			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())774				.map_err(Self::map_unique_err_to_proxy)?;775776			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {777				nft_id: rmrk_nft_id,778				resource_id: rmrk_resource_id,779			});780781			Ok(())782		}783784		/// set a custom value on an NFT785		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]786		#[transactional]787		pub fn set_property(788			origin: OriginFor<T>,789			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,790			maybe_nft_id: Option<RmrkNftId>,791			key: RmrkKeyString,792			value: RmrkValueString,793		) -> DispatchResult {794			let sender = ensure_signed(origin)?;795			let sender = T::CrossAccountId::from_sub(sender);796797			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;798			let collection =799				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;800			collection.check_is_external()?;801802			let budget = budget::Value::new(NESTING_BUDGET);803804			match maybe_nft_id {805				Some(nft_id) => {806					let token_id: TokenId = nft_id.into();807808					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;809					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;810811					<PalletNft<T>>::set_scoped_token_property(812						collection_id,813						token_id,814						PropertyScope::Rmrk,815						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,816					)?;817				}818				None => {819					let collection = Self::get_typed_nft_collection(820						collection_id,821						misc::CollectionType::Regular,822					)?;823824					Self::check_collection_owner(&collection, &sender)?;825826					<PalletCommon<T>>::set_scoped_collection_property(827						collection_id,828						PropertyScope::Rmrk,829						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,830					)?;831				}832			}833834			Self::deposit_event(Event::PropertySet {835				collection_id: rmrk_collection_id,836				maybe_nft_id,837				key,838				value,839			});840841			Ok(())842		}843844		/// set a different order of resource priority845		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]846		#[transactional]847		pub fn set_priority(848			origin: OriginFor<T>,849			rmrk_collection_id: RmrkCollectionId,850			rmrk_nft_id: RmrkNftId,851			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,852		) -> DispatchResult {853			let sender = ensure_signed(origin)?;854			let sender = T::CrossAccountId::from_sub(sender);855856			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;857			let nft_id = rmrk_nft_id.into();858859			let collection =860				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;861			collection.check_is_external()?;862863			let budget = budget::Value::new(NESTING_BUDGET);864865			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;866			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;867868			<PalletNft<T>>::set_scoped_token_property(869				collection_id,870				nft_id,871				PropertyScope::Rmrk,872				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,873			)?;874875			Self::deposit_event(Event::<T>::PrioritySet {876				collection_id: rmrk_collection_id,877				nft_id: rmrk_nft_id,878			});879880			Ok(())881		}882883		/// Create basic resource884		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]885		#[transactional]886		pub fn add_basic_resource(887			origin: OriginFor<T>,888			rmrk_collection_id: RmrkCollectionId,889			nft_id: RmrkNftId,890			resource: RmrkBasicResource,891		) -> DispatchResult {892			let sender = ensure_signed(origin.clone())?;893894			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;895			let collection =896				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;897			collection.check_is_external()?;898899			let resource_id = Self::resource_add(900				sender,901				collection_id,902				nft_id.into(),903				[904					Self::rmrk_property(TokenType, &NftType::Resource)?,905					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,906					Self::rmrk_property(Src, &resource.src)?,907					Self::rmrk_property(Metadata, &resource.metadata)?,908					Self::rmrk_property(License, &resource.license)?,909					Self::rmrk_property(Thumb, &resource.thumb)?,910				]911				.into_iter(),912			)?;913914			Self::deposit_event(Event::ResourceAdded {915				nft_id,916				resource_id,917			});918			Ok(())919		}920921		/// Create composable resource922		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]923		#[transactional]924		pub fn add_composable_resource(925			origin: OriginFor<T>,926			rmrk_collection_id: RmrkCollectionId,927			nft_id: RmrkNftId,928			_resource_id: RmrkBoundedResource,929			resource: RmrkComposableResource,930		) -> DispatchResult {931			let sender = ensure_signed(origin.clone())?;932933			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;934			let collection =935				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;936			collection.check_is_external()?;937938			let resource_id = Self::resource_add(939				sender,940				collection_id,941				nft_id.into(),942				[943					Self::rmrk_property(TokenType, &NftType::Resource)?,944					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,945					Self::rmrk_property(Parts, &resource.parts)?,946					Self::rmrk_property(Base, &resource.base)?,947					Self::rmrk_property(Src, &resource.src)?,948					Self::rmrk_property(Metadata, &resource.metadata)?,949					Self::rmrk_property(License, &resource.license)?,950					Self::rmrk_property(Thumb, &resource.thumb)?,951				]952				.into_iter(),953			)?;954955			Self::deposit_event(Event::ResourceAdded {956				nft_id,957				resource_id,958			});959			Ok(())960		}961962		/// Create slot resource963		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]964		#[transactional]965		pub fn add_slot_resource(966			origin: OriginFor<T>,967			rmrk_collection_id: RmrkCollectionId,968			nft_id: RmrkNftId,969			resource: RmrkSlotResource,970		) -> DispatchResult {971			let sender = ensure_signed(origin.clone())?;972973			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;974			let collection =975				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;976			collection.check_is_external()?;977978			let resource_id = Self::resource_add(979				sender,980				collection_id,981				nft_id.into(),982				[983					Self::rmrk_property(TokenType, &NftType::Resource)?,984					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,985					Self::rmrk_property(Base, &resource.base)?,986					Self::rmrk_property(Src, &resource.src)?,987					Self::rmrk_property(Metadata, &resource.metadata)?,988					Self::rmrk_property(Slot, &resource.slot)?,989					Self::rmrk_property(License, &resource.license)?,990					Self::rmrk_property(Thumb, &resource.thumb)?,991				]992				.into_iter(),993			)?;994995			Self::deposit_event(Event::ResourceAdded {996				nft_id,997				resource_id,998			});999			Ok(())1000		}10011002		/// remove resource1003		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1004		#[transactional]1005		pub fn remove_resource(1006			origin: OriginFor<T>,1007			rmrk_collection_id: RmrkCollectionId,1008			nft_id: RmrkNftId,1009			resource_id: RmrkResourceId,1010		) -> DispatchResult {1011			let sender = ensure_signed(origin.clone())?;10121013			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1014			let collection =1015				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1016			collection.check_is_external()?;10171018			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10191020			Self::deposit_event(Event::ResourceRemoval {1021				nft_id,1022				resource_id,1023			});1024			Ok(())1025		}1026	}1027}10281029impl<T: Config> Pallet<T> {1030	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1031		let key = rmrk_key.to_key::<T>()?;10321033		let scoped_key = PropertyScope::Rmrk1034			.apply(key)1035			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10361037		Ok(scoped_key)1038	}10391040	// todo think about renaming these1041	pub fn rmrk_property<E: Encode>(1042		rmrk_key: RmrkProperty,1043		value: &E,1044	) -> Result<Property, DispatchError> {1045		let key = rmrk_key.to_key::<T>()?;10461047		let value = value1048			.encode()1049			.try_into()1050			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10511052		let property = Property { key, value };10531054		Ok(property)1055	}10561057	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1058		vec.decode()1059			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1060	}10611062	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1063	where1064		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1065	{1066		vec.rebind()1067			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1068	}10691070	fn init_collection(1071		sender: T::CrossAccountId,1072		data: CreateCollectionData<T::AccountId>,1073		properties: impl Iterator<Item = Property>,1074	) -> Result<CollectionId, DispatchError> {1075		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10761077		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1078			return Err(<Error<T>>::NoAvailableCollectionId.into());1079		}10801081		<PalletCommon<T>>::set_scoped_collection_properties(1082			collection_id?,1083			PropertyScope::Rmrk,1084			properties,1085		)?;10861087		collection_id1088	}10891090	pub fn create_nft(1091		sender: &T::CrossAccountId,1092		owner: &T::CrossAccountId,1093		collection: &NonfungibleHandle<T>,1094		properties: impl Iterator<Item = Property>,1095	) -> Result<TokenId, DispatchError> {1096		let data = CreateNftExData {1097			properties: BoundedVec::default(),1098			owner: owner.clone(),1099		};11001101		let budget = budget::Value::new(NESTING_BUDGET);11021103		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11041105		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11061107		<PalletNft<T>>::set_scoped_token_properties(1108			collection.id,1109			nft_id,1110			PropertyScope::Rmrk,1111			properties,1112		)?;11131114		Ok(nft_id)1115	}11161117	fn destroy_nft(1118		sender: T::CrossAccountId,1119		collection_id: CollectionId,1120		token_id: TokenId,1121	) -> DispatchResult {1122		let collection =1123			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11241125		let token_data =1126			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11271128		let from = token_data.owner;11291130		let budget = budget::Value::new(NESTING_BUDGET);11311132		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1133	}11341135	fn resource_add(1136		sender: T::AccountId,1137		collection_id: CollectionId,1138		token_id: TokenId,1139		resource_properties: impl Iterator<Item = Property>,1140	) -> Result<RmrkResourceId, DispatchError> {1141		let collection =1142			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1143		ensure!(collection.owner == sender, Error::<T>::NoPermission);11441145		let sender = T::CrossAccountId::from_sub(sender);1146		let budget = budget::Value::new(NESTING_BUDGET);11471148		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1149			.map_err(Self::map_unique_err_to_proxy)?;11501151		let pending = sender != nft_owner;11521153		let resource_collection_id: CollectionId =1154			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1155		let resource_collection =1156			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11571158		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11591160		let resource_id = Self::create_nft(1161			&sender,1162			&nft_owner,1163			&resource_collection,1164			resource_properties.chain(1165				[1166					Self::rmrk_property(PendingResourceAccept, &pending)?,1167					Self::rmrk_property(PendingResourceRemoval, &false)?,1168				]1169				.into_iter(),1170			),1171		)1172		.map_err(|err| match err {1173			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1174			err => Self::map_unique_err_to_proxy(err),1175		})?;11761177		Ok(resource_id.0)1178	}11791180	fn resource_remove(1181		sender: T::AccountId,1182		collection_id: CollectionId,1183		nft_id: TokenId,1184		resource_id: TokenId,1185	) -> DispatchResult {1186		let collection =1187			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1188		ensure!(collection.owner == sender, Error::<T>::NoPermission);11891190		let resource_collection_id: CollectionId =1191			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1192		let resource_collection =1193			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1194		ensure!(1195			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1196			Error::<T>::ResourceDoesntExist1197		);11981199		let budget = up_data_structs::budget::Value::new(10);1200		let topmost_owner =1201			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12021203		let sender = T::CrossAccountId::from_sub(sender);1204		if topmost_owner == sender {1205			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1206				.map_err(Self::map_unique_err_to_proxy)?;1207		} else {1208			<PalletNft<T>>::set_scoped_token_property(1209				resource_collection_id,1210				resource_id,1211				PropertyScope::Rmrk,1212				Self::rmrk_property(PendingResourceRemoval, &true)?,1213			)?;1214		}12151216		Ok(())1217	}12181219	fn change_collection_owner(1220		collection_id: CollectionId,1221		collection_type: misc::CollectionType,1222		sender: T::AccountId,1223		new_owner: T::AccountId,1224	) -> DispatchResult {1225		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1226		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12271228		let mut collection = collection.into_inner();12291230		collection.owner = new_owner;1231		collection.save()1232	}12331234	fn check_collection_owner(1235		collection: &NonfungibleHandle<T>,1236		account: &T::CrossAccountId,1237	) -> DispatchResult {1238		collection1239			.check_is_owner(account)1240			.map_err(Self::map_unique_err_to_proxy)1241	}12421243	pub fn last_collection_idx() -> RmrkCollectionId {1244		<CollectionIndex<T>>::get()1245	}12461247	pub fn unique_collection_id(1248		rmrk_collection_id: RmrkCollectionId,1249	) -> Result<CollectionId, DispatchError> {1250		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1251			.map_err(|_| <Error<T>>::CollectionUnknown.into())1252	}12531254	pub fn rmrk_collection_id(1255		unique_collection_id: CollectionId,1256	) -> Result<RmrkCollectionId, DispatchError> {1257		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1258			.map_err(|_| <Error<T>>::CollectionUnknown.into())1259	}12601261	pub fn get_nft_collection(1262		collection_id: CollectionId,1263	) -> Result<NonfungibleHandle<T>, DispatchError> {1264		let collection = <CollectionHandle<T>>::try_get(collection_id)1265			.map_err(|_| <Error<T>>::CollectionUnknown)?;12661267		match collection.mode {1268			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1269			_ => Err(<Error<T>>::CollectionUnknown.into()),1270		}1271	}12721273	pub fn collection_exists(collection_id: CollectionId) -> bool {1274		<CollectionHandle<T>>::try_get(collection_id).is_ok()1275	}12761277	pub fn get_collection_property(1278		collection_id: CollectionId,1279		key: RmrkProperty,1280	) -> Result<PropertyValue, DispatchError> {1281		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1282			.get(&Self::rmrk_property_key(key)?)1283			.ok_or(<Error<T>>::CollectionUnknown)?1284			.clone();12851286		Ok(collection_property)1287	}12881289	pub fn get_collection_property_decoded<V: Decode>(1290		collection_id: CollectionId,1291		key: RmrkProperty,1292	) -> Result<V, DispatchError> {1293		Self::decode_property(Self::get_collection_property(collection_id, key)?)1294	}12951296	pub fn get_collection_type(1297		collection_id: CollectionId,1298	) -> Result<misc::CollectionType, DispatchError> {1299		Self::get_collection_property_decoded(collection_id, CollectionType)1300			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1301	}13021303	pub fn ensure_collection_type(1304		collection_id: CollectionId,1305		collection_type: misc::CollectionType,1306	) -> DispatchResult {1307		let actual_type = Self::get_collection_type(collection_id)?;1308		ensure!(1309			actual_type == collection_type,1310			<CommonError<T>>::NoPermission1311		);13121313		Ok(())1314	}13151316	pub fn get_typed_nft_collection(1317		collection_id: CollectionId,1318		collection_type: misc::CollectionType,1319	) -> Result<NonfungibleHandle<T>, DispatchError> {1320		Self::ensure_collection_type(collection_id, collection_type)?;13211322		Self::get_nft_collection(collection_id)1323	}13241325	pub fn get_typed_nft_collection_mapped(1326		rmrk_collection_id: RmrkCollectionId,1327		collection_type: misc::CollectionType,1328	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1329		let unique_collection_id = match collection_type {1330			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1331			_ => rmrk_collection_id.into(),1332		};13331334		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13351336		Ok((collection, unique_collection_id))1337	}13381339	pub fn get_nft_property(1340		collection_id: CollectionId,1341		nft_id: TokenId,1342		key: RmrkProperty,1343	) -> Result<PropertyValue, DispatchError> {1344		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1345			.get(&Self::rmrk_property_key(key)?)1346			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1347			.clone();13481349		Ok(nft_property)1350	}13511352	pub fn get_nft_property_decoded<V: Decode>(1353		collection_id: CollectionId,1354		nft_id: TokenId,1355		key: RmrkProperty,1356	) -> Result<V, DispatchError> {1357		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1358	}13591360	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1361		<TokenData<T>>::contains_key((collection_id, nft_id))1362	}13631364	pub fn get_nft_type(1365		collection_id: CollectionId,1366		token_id: TokenId,1367	) -> Result<NftType, DispatchError> {1368		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1369			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1370	}13711372	pub fn ensure_nft_type(1373		collection_id: CollectionId,1374		token_id: TokenId,1375		nft_type: NftType,1376	) -> DispatchResult {1377		let actual_type = Self::get_nft_type(collection_id, token_id)?;1378		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13791380		Ok(())1381	}13821383	pub fn ensure_nft_owner(1384		collection_id: CollectionId,1385		token_id: TokenId,1386		possible_owner: &T::CrossAccountId,1387		nesting_budget: &dyn budget::Budget,1388	) -> DispatchResult {1389		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1390			possible_owner.clone(),1391			collection_id,1392			token_id,1393			None,1394			nesting_budget,1395		)1396		.map_err(Self::map_unique_err_to_proxy)?;13971398		ensure!(is_owned, <Error<T>>::NoPermission);13991400		Ok(())1401	}14021403	pub fn filter_user_properties<Key, Value, R, Mapper>(1404		collection_id: CollectionId,1405		token_id: Option<TokenId>,1406		filter_keys: Option<Vec<RmrkPropertyKey>>,1407		mapper: Mapper,1408	) -> Result<Vec<R>, DispatchError>1409	where1410		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1411		Value: Decode + Default,1412		Mapper: Fn(Key, Value) -> R,1413	{1414		filter_keys1415			.map(|keys| {1416				let properties = keys1417					.into_iter()1418					.filter_map(|key| {1419						let key: Key = key.try_into().ok()?;14201421						let value = match token_id {1422							Some(token_id) => Self::get_nft_property_decoded(1423								collection_id,1424								token_id,1425								UserProperty(key.as_ref()),1426							),1427							None => Self::get_collection_property_decoded(1428								collection_id,1429								UserProperty(key.as_ref()),1430							),1431						}1432						.ok()?;14331434						Some(mapper(key, value))1435					})1436					.collect();14371438				Ok(properties)1439			})1440			.unwrap_or_else(|| {1441				let properties =1442					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14431444				Ok(properties)1445			})1446	}14471448	pub fn iterate_user_properties<Key, Value, R, Mapper>(1449		collection_id: CollectionId,1450		token_id: Option<TokenId>,1451		mapper: Mapper,1452	) -> Result<impl Iterator<Item = R>, DispatchError>1453	where1454		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1455		Value: Decode + Default,1456		Mapper: Fn(Key, Value) -> R,1457	{1458		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14591460		let properties = match token_id {1461			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1462			None => <PalletCommon<T>>::collection_properties(collection_id),1463		};14641465		let properties = properties.into_iter().filter_map(move |(key, value)| {1466			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14671468			let key: Key = key.to_vec().try_into().ok()?;1469			let value: Value = value.decode().ok()?;14701471			Some(mapper(key, value))1472		});14731474		Ok(properties)1475	}14761477	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1478		map_unique_err_to_proxy! {1479			match err {1480				CommonError::NoPermission => NoPermission,1481				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1482				CommonError::PublicMintingNotAllowed => NoPermission,1483				CommonError::TokenNotFound => NoAvailableNftId,1484				CommonError::ApprovedValueTooLow => NoPermission,1485				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1486				StructureError::TokenNotFound => NoAvailableNftId,1487				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1488			}1489		}1490	}1491}
modifiedpallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use super::*;
 use codec::{Encode, Decode, Error};
 
modifiedpallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use super::*;
 use core::convert::AsRef;
 
addedpallets/proxy-rmrk-core/src/weights.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/proxy-rmrk-core/src/weights.rs
@@ -0,0 +1,74 @@
+// Template adopted from https://github.com/paritytech/substrate/blob/master/.maintain/frame-weight-template.hbs
+
+//! Autogenerated weights for pallet_proxy_rmrk_core
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-06-09, STEPS: `50`, REPEAT: 200, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
+
+// Executed Command:
+// target/release/unique-collator
+// benchmark
+// pallet
+// --pallet
+// pallet-proxy-rmrk-core
+// --wasm-execution
+// compiled
+// --extrinsic
+// *
+// --template
+// .maintain/frame-weight-template.hbs
+// --steps=50
+// --repeat=200
+// --heap-pages=4096
+// --output=./pallets/proxy-rmrk-core/src/weights.rs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for pallet_proxy_rmrk_core.
+pub trait WeightInfo {
+	fn create_collection() -> Weight;
+}
+
+/// Weights for pallet_proxy_rmrk_core using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	// Storage: Common CreatedCollectionCount (r:1 w:1)
+	// Storage: Common DestroyedCollectionCount (r:1 w:0)
+	// Storage: System Account (r:2 w:2)
+	// Storage: RmrkCore CollectionIndex (r:1 w:1)
+	// Storage: Common CollectionPropertyPermissions (r:0 w:1)
+	// Storage: Common CollectionProperties (r:0 w:1)
+	// Storage: Common CollectionById (r:0 w:1)
+	// Storage: RmrkCore UniqueCollectionId (r:0 w:1)
+	// Storage: RmrkCore RmrkInernalCollectionId (r:0 w:1)
+	fn create_collection() -> Weight {
+		(76_647_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(9 as Weight))
+	}
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+	// Storage: Common CreatedCollectionCount (r:1 w:1)
+	// Storage: Common DestroyedCollectionCount (r:1 w:0)
+	// Storage: System Account (r:2 w:2)
+	// Storage: RmrkCore CollectionIndex (r:1 w:1)
+	// Storage: Common CollectionPropertyPermissions (r:0 w:1)
+	// Storage: Common CollectionProperties (r:0 w:1)
+	// Storage: Common CollectionById (r:0 w:1)
+	// Storage: RmrkCore UniqueCollectionId (r:0 w:1)
+	// Storage: RmrkCore RmrkInernalCollectionId (r:0 w:1)
+	fn create_collection() -> Weight {
+		(76_647_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(9 as Weight))
+	}
+}
modifiedpallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -74,6 +74,15 @@
 
 	#[pallet::call]
 	impl<T: Config> Pallet<T> {
+		/// Creates a new Base.
+		/// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
+		///
+		/// Parameters:
+		/// - origin: Caller, will be assigned as the issuer of the Base
+		/// - base_type: media type, e.g. "svg"
+		/// - symbol: arbitrary client-chosen symbol
+		/// - parts: array of Fixed and Slot parts composing the base, confined in length by
+		///   RmrkPartsLimit
 		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
 		#[transactional]
 		pub fn create_base(
@@ -137,6 +146,19 @@
 			Ok(())
 		}
 
+		/// Adds a Theme to a Base.
+		/// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)
+		/// Themes are stored in the Themes storage
+		/// A Theme named "default" is required prior to adding other Themes.
+		///
+		/// Parameters:
+		/// - origin: The caller of the function, must be issuer of the base
+		/// - base_id: The Base containing the Theme to be updated
+		/// - theme: The Theme to add to the Base.  A Theme has a name and properties, which are an
+		///   array of [key, value, inherit].
+		///   - key: arbitrary BoundedString, defined by client
+		///   - value: arbitrary BoundedString, defined by client
+		///   - inherit: optional bool
 		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
 		#[transactional]
 		pub fn theme_add(
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -149,7 +149,7 @@
 		})
 	}
 
-	pub fn get_checked_indirect_owner(
+	pub fn get_checked_topmost_owner(
 		collection: CollectionId,
 		token: TokenId,
 		for_nest: Option<(CollectionId, TokenId)>,
@@ -203,7 +203,7 @@
 			None => user,
 		};
 
-		Self::get_checked_indirect_owner(collection, token, for_nest, budget)
+		Self::get_checked_topmost_owner(collection, token, for_nest, budget)
 			.map(|indirect_owner| indirect_owner == target_parent)
 	}
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -44,7 +44,7 @@
 };
 use pallet_evm::account::CrossAccountId;
 use pallet_common::{
-	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,
+	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,
 	dispatch::CollectionDispatch,
 };
 pub mod eth;
@@ -581,7 +581,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))
+			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))
 		}
 
 		/// This method creates multiple items in a collection created with CreateCollection method.
@@ -609,7 +609,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))
+			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))
 		}
 
 		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]
@@ -623,7 +623,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))
+			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))
 		}
 
 		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]
@@ -637,7 +637,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))
+			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))
 		}
 
 		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]
@@ -652,7 +652,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))
+			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))
 		}
 
 		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]
@@ -667,7 +667,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))
+			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))
 		}
 
 		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]
@@ -681,7 +681,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))
+			dispatch_tx::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))
 		}
 
 		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]
@@ -690,7 +690,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))
+			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))
 		}
 
 		// TODO! transaction weight
@@ -738,7 +738,7 @@
 		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;
+			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;
 			if value == 1 {
 				<NftTransferBasket<T>>::remove(collection_id, item_id);
 				<NftApproveBasket<T>>::remove(collection_id, item_id);
@@ -771,7 +771,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))
+			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))
 		}
 
 		/// Change ownership of the token.
@@ -803,7 +803,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))
+			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))
 		}
 
 		/// Set, change, or remove approved address to transfer the ownership of the NFT.
@@ -826,7 +826,7 @@
 		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
+			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
 		}
 
 		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
@@ -854,7 +854,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
+			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
 		}
 
 		#[weight = <SelfWeightOf<T>>::set_collection_limits()]
modifiedprimitives/rmrk-rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rmrk-rpc/src/lib.rs
+++ b/primitives/rmrk-rpc/src/lib.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use sp_api::{Encode, Decode};
modifiedprimitives/rmrk-traits/src/base.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/base.rs
+++ b/primitives/rmrk-traits/src/base.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
modifiedprimitives/rmrk-traits/src/collection.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/collection.rs
+++ b/primitives/rmrk-traits/src/collection.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
modifiedprimitives/rmrk-traits/src/lib.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/lib.rs
+++ b/primitives/rmrk-traits/src/lib.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod base;
modifiedprimitives/rmrk-traits/src/nft.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/nft.rs
+++ b/primitives/rmrk-traits/src/nft.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
modifiedprimitives/rmrk-traits/src/part.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/part.rs
+++ b/primitives/rmrk-traits/src/part.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
modifiedprimitives/rmrk-traits/src/property.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/property.rs
+++ b/primitives/rmrk-traits/src/property.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode};
 use scale_info::TypeInfo;
 
modifiedprimitives/rmrk-traits/src/resource.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/resource.rs
+++ b/primitives/rmrk-traits/src/resource.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
modifiedprimitives/rmrk-traits/src/serialize.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/serialize.rs
+++ b/primitives/rmrk-traits/src/serialize.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use core::convert::AsRef;
 use serde::ser::{self, Serialize};
 
modifiedprimitives/rmrk-traits/src/theme.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/theme.rs
+++ b/primitives/rmrk-traits/src/theme.rs
@@ -1,3 +1,7 @@
+// Copyright (C) 2021-2022 RMRK
+// This file is part of rmrk-substrate.
+// License: Apache 2.0 modified by RMRK, see https://github.com/rmrk-team/rmrk-substrate/blob/main/LICENSE
+
 use codec::{Decode, Encode};
 use scale_info::TypeInfo;
 
modifiedruntime/common/src/constants.rsdiffbeforeafterboth
--- a/runtime/common/src/constants.rs
+++ b/runtime/common/src/constants.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use sp_runtime::Perbill;
 use frame_support::{
 	parameter_types,
modifiedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use frame_support::{dispatch::DispatchResult, ensure};
 use pallet_evm::{PrecompileHandle, PrecompileResult};
 use sp_core::H160;
modifiedruntime/common/src/lib.rsdiffbeforeafterboth
--- a/runtime/common/src/lib.rs
+++ b/runtime/common/src/lib.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod constants;
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 #[macro_export]
 macro_rules! impl_common_runtime_apis {
     (
@@ -331,30 +347,30 @@
                         .iter()
                         .filter_map(|(res_id)| Some(RmrkResourceInfo {
                             id: res_id.0,
-                            pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).unwrap(),
-                            pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).unwrap(),
-                            resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).unwrap() {
+                            pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).ok()?,
+                            pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).ok()?,
+                            resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).ok()? {
                                 ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
-                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).unwrap(),
-                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap(),
-                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).unwrap(),
-                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap(),
+                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
                                 }),
                                 ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
-                                    parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).unwrap(),
-                                    base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).unwrap(),
-                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).unwrap(),
-                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap(),
-                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).unwrap(),
-                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap(),
+                                    parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).ok()?,
+                                    base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
+                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
                                 }),
                                 ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
-                                    base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).unwrap(),
-                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).unwrap(),
-                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).unwrap(),
-                                    slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).unwrap(),
-                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).unwrap(),
-                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).unwrap(),
+                                    base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
+                                    src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+                                    metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+                                    slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).ok()?,
+                                    license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+                                    thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
                                 }),
                             },
                         }))
@@ -447,12 +463,10 @@
                     let theme_names = collection.collection_tokens()
                         .iter()
                         .filter_map(|token_id| {
-                            let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).unwrap();
+                            let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).ok()?;
 
                             match nft_type {
-                                Theme => Some(
-                                    RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).unwrap()
-                                ),
+                                Theme => RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).ok(),
                                 _ => None
                             }
                         })
@@ -829,6 +843,7 @@
                     list_benchmark!(list, extra, pallet_fungible, Fungible);
                     list_benchmark!(list, extra, pallet_refungible, Refungible);
                     list_benchmark!(list, extra, pallet_nonfungible, Nonfungible);
+                    list_benchmark!(list, extra, pallet_proxy_rmrk_core, RmrkCore);
                     // list_benchmark!(list, extra, pallet_evm_coder_substrate, EvmCoderSubstrate);
 
                     let storage_info = AllPalletsReversedWithSystemFirst::storage_info();
@@ -872,6 +887,7 @@
                     add_benchmark!(params, batches, pallet_fungible, Fungible);
                     add_benchmark!(params, batches, pallet_refungible, Refungible);
                     add_benchmark!(params, batches, pallet_nonfungible, Nonfungible);
+                    add_benchmark!(params, batches, pallet_proxy_rmrk_core, RmrkCore);
                     // add_benchmark!(params, batches, pallet_evm_coder_substrate, EvmCoderSubstrate);
 
                     if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) }
modifiedruntime/common/src/types.rsdiffbeforeafterboth
--- a/runtime/common/src/types.rs
+++ b/runtime/common/src/types.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use sp_runtime::{
 	traits::{Verify, IdentifyAccount, BlakeTwo256},
 	generic, MultiSignature,
modifiedruntime/opal/src/lib.rsdiffbeforeafterboth
--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -919,6 +919,7 @@
 }
 
 impl pallet_proxy_rmrk_core::Config for Runtime {
+	type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
 	type Event = Event;
 }
 
modifiedruntime/quartz/src/lib.rsdiffbeforeafterboth
--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -918,6 +918,7 @@
 }
 
 impl pallet_proxy_rmrk_core::Config for Runtime {
+	type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
 	type Event = Event;
 }
 
modifiedruntime/unique/src/lib.rsdiffbeforeafterboth
--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -917,6 +917,7 @@
 }
 
 impl pallet_proxy_rmrk_core::Config for Runtime {
+	type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
 	type Event = Event;
 }
 
modifiedtests/src/addCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -40,8 +40,10 @@
       expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
     });
   });
+});
 
-  it('Add admin using added collection admin.', async () => {
+describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
+  it("Not owner can't add collection admin.", async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       const collectionId = await createCollectionExpectSuccess();
       const alice = privateKeyWrapper('//Alice');
@@ -51,38 +53,43 @@
       const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.equal(alice.address);
 
-      const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(alice, changeAdminTx);
-
       const adminListAfterAddAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+      expect(adminListAfterAddAdmin).to.be.not.deep.contains(normalizeAccountId(bob.address));
 
       const changeAdminTxCharlie = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address));
-      await submitTransactionAsync(bob, changeAdminTxCharlie);
+      await expect(submitTransactionAsync(bob, changeAdminTxCharlie)).to.be.rejected;
+     
       const adminListAfterAddNewAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterAddNewAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
-      expect(adminListAfterAddNewAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
+      expect(adminListAfterAddNewAdmin).to.be.not.deep.contains(normalizeAccountId(bob.address));
+      expect(adminListAfterAddNewAdmin).to.be.not.deep.contains(normalizeAccountId(charlie.address));
     });
   });
-});
 
-describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
-  it("Not owner can't add collection admin.", async () => {
+  it("Admin can't add collection admin.", async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       const collectionId = await createCollectionExpectSuccess();
       const alice = privateKeyWrapper('//Alice');
-      const nonOwner = privateKeyWrapper('//Bob_stash');
+      const bob = privateKeyWrapper('//Bob');
+      const charlie = privateKeyWrapper('//CHARLIE');
+
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
+      expect(collection.owner.toString()).to.be.equal(alice.address);
 
-      const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(alice.address));
-      await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected;
+      const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
+      await submitTransactionAsync(alice, changeAdminTx);
 
       const adminListAfterAddAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterAddAdmin).not.to.be.deep.contains(normalizeAccountId(alice.address));
+      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
 
-      // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
-      await createCollectionExpectSuccess();
+      const changeAdminTxCharlie = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address));
+      await expect(submitTransactionAsync(bob, changeAdminTxCharlie)).to.be.rejected;
+     
+      const adminListAfterAddNewAdmin = await getAdminList(api, collectionId);
+      expect(adminListAfterAddNewAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+      expect(adminListAfterAddNewAdmin).to.be.not.deep.contains(normalizeAccountId(charlie.address));
     });
   });
+
   it("Can't add collection admin of not existing collection.", async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       // tslint:disable-next-line: no-bitwise
modifiedtests/src/eth/allowlist.test.tsdiffbeforeafterboth
--- a/tests/src/eth/allowlist.test.ts
+++ b/tests/src/eth/allowlist.test.ts
@@ -18,8 +18,8 @@
 import {contractHelpers, createEthAccountWithBalance, deployFlipper, itWeb3} from './util/helpers';
 
 describe('EVM allowlist', () => {
-  itWeb3('Contract allowlist can be toggled', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Contract allowlist can be toggled', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, owner);
 
     const helpers = contractHelpers(web3, owner);
@@ -36,10 +36,10 @@
     expect(await helpers.methods.allowlistEnabled(flipper.options.address).call()).to.be.false;
   });
 
-  itWeb3('Non-allowlisted user can\'t call contract with allowlist enabled', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Non-allowlisted user can\'t call contract with allowlist enabled', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, owner);
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const helpers = contractHelpers(web3, owner);
 
modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -32,16 +32,16 @@
 import Web3 from 'web3';
 
 describe('Contract calls', () => {
-  itWeb3('Call of simple contract fee is less than 0.2 UNQ', async ({web3, api}) => {
-    const deployer = await createEthAccountWithBalance(api, web3);
+  itWeb3('Call of simple contract fee is less than 0.2 UNQ', async ({web3, api, privateKeyWrapper}) => {
+    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, deployer);
 
     const cost = await recordEthFee(api, deployer, () => flipper.methods.flip().send({from: deployer}));
     expect(cost < BigInt(0.2 * Number(UNIQUE))).to.be.true;
   });
 
-  itWeb3('Balance transfer fee is less than 0.2 UNQ', async ({web3, api}) => {
-    const userA = await createEthAccountWithBalance(api, web3);
+  itWeb3('Balance transfer fee is less than 0.2 UNQ', async ({web3, api, privateKeyWrapper}) => {
+    const userA = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const userB = createEthAccount(web3);
 
     const cost = await recordEthFee(api, userA, () => web3.eth.sendTransaction({from: userA, to: userB, value: '1000000', ...GAS_ARGS}));
@@ -50,7 +50,7 @@
   });
 
   itWeb3('NFT transfer is close to 0.15 UNQ', async ({web3, api, privateKeyWrapper}) => {
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const receiver = createEthAccount(web3);
 
     const alice = privateKeyWrapper('//Alice');
modifiedtests/src/eth/collectionProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -7,7 +7,7 @@
 describe('EVM collection properties', () => {
   itWeb3('Can be set', async({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
 
     await addCollectionAdminExpectSuccess(alice, collection, {Ethereum: caller});
@@ -22,7 +22,7 @@
   });
   itWeb3('Can be deleted', async({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
 
     await executeTransaction(api, alice, api.tx.unique.setCollectionProperties(collection, [{key: 'testKey', value: 'testValue'}]));
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/contractSponsoring.test.ts
+++ b/tests/src/eth/contractSponsoring.test.ts
@@ -44,8 +44,8 @@
 import {evmToAddress} from '@polkadot/util-crypto';
 
 describe('Sponsoring EVM contracts', () => {
-  itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, owner);
     const helpers = contractHelpers(web3, owner);
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
@@ -53,9 +53,9 @@
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
   });
 
-  itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
-    const notOwner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, owner);
     const helpers = contractHelpers(web3, owner);
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
@@ -66,8 +66,8 @@
   itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
-    const caller = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const flipper = await deployFlipper(web3, owner);
 
@@ -94,7 +94,7 @@
   itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = createEthAccount(web3);
 
     const flipper = await deployFlipper(web3, owner);
@@ -124,7 +124,7 @@
   itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = createEthAccount(web3);
 
     const flipper = await deployFlipper(web3, owner);
@@ -152,8 +152,8 @@
   itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
-    const caller = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const originalCallerBalance = await web3.eth.getBalance(caller);
 
     const flipper = await deployFlipper(web3, owner);
@@ -181,8 +181,8 @@
   itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
-    const caller = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const originalCallerBalance = await web3.eth.getBalance(caller);
 
     const flipper = await deployFlipper(web3, owner);
@@ -214,30 +214,31 @@
   });
 
   // TODO: Find a way to calculate default rate limit
-  itWeb3('Default rate limit equals 7200', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Default rate limit equals 7200', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, owner);
     const helpers = contractHelpers(web3, owner);
     expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');
   });
 
-  itWeb3('Sponsoring collection from evm address via access list', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Sponsoring collection from evm address via access list', 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();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
-    const sponsor = await createEthAccountWithBalance(api, web3);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
     let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
     await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
 
     await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isConfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
 
     const user = createEthAccount(web3);
     const nextTokenId = await collectionEvm.methods.nextTokenId().call();
@@ -289,23 +290,24 @@
     }
   });
 
-  itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Check that transaction via EVM spend money from sponsor address', 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();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
-    const sponsor = await createEthAccountWithBalance(api, web3);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
     let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
     await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
     const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isConfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
 
     const user = createEthAccount(web3);
     await collectionEvm.methods.addCollectionAdmin(user).send();
modifiedtests/src/eth/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createCollection.test.ts
+++ b/tests/src/eth/createCollection.test.ts
@@ -28,67 +28,68 @@
 } from './util/helpers';
 
 describe('Create collection from EVM', () => {
-  itWeb3('Create collection', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
-    const collectionHelper = evmCollectionHelpers(web3, owner);
-    const collectionName = 'CollectionEVM';
-    const description = 'Some description';
-    const tokenPrefix = 'token prefix';
+  // itWeb3('Create collection', async ({api, web3, privateKeyWrapper}) => {
+  //   const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+  //   const collectionHelper = evmCollectionHelpers(web3, owner);
+  //   const collectionName = 'CollectionEVM';
+  //   const description = 'Some description';
+  //   const tokenPrefix = 'token prefix';
   
-    const collectionCountBefore = await getCreatedCollectionCount(api);
-    const result = await collectionHelper.methods
-      .createNonfungibleCollection(collectionName, description, tokenPrefix)
-      .send();
-    const collectionCountAfter = await getCreatedCollectionCount(api);
+  //   const collectionCountBefore = await getCreatedCollectionCount(api);
+  //   const result = await collectionHelper.methods
+  //     .createNonfungibleCollection(collectionName, description, tokenPrefix)
+  //     .send();
+  //   const collectionCountAfter = await getCreatedCollectionCount(api);
   
-    const {collectionId, collection} = await getCollectionAddressFromResult(api, result);
-    expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
-    expect(collectionId).to.be.eq(collectionCountAfter);
-    expect(collection.name.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(collectionName);
-    expect(collection.description.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(description);
-    expect(collection.tokenPrefix.toHuman()).to.be.eq(tokenPrefix);
-  });
+  //   const {collectionId, collection} = await getCollectionAddressFromResult(api, result);
+  //   expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
+  //   expect(collectionId).to.be.eq(collectionCountAfter);
+  //   expect(collection.name.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(collectionName);
+  //   expect(collection.description.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(description);
+  //   expect(collection.tokenPrefix.toHuman()).to.be.eq(tokenPrefix);
+  // });
 
-  itWeb3('Check collection address exist', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
-    const collectionHelpers = evmCollectionHelpers(web3, owner);
+  // itWeb3('Check collection address exist', async ({api, web3, privateKeyWrapper}) => {
+  //   const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+  //   const collectionHelpers = evmCollectionHelpers(web3, owner);
   
-    const expectedCollectionId = await getCreatedCollectionCount(api) + 1;
-    const expectedCollectionAddress = collectionIdToAddress(expectedCollectionId);
-    expect(await collectionHelpers.methods
-      .isCollectionExist(expectedCollectionAddress)
-      .call()).to.be.false;
+  //   const expectedCollectionId = await getCreatedCollectionCount(api) + 1;
+  //   const expectedCollectionAddress = collectionIdToAddress(expectedCollectionId);
+  //   expect(await collectionHelpers.methods
+  //     .isCollectionExist(expectedCollectionAddress)
+  //     .call()).to.be.false;
 
-    await collectionHelpers.methods
-      .createNonfungibleCollection('A', 'A', 'A')
-      .send();
+  //   await collectionHelpers.methods
+  //     .createNonfungibleCollection('A', 'A', 'A')
+  //     .send();
     
-    expect(await collectionHelpers.methods
-      .isCollectionExist(expectedCollectionAddress)
-      .call()).to.be.true;
-  });
+  //   expect(await collectionHelpers.methods
+  //     .isCollectionExist(expectedCollectionAddress)
+  //     .call()).to.be.true;
+  // });
   
-  itWeb3('Set sponsorship', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Set sponsorship', 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();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
-    const sponsor = await createEthAccountWithBalance(api, web3);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
     let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
     await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
     const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isConfirmed).to.be.true;
-    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
   });
 
-  itWeb3('Set limits', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Set limits', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionHelpers = evmCollectionHelpers(web3, owner);
     const result = await collectionHelpers.methods.createNonfungibleCollection('Const collection', '5', '5').send();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
@@ -127,8 +128,8 @@
     expect(collectionSub.limits.transfersEnabled.toHuman()).to.be.eq(limits.transfersEnabled);
   });
 
-  itWeb3('Collection address exist', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Collection address exist', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
     const collectionHelpers = evmCollectionHelpers(web3, owner);
     expect(await collectionHelpers.methods
@@ -144,8 +145,8 @@
 });
 
 describe('(!negative tests!) Create collection from EVM', () => {
-  itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, owner);
     {
       const MAX_NAME_LENGHT = 64;
@@ -190,8 +191,8 @@
       .call()).to.be.rejectedWith('NotSufficientFounds');
   });
 
-  itWeb3('(!negative test!) Check owner', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const notOwner = await createEthAccount(web3);
     const collectionHelpers = evmCollectionHelpers(web3, owner);
     const result = await collectionHelpers.methods.createNonfungibleCollection('A', 'A', 'A').send();
@@ -199,7 +200,7 @@
     const contractEvmFromNotOwner = evmCollection(web3, notOwner, collectionIdAddress);
     const EXPECTED_ERROR = 'NoPermission';
     {
-      const sponsor = await createEthAccountWithBalance(api, web3);
+      const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
       await expect(contractEvmFromNotOwner.methods
         .setCollectionSponsor(sponsor)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
@@ -216,8 +217,8 @@
     }
   });
 
-  itWeb3('(!negative test!) Set limits', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('(!negative test!) Set limits', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionHelpers = evmCollectionHelpers(web3, owner);
     const result = await collectionHelpers.methods.createNonfungibleCollection('Schema collection', 'A', 'A').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
modifiedtests/src/eth/crossTransfer.test.tsdiffbeforeafterboth
--- a/tests/src/eth/crossTransfer.test.ts
+++ b/tests/src/eth/crossTransfer.test.ts
@@ -48,8 +48,8 @@
     });
     const alice = privateKeyWrapper('//Alice');
     const bob = privateKeyWrapper('//Bob');
-    const bobProxy = await createEthAccountWithBalance(api, web3);
-    const aliceProxy = await createEthAccountWithBalance(api, web3);
+    const bobProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const aliceProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, alice.address);
     await transferExpectSuccess(collection, 0, alice, {Ethereum: aliceProxy} , 200, 'Fungible');
@@ -85,8 +85,8 @@
     const alice = privateKeyWrapper('//Alice');
     const bob = privateKeyWrapper('//Bob');
     const charlie = privateKeyWrapper('//Charlie');
-    const bobProxy = await createEthAccountWithBalance(api, web3);
-    const aliceProxy = await createEthAccountWithBalance(api, web3);
+    const bobProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const aliceProxy = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});
     await transferExpectSuccess(collection, tokenId, alice, {Ethereum: aliceProxy} , 1, 'NFT');
     const address = collectionIdToAddress(collection);
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -27,7 +27,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Substrate: alice.address});
 
@@ -45,7 +45,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: caller});
 
@@ -65,7 +65,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});
 
@@ -208,7 +208,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const spender = createEthAccount(web3);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});
@@ -226,8 +226,8 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
-    const spender = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});
 
@@ -246,7 +246,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const receiver = createEthAccount(web3);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});
modifiedtests/src/eth/helpersSmoke.test.tsdiffbeforeafterboth
--- a/tests/src/eth/helpersSmoke.test.ts
+++ b/tests/src/eth/helpersSmoke.test.ts
@@ -18,16 +18,16 @@
 import {createEthAccountWithBalance, deployFlipper, itWeb3, contractHelpers} from './util/helpers';
 
 describe('Helpers sanity check', () => {
-  itWeb3('Contract owner is recorded', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Contract owner is recorded', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const flipper = await deployFlipper(web3, owner);
 
     expect(await contractHelpers(web3, owner).methods.contractOwner(flipper.options.address).call()).to.be.equal(owner);
   });
 
-  itWeb3('Flipper is working', async ({api, web3}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Flipper is working', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, owner);
 
     expect(await flipper.methods.getValue().call()).to.be.false;
modifiedtests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth
--- a/tests/src/eth/marketplace/marketplace.test.ts
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -39,7 +39,7 @@
 describe('Matcher contract usage', () => {
   itWeb3('With UNQ', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
       ...GAS_ARGS,
@@ -100,8 +100,8 @@
 
   itWeb3('With escrow', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const matcherOwner = await createEthAccountWithBalance(api, web3);
-    const escrow = await createEthAccountWithBalance(api, web3);
+    const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const escrow = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
       ...GAS_ARGS,
@@ -171,7 +171,7 @@
 
   itWeb3('Sell tokens from substrate user via EVM contract', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
       ...GAS_ARGS,
modifiedtests/src/eth/migration.test.tsdiffbeforeafterboth
--- a/tests/src/eth/migration.test.ts
+++ b/tests/src/eth/migration.test.ts
@@ -54,7 +54,7 @@
     ];
 
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await submitTransactionAsync(alice, api.tx.sudo.sudo(api.tx.evmMigration.begin(ADDRESS) as any));
     await submitTransactionAsync(alice, api.tx.sudo.sudo(api.tx.evmMigration.setData(ADDRESS, DATA as any) as any));
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -26,7 +26,7 @@
       mode: {type: 'NFT'},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});
 
@@ -43,7 +43,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum:caller});
     await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
     await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
@@ -61,7 +61,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
 
     const address = collectionIdToAddress(collection);
@@ -73,8 +73,8 @@
 });
 
 describe('NFT: Plain calls', () => {
-  itWeb3('Can perform mint()', async ({web3, api}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, owner);
     let result = await helper.methods.createNonfungibleCollection('Mint collection', '6', '6').send();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
@@ -119,7 +119,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: caller});
     await submitTransactionAsync(alice, changeAdminTx);
     const receiver = createEthAccount(web3);
@@ -182,7 +182,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});
 
@@ -341,7 +341,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const spender = createEthAccount(web3);
 
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});
@@ -359,8 +359,8 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
-    const spender = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});
 
@@ -379,7 +379,7 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const receiver = createEthAccount(web3);
 
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});
@@ -541,12 +541,12 @@
 });
 
 describe('Common metadata', () => {
-  itWeb3('Returns collection name', async ({api, web3}) => {
+  itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => {
     const collection = await createCollectionExpectSuccess({
       name: 'token name',
       mode: {type: 'NFT'},
     });
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const address = collectionIdToAddress(collection);
     const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
@@ -555,12 +555,12 @@
     expect(name).to.equal('token name');
   });
 
-  itWeb3('Returns symbol name', async ({api, web3}) => {
+  itWeb3('Returns symbol name', async ({api, web3, privateKeyWrapper}) => {
     const collection = await createCollectionExpectSuccess({
       tokenPrefix: 'TOK',
       mode: {type: 'NFT'},
     });
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const address = collectionIdToAddress(collection);
     const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
modifiedtests/src/eth/payable.test.tsdiffbeforeafterboth
--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -22,8 +22,8 @@
 import {getBalanceSingle, transferBalanceExpectSuccess} from '../substrate/get-balance';
 
 describe('EVM payable contracts', () => {
-  itWeb3('Evm contract can receive wei from eth account', async ({api, web3}) => {
-    const deployer = await createEthAccountWithBalance(api, web3);
+  itWeb3('Evm contract can receive wei from eth account', async ({api, web3, privateKeyWrapper}) => {
+    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const contract = await deployCollector(web3, deployer);
 
     await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', ...GAS_ARGS});
@@ -32,7 +32,7 @@
   });
 
   itWeb3('Evm contract can receive wei from substrate account', async ({api, web3, privateKeyWrapper}) => {
-    const deployer = await createEthAccountWithBalance(api, web3);
+    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const contract = await deployCollector(web3, deployer);
     const alice = privateKeyWrapper('//Alice');
 
@@ -62,7 +62,7 @@
 
   // We can't handle sending balance to backing storage of evm balance, because evmToAddress operation is irreversible
   itWeb3('Wei sent directly to backing storage of evm contract balance is unaccounted', async({api, web3, privateKeyWrapper}) => {
-    const deployer = await createEthAccountWithBalance(api, web3);
+    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const contract = await deployCollector(web3, deployer);
     const alice = privateKeyWrapper('//Alice');
 
@@ -75,7 +75,7 @@
     const FEE_BALANCE = 1000n * UNIQUE;
     const CONTRACT_BALANCE = 1n * UNIQUE;
 
-    const deployer = await createEthAccountWithBalance(api, web3);
+    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const contract = await deployCollector(web3, deployer);
     const alice = privateKeyWrapper('//Alice');
 
modifiedtests/src/eth/proxy/fungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/fungibleProxy.test.ts
+++ b/tests/src/eth/proxy/fungibleProxy.test.ts
@@ -21,10 +21,11 @@
 import {ApiPromise} from '@polkadot/api';
 import Web3 from 'web3';
 import {readFile} from 'fs/promises';
+import {IKeyringPair} from '@polkadot/types/types';
 
-async function proxyWrap(api: ApiPromise, web3: Web3, wrapped: any) {
+async function proxyWrap(api: ApiPromise, web3: Web3, wrapped: any, privateKeyWrapper: (account: string) => IKeyringPair) {
   // Proxy owner has no special privilegies, we don't need to reuse them
-  const owner = await createEthAccountWithBalance(api, web3);
+  const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
   const proxyContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/UniqueFungibleProxy.abi`)).toString()), undefined, {
     from: owner,
     ...GAS_ARGS,
@@ -40,12 +41,12 @@
       mode: {type: 'Fungible', decimalPoints: 0},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Substrate: alice.address});
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const totalSupply = await contract.methods.totalSupply().call();
 
     expect(totalSupply).to.equal('200');
@@ -57,12 +58,12 @@
       mode: {type: 'Fungible', decimalPoints: 0},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: caller});
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const balance = await contract.methods.balanceOf(caller).call();
 
     expect(balance).to.equal('200');
@@ -76,11 +77,11 @@
       mode: {type: 'Fungible', decimalPoints: 0},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const spender = createEthAccount(web3);
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: contract.options.address});
 
     {
@@ -112,8 +113,8 @@
       mode: {type: 'Fungible', decimalPoints: 0},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
-    const owner = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});
 
@@ -121,7 +122,7 @@
 
     const address = collectionIdToAddress(collection);
     const evmCollection = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS});
-    const contract = await proxyWrap(api, web3, evmCollection);
+    const contract = await proxyWrap(api, web3, evmCollection, privateKeyWrapper);
 
     await evmCollection.methods.approve(contract.options.address, 100).send({from: owner});
 
@@ -167,11 +168,11 @@
       mode: {type: 'Fungible', decimalPoints: 0},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
-    const receiver = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: contract.options.address});
 
     {
modifiedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -22,10 +22,11 @@
 import Web3 from 'web3';
 import {readFile} from 'fs/promises';
 import {ApiPromise} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
 
-async function proxyWrap(api: ApiPromise, web3: Web3, wrapped: any) {
+async function proxyWrap(api: ApiPromise, web3: Web3, wrapped: any, privateKeyWrapper: (account: string) => IKeyringPair) {
   // Proxy owner has no special privilegies, we don't need to reuse them
-  const owner = await createEthAccountWithBalance(api, web3);
+  const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
   const proxyContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/UniqueNFTProxy.abi`)).toString()), undefined, {
     from: owner,
     ...GAS_ARGS,
@@ -40,12 +41,12 @@
       mode: {type: 'NFT'},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const totalSupply = await contract.methods.totalSupply().call();
 
     expect(totalSupply).to.equal('1');
@@ -57,13 +58,13 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
     await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
     await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const balance = await contract.methods.balanceOf(caller).call();
 
     expect(balance).to.equal('3');
@@ -75,11 +76,11 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const owner = await contract.methods.ownerOf(tokenId).call();
 
     expect(owner).to.equal(caller);
@@ -87,18 +88,18 @@
 });
 
 describe('NFT (Via EVM proxy): Plain calls', () => {
-  itWeb3('Can perform mint()', async ({web3, api}) => {
-    const owner = await createEthAccountWithBalance(api, web3);
+  itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionHelper = evmCollectionHelpers(web3, owner);
     const result = await collectionHelper.methods
       .createNonfungibleCollection('A', 'A', 'A')
       .send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const receiver = createEthAccount(web3);
     const collectionEvmOwned = evmCollection(web3, owner, collectionIdAddress);
     const collectionEvm = evmCollection(web3, caller, collectionIdAddress);
-    const contract = await proxyWrap(api, web3, collectionEvm);
+    const contract = await proxyWrap(api, web3, collectionEvm, privateKeyWrapper);
     await collectionEvmOwned.methods.addCollectionAdmin(contract.options.address).send();
 
     {
@@ -135,11 +136,11 @@
     });
     const alice = privateKeyWrapper('//Alice');
 
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const receiver = createEthAccount(web3);
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: contract.options.address});
     await submitTransactionAsync(alice, changeAdminTx);
 
@@ -197,10 +198,10 @@
       mode: {type: 'NFT'},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: contract.options.address});
 
     const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: contract.options.address});
@@ -229,11 +230,11 @@
       mode: {type: 'NFT'},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const spender = createEthAccount(web3);
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address), privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: contract.options.address});
 
     {
@@ -259,14 +260,14 @@
       mode: {type: 'NFT'},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
-    const owner = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
 
     const receiver = createEthAccount(web3);
 
     const address = collectionIdToAddress(collection);
     const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
-    const contract = await proxyWrap(api, web3, evmCollection);
+    const contract = await proxyWrap(api, web3, evmCollection, privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});
 
     await evmCollection.methods.approve(contract.options.address, tokenId).send({from: owner});
@@ -303,11 +304,11 @@
       mode: {type: 'NFT'},
     });
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const receiver = createEthAccount(web3);
 
     const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper);
     const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: contract.options.address});
 
     {
modifiedtests/src/eth/scheduling.test.tsdiffbeforeafterboth
--- a/tests/src/eth/scheduling.test.ts
+++ b/tests/src/eth/scheduling.test.ts
@@ -17,14 +17,13 @@
 import {expect} from 'chai';
 import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers';
 import {scheduleExpectSuccess, waitNewBlocks} from '../util/helpers';
-import privateKey from '../substrate/privateKey';
 
 describe('Scheduing EVM smart contracts', () => {
-  itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3}) => {
-    const deployer = await createEthAccountWithBalance(api, web3);
+  itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3, privateKeyWrapper}) => {
+    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, deployer);
     const initialValue = await flipper.methods.getValue().call();
-    const alice = privateKey('//Alice');
+    const alice = privateKeyWrapper('//Alice');
     await transferBalanceToEth(api, alice, subToEth(alice.address));
 
     {
modifiedtests/src/eth/sponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/sponsoring.test.ts
+++ b/tests/src/eth/sponsoring.test.ts
@@ -21,7 +21,7 @@
   itWeb3('Fee is deducted from contract if sponsoring is enabled', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = createEthAccount(web3);
     const originalCallerBalance = await web3.eth.getBalance(caller);
     expect(originalCallerBalance).to.be.equal('0');
@@ -52,8 +52,8 @@
   itWeb3('...but this doesn\'t applies to payable value', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
-    const owner = await createEthAccountWithBalance(api, web3);
-    const caller = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const originalCallerBalance = await web3.eth.getBalance(caller);
     expect(originalCallerBalance).to.be.not.equal('0');
 
modifiedtests/src/eth/tokenProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -7,7 +7,7 @@
 describe('EVM token properties', () => {
   itWeb3('Can be reconfigured', async({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {
       const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await addCollectionAdminExpectSuccess(alice, collection, {Ethereum: caller});
@@ -25,7 +25,7 @@
   });
   itWeb3('Can be set', async({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     const token = await createItemExpectSuccess(alice, collection, 'NFT');
 
@@ -48,7 +48,7 @@
   });
   itWeb3('Can be deleted', async({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3);
+    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     const token = await createItemExpectSuccess(alice, collection, 'NFT');
 
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -112,8 +112,8 @@
   return account.address;
 }
 
-export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3) {
-  const alice = privateKey('//Alice');
+export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) {
+  const alice = privateKeyWrapper('//Alice');
   const account = createEthAccount(web3);
   await transferBalanceToEth(api, alice, account);
 
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -46,33 +46,6 @@
     });
   });
 
-  it('Remove collection admin by admin.', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      const collectionId = await createCollectionExpectSuccess();
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
-      const charlie = privateKeyWrapper('//Charlie');
-      const collection = await queryCollectionExpectSuccess(api, collectionId);
-      expect(collection.owner.toString()).to.be.eq(alice.address);
-      // first - add collection admin Bob
-      const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(alice, addAdminTx);
-
-      const addAdminTx2 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address));
-      await submitTransactionAsync(alice, addAdminTx2);
-
-      const adminListAfterAddAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
-
-      // then remove bob from admins of collection
-      const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(charlie, removeAdminTx);
-
-      const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterRemoveAdmin).not.to.be.deep.contains(normalizeAccountId(bob.address));
-    });
-  });
-
   it('Remove admin from collection that has no admins', async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       const alice = privateKeyWrapper('//Alice');
@@ -120,7 +93,7 @@
     });
   });
 
-  it('Regular user Can\'t remove collection admin', async () => {
+  it('Regular user can\'t remove collection admin', async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       const collectionId = await createCollectionExpectSuccess();
       const alice = privateKeyWrapper('//Alice');
@@ -137,4 +110,23 @@
       await createCollectionExpectSuccess();
     });
   });
+
+  it('Admin can\'t remove collection admin.', async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      const collectionId = await createCollectionExpectSuccess();
+      const alice = privateKeyWrapper('//Alice');
+      const bob = privateKeyWrapper('//Bob');
+      const charlie = privateKeyWrapper('//Charlie');
+
+      const adminListAfterAddAdmin = await getAdminList(api, collectionId);
+      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+
+      const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
+      await expect(submitTransactionAsync(charlie, removeAdminTx)).to.be.rejected;
+
+      const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
+      expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+      expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
+    });
+  });
 });
modifiedtests/src/rmrk/rmrk.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/rmrk.test.ts
+++ b/tests/src/rmrk/rmrk.test.ts
@@ -49,8 +49,8 @@
 
 describe('RMRK External Integration Test', () => {
   before(async () => {
-    await usingApi(async () => {
-      alice = privateKey('//Alice');
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
     });
   });
 
@@ -75,9 +75,9 @@
   let rmrkNftId: number;
 
   before(async () => {
-    await usingApi(async api => {
-      alice = privateKey('//Alice');
-      bob = privateKey('//Bob');
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
 
       const collectionIds = await createRmrkCollection(api, alice);
       uniqueCollectionId = collectionIds.uniqueId;
@@ -210,9 +210,9 @@
   let nftId: number;
 
   before(async () => {
-    await usingApi(async () => {
-      alice = privateKey('//Alice');
-      bob = privateKey('//Bob');
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
 
       collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       nftId = await createItemExpectSuccess(alice, collectionId, 'NFT');
modifiedtests/src/rpc.load.tsdiffbeforeafterboth
--- a/tests/src/rpc.load.ts
+++ b/tests/src/rpc.load.ts
@@ -59,8 +59,7 @@
   const deployer = await findUnusedAddress(api, privateKeyWrapper);
 
   // Transfer balance to it
-  const keyring = new Keyring({type: 'sr25519'});
-  const alice = keyring.addFromUri('//Alice');
+  const alice = privateKeyWrapper('//Alice');
   const amount = BigInt(endowment) + 10n**15n;
   const tx = api.tx.balances.transfer(deployer.address, amount);
   await submitTransactionAsync(alice, tx);
modifiedtests/src/scheduler.test.tsdiffbeforeafterboth
--- a/tests/src/scheduler.test.ts
+++ b/tests/src/scheduler.test.ts
@@ -16,7 +16,6 @@
 
 import chai, {expect} from 'chai';
 import chaiAsPromised from 'chai-as-promised';
-import privateKey from './substrate/privateKey';
 import {
   default as usingApi, 
   submitTransactionAsync,
@@ -52,9 +51,9 @@
   let scheduledIdSlider: number;
 
   before(async() => {
-    await usingApi(async () => {
-      alice = privateKey('//Alice');
-      bob = privateKey('//Bob');
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
     });
 
     scheduledIdBase = '0x' + '0'.repeat(31);