git.delta.rocks / unique-network / refs/commits / 2ef3805aebca

difftreelog

fix(rmrk) move rmrk specific types to rmrk-traits

Daniel Shiposha2022-06-06parent: #17af80b.patch.diff
in: master

19 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6338,6 +6338,7 @@
  "pallet-nonfungible",
  "pallet-structure",
  "parity-scale-codec 3.1.2",
+ "rmrk-traits",
  "scale-info",
  "sp-core",
  "sp-runtime",
@@ -6357,6 +6358,7 @@
  "pallet-nonfungible",
  "pallet-rmrk-core",
  "parity-scale-codec 3.1.2",
+ "rmrk-traits",
  "scale-info",
  "sp-core",
  "sp-runtime",
@@ -8999,15 +9001,24 @@
 version = "0.0.1"
 dependencies = [
  "parity-scale-codec 2.3.1",
+ "rmrk-traits",
  "serde",
  "sp-api",
  "sp-core",
  "sp-runtime",
  "sp-std",
- "up-data-structs",
 ]
 
 [[package]]
+name = "rmrk-traits"
+version = "0.1.0"
+dependencies = [
+ "parity-scale-codec 3.1.2",
+ "scale-info",
+ "serde",
+]
+
+[[package]]
 name = "rocksdb"
 version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -12723,6 +12734,7 @@
  "frame-system",
  "pallet-evm",
  "parity-scale-codec 3.1.2",
+ "rmrk-traits",
  "scale-info",
  "serde",
  "sp-core",
modifiedpallets/proxy-rmrk-core/Cargo.tomldiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/Cargo.toml
+++ b/pallets/proxy-rmrk-core/Cargo.toml
@@ -22,6 +22,7 @@
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.22" }
 frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
+rmrk-traits = { default-features = false, path = "../../primitives/rmrk-traits" }
 scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
 derivative = { version = "2.2.0", features = ["use_core"] }
 
@@ -33,6 +34,7 @@
     "sp-runtime/std",
     "sp-std/std",
     "up-data-structs/std",
+    "rmrk-traits/std",
     "pallet-common/std",
     "pallet-nonfungible/std",
     "pallet-structure/std",
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		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136		CannotRejectNonOwnedNft,137		ResourceNotPending,138	}139140	#[pallet::call]141	impl<T: Config> Pallet<T> {142		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143		#[transactional]144		pub fn create_collection(145			origin: OriginFor<T>,146			metadata: RmrkString,147			max: Option<u32>,148			symbol: RmrkCollectionSymbol,149		) -> DispatchResult {150			let sender = ensure_signed(origin)?;151152			let limits = CollectionLimits {153				owner_can_transfer: Some(false),154				token_limit: max,155				..Default::default()156			};157158			let data = CreateCollectionData {159				limits: Some(limits),160				token_prefix: symbol161					.into_inner()162					.try_into()163					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164				permissions: Some(CollectionPermissions {165					nesting: Some(NestingRule::Owner),166					..Default::default()167				}),168				..Default::default()169			};170171			let unique_collection_id = Self::init_collection(172				T::CrossAccountId::from_sub(sender.clone()),173				data,174				[175					Self::rmrk_property(Metadata, &metadata)?,176					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177				]178				.into_iter(),179			)?;180			let rmrk_collection_id = <CollectionIndex<T>>::get();181182			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185			<CollectionIndex<T>>::mutate(|n| *n += 1);186187			Self::deposit_event(Event::CollectionCreated {188				issuer: sender,189				collection_id: rmrk_collection_id,190			});191192			Ok(())193		}194195		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196		#[transactional]197		pub fn destroy_collection(198			origin: OriginFor<T>,199			collection_id: RmrkCollectionId,200		) -> DispatchResult {201			let sender = ensure_signed(origin)?;202			let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204			let collection = Self::get_typed_nft_collection(205				Self::unique_collection_id(collection_id)?,206				misc::CollectionType::Regular,207			)?;208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_unique_err_to_proxy)?;211212			Self::deposit_event(Event::CollectionDestroyed {213				issuer: sender,214				collection_id,215			});216217			Ok(())218		}219220		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221		#[transactional]222		pub fn change_collection_issuer(223			origin: OriginFor<T>,224			collection_id: RmrkCollectionId,225			new_issuer: <T::Lookup as StaticLookup>::Source,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228229			let new_issuer = T::Lookup::lookup(new_issuer)?;230231			Self::change_collection_owner(232				Self::unique_collection_id(collection_id)?,233				misc::CollectionType::Regular,234				sender.clone(),235				new_issuer.clone(),236			)?;237238			Self::deposit_event(Event::IssuerChanged {239				old_issuer: sender,240				new_issuer,241				collection_id,242			});243244			Ok(())245		}246247		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248		#[transactional]249		pub fn lock_collection(250			origin: OriginFor<T>,251			collection_id: RmrkCollectionId,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254			let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256			let collection = Self::get_typed_nft_collection(257				Self::unique_collection_id(collection_id)?,258				misc::CollectionType::Regular,259			)?;260261			Self::check_collection_owner(&collection, &cross_sender)?;262263			let token_count = collection.total_supply();264265			let mut collection = collection.into_inner();266			collection.limits.token_limit = Some(token_count);267			collection.save()?;268269			Self::deposit_event(Event::CollectionLocked {270				issuer: sender,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn mint_nft(280			origin: OriginFor<T>,281			owner: T::AccountId,282			collection_id: RmrkCollectionId,283			recipient: Option<T::AccountId>,284			royalty_amount: Option<Permill>,285			metadata: RmrkString,286			transferable: bool,287		) -> DispatchResult {288			let sender = ensure_signed(origin)?;289			let sender = T::CrossAccountId::from_sub(sender);290			let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {293				recipient: recipient.unwrap_or_else(|| owner.clone()),294				amount,295			});296297			let collection = Self::get_typed_nft_collection(298				Self::unique_collection_id(collection_id)?,299				misc::CollectionType::Regular,300			)?;301302			let nft_id = Self::create_nft(303				&sender,304				&cross_owner,305				&collection,306				[307					Self::rmrk_property(TokenType, &NftType::Regular)?,308					Self::rmrk_property(Transferable, &transferable)?,309					Self::rmrk_property(PendingNftAccept, &false)?,310					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311					Self::rmrk_property(Metadata, &metadata)?,312					Self::rmrk_property(Equipped, &false)?,313					Self::rmrk_property(314						ResourceCollection,315						&Self::init_collection(316							sender.clone(),317							CreateCollectionData {318								..Default::default()319							},320							[Self::rmrk_property(321								CollectionType,322								&misc::CollectionType::Resource,323							)?]324							.into_iter(),325						)?,326					)?, // todo possibly add limits to the collection if rmrk warrants them327					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328				]329				.into_iter(),330			)331			.map_err(|err| match err {332				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333				err => Self::map_unique_err_to_proxy(err),334			})?;335336			Self::deposit_event(Event::NftMinted {337				owner,338				collection_id,339				nft_id: nft_id.0,340			});341342			Ok(())343		}344345		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346		#[transactional]347		pub fn burn_nft(348			origin: OriginFor<T>,349			collection_id: RmrkCollectionId,350			nft_id: RmrkNftId,351		) -> DispatchResult {352			let sender = ensure_signed(origin)?;353			let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355			Self::destroy_nft(356				cross_sender,357				Self::unique_collection_id(collection_id)?,358				nft_id.into(),359			)360			.map_err(Self::map_unique_err_to_proxy)?;361362			Self::deposit_event(Event::NFTBurned {363				owner: sender,364				nft_id,365			});366367			Ok(())368		}369370		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371		#[transactional]372		pub fn send(373			origin: OriginFor<T>,374			rmrk_collection_id: RmrkCollectionId,375			rmrk_nft_id: RmrkNftId,376			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377		) -> DispatchResult {378			let sender = ensure_signed(origin.clone())?;379			let cross_sender = T::CrossAccountId::from_sub(sender);380381			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382			let nft_id = rmrk_nft_id.into();383384			let token_data =385				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387			let from = token_data.owner;388389			let collection =390				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392			ensure!(393				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394				<Error<T>>::NonTransferable395			);396397			ensure!(398				!Self::get_nft_property_decoded(399					collection_id,400					nft_id,401					RmrkProperty::PendingNftAccept402				)?,403				<Error<T>>::NoPermission404			);405406			let target_owner;407408			match new_owner {409				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410					target_owner = T::CrossAccountId::from_sub(account_id);411				}412				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413					target_collection_id,414					target_nft_id,415				) => {416					let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418					let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421						target_collection_id,422						target_nft_id.into(),423						Some((collection_id, nft_id)),424						&target_nft_budget,425					)426					.map_err(Self::map_unique_err_to_proxy)?;427428					let is_approval_required = cross_sender != target_nft_owner;429430					if is_approval_required {431						target_owner = target_nft_owner;432433						<PalletNft<T>>::set_scoped_token_property(434							collection.id,435							nft_id,436							PropertyScope::Rmrk,437							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438						)?;439					} else {440						target_owner = T::CrossTokenAddressMapping::token_to_address(441							target_collection_id,442							target_nft_id.into(),443						);444					}445				}446			}447448			let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450			<PalletNft<T>>::transfer_from(451				&collection,452				&cross_sender,453				&from,454				&target_owner,455				nft_id,456				&src_nft_budget,457			)458			.map_err(Self::map_unique_err_to_proxy)?;459460			Ok(())461		}462463		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464		#[transactional]465		pub fn accept_nft(466			origin: OriginFor<T>,467			rmrk_collection_id: RmrkCollectionId,468			rmrk_nft_id: RmrkNftId,469			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470		) -> DispatchResult {471			let sender = ensure_signed(origin.clone())?;472			let cross_sender = T::CrossAccountId::from_sub(sender);473474			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475			let nft_id = rmrk_nft_id.into();476477			let collection =478				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480			let new_owner = match new_owner {481				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482					T::CrossAccountId::from_sub(account_id)483				}484				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485					target_collection_id,486					target_nft_id,487				) => {488					let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490					T::CrossTokenAddressMapping::token_to_address(491						target_collection_id,492						TokenId(target_nft_id),493					)494				}495			};496497			let budget = budget::Value::new(NESTING_BUDGET);498499			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500				.map_err(|err| {501					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502						<Error<T>>::CannotAcceptNonOwnedNft.into()503					} else {504						Self::map_unique_err_to_proxy(err)505					}506				})?;507508			<PalletNft<T>>::set_scoped_token_property(509				collection.id,510				nft_id,511				PropertyScope::Rmrk,512				Self::rmrk_property(PendingNftAccept, &false)?,513			)?;514515			Ok(())516		}517518		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519		#[transactional]520		pub fn reject_nft(521			origin: OriginFor<T>,522			collection_id: RmrkCollectionId,523			nft_id: RmrkNftId,524		) -> DispatchResult {525			let sender = ensure_signed(origin)?;526			let cross_sender = T::CrossAccountId::from_sub(sender);527528			let collection_id = Self::unique_collection_id(collection_id)?;529			let nft_id = nft_id.into();530531			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532				if err == <CommonError<T>>::NoPermission.into()533					|| err == <CommonError<T>>::ApprovedValueTooLow.into()534				{535					<Error<T>>::CannotRejectNonOwnedNft.into()536				} else {537					Self::map_unique_err_to_proxy(err)538				}539			})?;540541			Ok(())542		}543544		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545		#[transactional]546		pub fn accept_resource(547			origin: OriginFor<T>,548			rmrk_collection_id: RmrkCollectionId,549			rmrk_nft_id: RmrkNftId,550			rmrk_resource_id: RmrkResourceId,551		) -> DispatchResult {552			let sender = ensure_signed(origin)?;553			let cross_sender = T::CrossAccountId::from_sub(sender);554555			let collection_id = Self::unique_collection_id(rmrk_collection_id)556				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558			let nft_id = rmrk_nft_id.into();559			let resource_id = rmrk_resource_id.into();560561			let budget = budget::Value::new(NESTING_BUDGET);562563			let nft_owner =564				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)565					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567			let resource_collection_id: CollectionId =568				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571			let is_pending: bool = Self::get_nft_property_decoded(572				resource_collection_id,573				resource_id,574				PendingResourceAccept,575			)576			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578			ensure!(is_pending, <Error<T>>::ResourceNotPending);579580			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582			<PalletNft<T>>::set_scoped_token_property(583				resource_collection_id,584				rmrk_resource_id.into(),585				PropertyScope::Rmrk,586				Self::rmrk_property(PendingResourceAccept, &false)?,587			)?;588589			Ok(())590		}591592		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593		#[transactional]594		pub fn accept_resource_removal(595			origin: OriginFor<T>,596			rmrk_collection_id: RmrkCollectionId,597			rmrk_nft_id: RmrkNftId,598			rmrk_resource_id: RmrkResourceId,599		) -> DispatchResult {600			let sender = ensure_signed(origin)?;601			let cross_sender = T::CrossAccountId::from_sub(sender);602603			let collection_id = Self::unique_collection_id(rmrk_collection_id)604				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606			let nft_id = rmrk_nft_id.into();607			let resource_id = rmrk_resource_id.into();608609			let budget = budget::Value::new(NESTING_BUDGET);610611			let nft_owner =612				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617			let resource_collection_id: CollectionId =618				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621			let is_pending: bool = Self::get_nft_property_decoded(622				resource_collection_id,623				resource_id,624				PendingResourceRemoval,625			)626			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628			ensure!(is_pending, <Error<T>>::ResourceNotPending);629630			let resource_collection = Self::get_typed_nft_collection(631				resource_collection_id,632				misc::CollectionType::Resource,633			)?;634635			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636				.map_err(Self::map_unique_err_to_proxy)?;637638			Ok(())639		}640641		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642		#[transactional]643		pub fn set_property(644			origin: OriginFor<T>,645			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,646			maybe_nft_id: Option<RmrkNftId>,647			key: RmrkKeyString,648			value: RmrkValueString,649		) -> DispatchResult {650			let sender = ensure_signed(origin)?;651			let sender = T::CrossAccountId::from_sub(sender);652653			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654			let budget = budget::Value::new(NESTING_BUDGET);655656			match maybe_nft_id {657				Some(nft_id) => {658					let token_id: TokenId = nft_id.into();659660					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;661					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;662663					<PalletNft<T>>::set_scoped_token_property(664						collection_id,665						token_id,666						PropertyScope::Rmrk,667						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668					)?;669				}670				None => {671					let collection = Self::get_typed_nft_collection(672						collection_id,673						misc::CollectionType::Regular,674					)?;675676					Self::check_collection_owner(&collection, &sender)?;677678					<PalletCommon<T>>::set_scoped_collection_property(679						collection_id,680						PropertyScope::Rmrk,681						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682					)?;683				}684			}685686			Self::deposit_event(Event::PropertySet {687				collection_id: rmrk_collection_id,688				maybe_nft_id,689				key,690				value,691			});692693			Ok(())694		}695696		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697		#[transactional]698		pub fn add_basic_resource(699			origin: OriginFor<T>,700			collection_id: RmrkCollectionId,701			nft_id: RmrkNftId,702			resource: RmrkBasicResource,703		) -> DispatchResult {704			let sender = ensure_signed(origin.clone())?;705706			let resource_id = Self::resource_add(707				sender,708				Self::unique_collection_id(collection_id)?,709				nft_id.into(),710				[711					Self::rmrk_property(TokenType, &NftType::Resource)?,712					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,713					Self::rmrk_property(Src, &resource.src)?,714					Self::rmrk_property(Metadata, &resource.metadata)?,715					Self::rmrk_property(License, &resource.license)?,716					Self::rmrk_property(Thumb, &resource.thumb)?,717				]718				.into_iter(),719			)?;720721			Self::deposit_event(Event::ResourceAdded {722				nft_id,723				resource_id,724			});725			Ok(())726		}727728		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]729		#[transactional]730		pub fn add_composable_resource(731			origin: OriginFor<T>,732			collection_id: RmrkCollectionId,733			nft_id: RmrkNftId,734			_resource_id: RmrkBoundedResource,735			resource: RmrkComposableResource,736		) -> DispatchResult {737			let sender = ensure_signed(origin.clone())?;738739			let resource_id = Self::resource_add(740				sender,741				Self::unique_collection_id(collection_id)?,742				nft_id.into(),743				[744					Self::rmrk_property(TokenType, &NftType::Resource)?,745					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,746					Self::rmrk_property(Parts, &resource.parts)?,747					Self::rmrk_property(Base, &resource.base)?,748					Self::rmrk_property(Src, &resource.src)?,749					Self::rmrk_property(Metadata, &resource.metadata)?,750					Self::rmrk_property(License, &resource.license)?,751					Self::rmrk_property(Thumb, &resource.thumb)?,752				]753				.into_iter(),754			)?;755756			Self::deposit_event(Event::ResourceAdded {757				nft_id,758				resource_id,759			});760			Ok(())761		}762763		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]764		#[transactional]765		pub fn add_slot_resource(766			origin: OriginFor<T>,767			collection_id: RmrkCollectionId,768			nft_id: RmrkNftId,769			resource: RmrkSlotResource,770		) -> DispatchResult {771			let sender = ensure_signed(origin.clone())?;772773			let resource_id = Self::resource_add(774				sender,775				Self::unique_collection_id(collection_id)?,776				nft_id.into(),777				[778					Self::rmrk_property(TokenType, &NftType::Resource)?,779					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,780					Self::rmrk_property(Base, &resource.base)?,781					Self::rmrk_property(Src, &resource.src)?,782					Self::rmrk_property(Metadata, &resource.metadata)?,783					Self::rmrk_property(Slot, &resource.slot)?,784					Self::rmrk_property(License, &resource.license)?,785					Self::rmrk_property(Thumb, &resource.thumb)?,786				]787				.into_iter(),788			)?;789790			Self::deposit_event(Event::ResourceAdded {791				nft_id,792				resource_id,793			});794			Ok(())795		}796797		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]798		#[transactional]799		pub fn remove_resource(800			origin: OriginFor<T>,801			collection_id: RmrkCollectionId,802			nft_id: RmrkNftId,803			resource_id: RmrkResourceId,804		) -> DispatchResult {805			let sender = ensure_signed(origin.clone())?;806807			Self::resource_remove(808				sender,809				Self::unique_collection_id(collection_id)?,810				nft_id.into(),811				resource_id.into(),812			)?;813814			Self::deposit_event(Event::ResourceRemoval {815				nft_id,816				resource_id,817			});818			Ok(())819		}820	}821}822823impl<T: Config> Pallet<T> {824	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {825		let key = rmrk_key.to_key::<T>()?;826827		let scoped_key = PropertyScope::Rmrk828			.apply(key)829			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;830831		Ok(scoped_key)832	}833834	// todo think about renaming these835	pub fn rmrk_property<E: Encode>(836		rmrk_key: RmrkProperty,837		value: &E,838	) -> Result<Property, DispatchError> {839		let key = rmrk_key.to_key::<T>()?;840841		let value = value842			.encode()843			.try_into()844			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;845846		let property = Property { key, value };847848		Ok(property)849	}850851	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {852		vec.decode()853			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())854	}855856	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>857	where858		BoundedVec<u8, S>: TryFrom<Vec<u8>>,859	{860		vec.rebind()861			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())862	}863864	fn init_collection(865		sender: T::CrossAccountId,866		data: CreateCollectionData<T::AccountId>,867		properties: impl Iterator<Item = Property>,868	) -> Result<CollectionId, DispatchError> {869		let collection_id = <PalletNft<T>>::init_collection(sender, data);870871		if let Err(DispatchError::Arithmetic(_)) = &collection_id {872			return Err(<Error<T>>::NoAvailableCollectionId.into());873		}874875		<PalletCommon<T>>::set_scoped_collection_properties(876			collection_id?,877			PropertyScope::Rmrk,878			properties,879		)?;880881		collection_id882	}883884	pub fn create_nft(885		sender: &T::CrossAccountId,886		owner: &T::CrossAccountId,887		collection: &NonfungibleHandle<T>,888		properties: impl Iterator<Item = Property>,889	) -> Result<TokenId, DispatchError> {890		let data = CreateNftExData {891			properties: BoundedVec::default(),892			owner: owner.clone(),893		};894895		let budget = budget::Value::new(NESTING_BUDGET);896897		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;898899		let nft_id = <PalletNft<T>>::current_token_id(collection.id);900901		<PalletNft<T>>::set_scoped_token_properties(902			collection.id,903			nft_id,904			PropertyScope::Rmrk,905			properties,906		)?;907908		Ok(nft_id)909	}910911	fn destroy_nft(912		sender: T::CrossAccountId,913		collection_id: CollectionId,914		token_id: TokenId,915	) -> DispatchResult {916		let collection =917			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;918919		let token_data =920			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;921922		let from = token_data.owner;923924		let budget = budget::Value::new(NESTING_BUDGET);925926		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)927	}928929	fn resource_add(930		sender: T::AccountId,931		collection_id: CollectionId,932		token_id: TokenId,933		resource_properties: impl Iterator<Item = Property>,934	) -> Result<RmrkResourceId, DispatchError> {935		let collection =936			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;937		ensure!(collection.owner == sender, Error::<T>::NoPermission);938939		let sender = T::CrossAccountId::from_sub(sender);940		let budget = budget::Value::new(NESTING_BUDGET);941942		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)943			.map_err(Self::map_unique_err_to_proxy)?;944945		let pending = sender != nft_owner;946947		let resource_collection_id: CollectionId =948			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;949		let resource_collection =950			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;951952		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them953954		let resource_id = Self::create_nft(955			&sender,956			&nft_owner,957			&resource_collection,958			resource_properties.chain(959				[960					Self::rmrk_property(PendingResourceAccept, &pending)?,961					Self::rmrk_property(PendingResourceRemoval, &false)?,962				]963				.into_iter(),964			),965		)966		.map_err(|err| match err {967			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),968			err => Self::map_unique_err_to_proxy(err),969		})?;970971		Ok(resource_id.0)972	}973974	fn resource_remove(975		sender: T::AccountId,976		collection_id: CollectionId,977		nft_id: TokenId,978		resource_id: TokenId,979	) -> DispatchResult {980		let collection =981			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982		ensure!(collection.owner == sender, Error::<T>::NoPermission);983984		let resource_collection_id: CollectionId =985			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;986		let resource_collection =987			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;988		ensure!(989			<PalletNft<T>>::token_exists(&resource_collection, resource_id),990			Error::<T>::ResourceDoesntExist991		);992993		let budget = up_data_structs::budget::Value::new(10);994		let topmost_owner =995			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;996997		let sender = T::CrossAccountId::from_sub(sender);998		if topmost_owner == sender {999			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1000				.map_err(Self::map_unique_err_to_proxy)?;1001		} else {1002			<PalletNft<T>>::set_scoped_token_property(1003				resource_collection_id,1004				resource_id,1005				PropertyScope::Rmrk,1006				Self::rmrk_property(PendingResourceRemoval, &true)?,1007			)?;1008		}10091010		Ok(())1011	}10121013	fn change_collection_owner(1014		collection_id: CollectionId,1015		collection_type: misc::CollectionType,1016		sender: T::AccountId,1017		new_owner: T::AccountId,1018	) -> DispatchResult {1019		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1020		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10211022		let mut collection = collection.into_inner();10231024		collection.owner = new_owner;1025		collection.save()1026	}10271028	fn check_collection_owner(1029		collection: &NonfungibleHandle<T>,1030		account: &T::CrossAccountId,1031	) -> DispatchResult {1032		collection1033			.check_is_owner(account)1034			.map_err(Self::map_unique_err_to_proxy)1035	}10361037	pub fn last_collection_idx() -> RmrkCollectionId {1038		<CollectionIndex<T>>::get()1039	}10401041	pub fn unique_collection_id(1042		rmrk_collection_id: RmrkCollectionId,1043	) -> Result<CollectionId, DispatchError> {1044		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1045			.map_err(|_| <Error<T>>::CollectionUnknown.into())1046	}10471048	pub fn rmrk_collection_id(1049		unique_collection_id: CollectionId,1050	) -> Result<RmrkCollectionId, DispatchError> {1051		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1052			.map_err(|_| <Error<T>>::CollectionUnknown.into())1053	}10541055	pub fn get_nft_collection(1056		collection_id: CollectionId,1057	) -> Result<NonfungibleHandle<T>, DispatchError> {1058		let collection = <CollectionHandle<T>>::try_get(collection_id)1059			.map_err(|_| <Error<T>>::CollectionUnknown)?;10601061		match collection.mode {1062			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1063			_ => Err(<Error<T>>::CollectionUnknown.into()),1064		}1065	}10661067	pub fn collection_exists(collection_id: CollectionId) -> bool {1068		<CollectionHandle<T>>::try_get(collection_id).is_ok()1069	}10701071	pub fn get_collection_property(1072		collection_id: CollectionId,1073		key: RmrkProperty,1074	) -> Result<PropertyValue, DispatchError> {1075		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1076			.get(&Self::rmrk_property_key(key)?)1077			.ok_or(<Error<T>>::CollectionUnknown)?1078			.clone();10791080		Ok(collection_property)1081	}10821083	pub fn get_collection_property_decoded<V: Decode>(1084		collection_id: CollectionId,1085		key: RmrkProperty,1086	) -> Result<V, DispatchError> {1087		Self::decode_property(Self::get_collection_property(collection_id, key)?)1088	}10891090	pub fn get_collection_type(1091		collection_id: CollectionId,1092	) -> Result<misc::CollectionType, DispatchError> {1093		Self::get_collection_property_decoded(collection_id, CollectionType)1094			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1095	}10961097	pub fn ensure_collection_type(1098		collection_id: CollectionId,1099		collection_type: misc::CollectionType,1100	) -> DispatchResult {1101		let actual_type = Self::get_collection_type(collection_id)?;1102		ensure!(1103			actual_type == collection_type,1104			<CommonError<T>>::NoPermission1105		);11061107		Ok(())1108	}11091110	pub fn get_typed_nft_collection(1111		collection_id: CollectionId,1112		collection_type: misc::CollectionType,1113	) -> Result<NonfungibleHandle<T>, DispatchError> {1114		Self::ensure_collection_type(collection_id, collection_type)?;11151116		Self::get_nft_collection(collection_id)1117	}11181119	pub fn get_typed_nft_collection_mapped(1120		rmrk_collection_id: RmrkCollectionId,1121		collection_type: misc::CollectionType,1122	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1123		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11241125		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11261127		Ok((collection, unique_collection_id))1128	}11291130	pub fn get_nft_property(1131		collection_id: CollectionId,1132		nft_id: TokenId,1133		key: RmrkProperty,1134	) -> Result<PropertyValue, DispatchError> {1135		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1136			.get(&Self::rmrk_property_key(key)?)1137			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1138			.clone();11391140		Ok(nft_property)1141	}11421143	pub fn get_nft_property_decoded<V: Decode>(1144		collection_id: CollectionId,1145		nft_id: TokenId,1146		key: RmrkProperty,1147	) -> Result<V, DispatchError> {1148		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1149	}11501151	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1152		<TokenData<T>>::contains_key((collection_id, nft_id))1153	}11541155	pub fn get_nft_type(1156		collection_id: CollectionId,1157		token_id: TokenId,1158	) -> Result<NftType, DispatchError> {1159		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1160			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1161	}11621163	pub fn ensure_nft_type(1164		collection_id: CollectionId,1165		token_id: TokenId,1166		nft_type: NftType,1167	) -> DispatchResult {1168		let actual_type = Self::get_nft_type(collection_id, token_id)?;1169		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11701171		Ok(())1172	}11731174	pub fn ensure_nft_owner(1175		collection_id: CollectionId,1176		token_id: TokenId,1177		possible_owner: &T::CrossAccountId,1178		nesting_budget: &dyn budget::Budget,1179	) -> DispatchResult {1180		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1181			possible_owner.clone(),1182			collection_id,1183			token_id,1184			None,1185			nesting_budget,1186		)?;11871188		ensure!(is_owned, <Error<T>>::NoPermission);11891190		Ok(())1191	}11921193	pub fn filter_user_properties<Key, Value, R, Mapper>(1194		collection_id: CollectionId,1195		token_id: Option<TokenId>,1196		filter_keys: Option<Vec<RmrkPropertyKey>>,1197		mapper: Mapper,1198	) -> Result<Vec<R>, DispatchError>1199	where1200		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1201		Value: Decode + Default,1202		Mapper: Fn(Key, Value) -> R,1203	{1204		filter_keys1205			.map(|keys| {1206				let properties = keys1207					.into_iter()1208					.filter_map(|key| {1209						let key: Key = key.try_into().ok()?;12101211						let value = match token_id {1212							Some(token_id) => Self::get_nft_property_decoded(1213								collection_id,1214								token_id,1215								UserProperty(key.as_ref()),1216							),1217							None => Self::get_collection_property_decoded(1218								collection_id,1219								UserProperty(key.as_ref()),1220							),1221						}1222						.ok()?;12231224						Some(mapper(key, value))1225					})1226					.collect();12271228				Ok(properties)1229			})1230			.unwrap_or_else(|| {1231				let properties =1232					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12331234				Ok(properties)1235			})1236	}12371238	pub fn iterate_user_properties<Key, Value, R, Mapper>(1239		collection_id: CollectionId,1240		token_id: Option<TokenId>,1241		mapper: Mapper,1242	) -> Result<impl Iterator<Item = R>, DispatchError>1243	where1244		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1245		Value: Decode + Default,1246		Mapper: Fn(Key, Value) -> R,1247	{1248		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12491250		let properties = match token_id {1251			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1252			None => <PalletCommon<T>>::collection_properties(collection_id),1253		};12541255		let properties = properties.into_iter().filter_map(move |(key, value)| {1256			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12571258			let key: Key = key.to_vec().try_into().ok()?;1259			let value: Value = value.decode().ok()?;12601261			Some(mapper(key, value))1262		});12631264		Ok(properties)1265	}12661267	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1268		map_unique_err_to_proxy! {1269			match err {1270				CommonError::NoPermission => NoPermission,1271				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1272				CommonError::PublicMintingNotAllowed => NoPermission,1273				CommonError::TokenNotFound => NoAvailableNftId,1274				CommonError::ApprovedValueTooLow => NoPermission,1275				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1276				StructureError::TokenNotFound => NoAvailableNftId,1277				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1278			}1279		}1280	}1281}
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::*;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		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136		CannotRejectNonOwnedNft,137		ResourceNotPending,138	}139140	#[pallet::call]141	impl<T: Config> Pallet<T> {142		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143		#[transactional]144		pub fn create_collection(145			origin: OriginFor<T>,146			metadata: RmrkString,147			max: Option<u32>,148			symbol: RmrkCollectionSymbol,149		) -> DispatchResult {150			let sender = ensure_signed(origin)?;151152			let limits = CollectionLimits {153				owner_can_transfer: Some(false),154				token_limit: max,155				..Default::default()156			};157158			let data = CreateCollectionData {159				limits: Some(limits),160				token_prefix: symbol161					.into_inner()162					.try_into()163					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164				permissions: Some(CollectionPermissions {165					nesting: Some(NestingRule::Owner),166					..Default::default()167				}),168				..Default::default()169			};170171			let unique_collection_id = Self::init_collection(172				T::CrossAccountId::from_sub(sender.clone()),173				data,174				[175					Self::rmrk_property(Metadata, &metadata)?,176					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177				]178				.into_iter(),179			)?;180			let rmrk_collection_id = <CollectionIndex<T>>::get();181182			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185			<CollectionIndex<T>>::mutate(|n| *n += 1);186187			Self::deposit_event(Event::CollectionCreated {188				issuer: sender,189				collection_id: rmrk_collection_id,190			});191192			Ok(())193		}194195		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196		#[transactional]197		pub fn destroy_collection(198			origin: OriginFor<T>,199			collection_id: RmrkCollectionId,200		) -> DispatchResult {201			let sender = ensure_signed(origin)?;202			let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204			let collection = Self::get_typed_nft_collection(205				Self::unique_collection_id(collection_id)?,206				misc::CollectionType::Regular,207			)?;208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_unique_err_to_proxy)?;211212			Self::deposit_event(Event::CollectionDestroyed {213				issuer: sender,214				collection_id,215			});216217			Ok(())218		}219220		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221		#[transactional]222		pub fn change_collection_issuer(223			origin: OriginFor<T>,224			collection_id: RmrkCollectionId,225			new_issuer: <T::Lookup as StaticLookup>::Source,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228229			let new_issuer = T::Lookup::lookup(new_issuer)?;230231			Self::change_collection_owner(232				Self::unique_collection_id(collection_id)?,233				misc::CollectionType::Regular,234				sender.clone(),235				new_issuer.clone(),236			)?;237238			Self::deposit_event(Event::IssuerChanged {239				old_issuer: sender,240				new_issuer,241				collection_id,242			});243244			Ok(())245		}246247		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248		#[transactional]249		pub fn lock_collection(250			origin: OriginFor<T>,251			collection_id: RmrkCollectionId,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254			let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256			let collection = Self::get_typed_nft_collection(257				Self::unique_collection_id(collection_id)?,258				misc::CollectionType::Regular,259			)?;260261			Self::check_collection_owner(&collection, &cross_sender)?;262263			let token_count = collection.total_supply();264265			let mut collection = collection.into_inner();266			collection.limits.token_limit = Some(token_count);267			collection.save()?;268269			Self::deposit_event(Event::CollectionLocked {270				issuer: sender,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn mint_nft(280			origin: OriginFor<T>,281			owner: T::AccountId,282			collection_id: RmrkCollectionId,283			recipient: Option<T::AccountId>,284			royalty_amount: Option<Permill>,285			metadata: RmrkString,286			transferable: bool,287		) -> DispatchResult {288			let sender = ensure_signed(origin)?;289			let sender = T::CrossAccountId::from_sub(sender);290			let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {293				recipient: recipient.unwrap_or_else(|| owner.clone()),294				amount,295			});296297			let collection = Self::get_typed_nft_collection(298				Self::unique_collection_id(collection_id)?,299				misc::CollectionType::Regular,300			)?;301302			let nft_id = Self::create_nft(303				&sender,304				&cross_owner,305				&collection,306				[307					Self::rmrk_property(TokenType, &NftType::Regular)?,308					Self::rmrk_property(Transferable, &transferable)?,309					Self::rmrk_property(PendingNftAccept, &false)?,310					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311					Self::rmrk_property(Metadata, &metadata)?,312					Self::rmrk_property(Equipped, &false)?,313					Self::rmrk_property(314						ResourceCollection,315						&Self::init_collection(316							sender.clone(),317							CreateCollectionData {318								..Default::default()319							},320							[Self::rmrk_property(321								CollectionType,322								&misc::CollectionType::Resource,323							)?]324							.into_iter(),325						)?,326					)?, // todo possibly add limits to the collection if rmrk warrants them327					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328				]329				.into_iter(),330			)331			.map_err(|err| match err {332				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333				err => Self::map_unique_err_to_proxy(err),334			})?;335336			Self::deposit_event(Event::NftMinted {337				owner,338				collection_id,339				nft_id: nft_id.0,340			});341342			Ok(())343		}344345		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346		#[transactional]347		pub fn burn_nft(348			origin: OriginFor<T>,349			collection_id: RmrkCollectionId,350			nft_id: RmrkNftId,351		) -> DispatchResult {352			let sender = ensure_signed(origin)?;353			let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355			Self::destroy_nft(356				cross_sender,357				Self::unique_collection_id(collection_id)?,358				nft_id.into(),359			)360			.map_err(Self::map_unique_err_to_proxy)?;361362			Self::deposit_event(Event::NFTBurned {363				owner: sender,364				nft_id,365			});366367			Ok(())368		}369370		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371		#[transactional]372		pub fn send(373			origin: OriginFor<T>,374			rmrk_collection_id: RmrkCollectionId,375			rmrk_nft_id: RmrkNftId,376			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377		) -> DispatchResult {378			let sender = ensure_signed(origin.clone())?;379			let cross_sender = T::CrossAccountId::from_sub(sender);380381			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382			let nft_id = rmrk_nft_id.into();383384			let token_data =385				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387			let from = token_data.owner;388389			let collection =390				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392			ensure!(393				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394				<Error<T>>::NonTransferable395			);396397			ensure!(398				!Self::get_nft_property_decoded(399					collection_id,400					nft_id,401					RmrkProperty::PendingNftAccept402				)?,403				<Error<T>>::NoPermission404			);405406			let target_owner;407408			match new_owner {409				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410					target_owner = T::CrossAccountId::from_sub(account_id);411				}412				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413					target_collection_id,414					target_nft_id,415				) => {416					let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418					let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421						target_collection_id,422						target_nft_id.into(),423						Some((collection_id, nft_id)),424						&target_nft_budget,425					)426					.map_err(Self::map_unique_err_to_proxy)?;427428					let is_approval_required = cross_sender != target_nft_owner;429430					if is_approval_required {431						target_owner = target_nft_owner;432433						<PalletNft<T>>::set_scoped_token_property(434							collection.id,435							nft_id,436							PropertyScope::Rmrk,437							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438						)?;439					} else {440						target_owner = T::CrossTokenAddressMapping::token_to_address(441							target_collection_id,442							target_nft_id.into(),443						);444					}445				}446			}447448			let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450			<PalletNft<T>>::transfer_from(451				&collection,452				&cross_sender,453				&from,454				&target_owner,455				nft_id,456				&src_nft_budget,457			)458			.map_err(Self::map_unique_err_to_proxy)?;459460			Ok(())461		}462463		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464		#[transactional]465		pub fn accept_nft(466			origin: OriginFor<T>,467			rmrk_collection_id: RmrkCollectionId,468			rmrk_nft_id: RmrkNftId,469			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470		) -> DispatchResult {471			let sender = ensure_signed(origin.clone())?;472			let cross_sender = T::CrossAccountId::from_sub(sender);473474			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475			let nft_id = rmrk_nft_id.into();476477			let collection =478				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480			let new_owner = match new_owner {481				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482					T::CrossAccountId::from_sub(account_id)483				}484				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485					target_collection_id,486					target_nft_id,487				) => {488					let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490					T::CrossTokenAddressMapping::token_to_address(491						target_collection_id,492						TokenId(target_nft_id),493					)494				}495			};496497			let budget = budget::Value::new(NESTING_BUDGET);498499			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500				.map_err(|err| {501					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502						<Error<T>>::CannotAcceptNonOwnedNft.into()503					} else {504						Self::map_unique_err_to_proxy(err)505					}506				})?;507508			<PalletNft<T>>::set_scoped_token_property(509				collection.id,510				nft_id,511				PropertyScope::Rmrk,512				Self::rmrk_property(PendingNftAccept, &false)?,513			)?;514515			Ok(())516		}517518		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519		#[transactional]520		pub fn reject_nft(521			origin: OriginFor<T>,522			collection_id: RmrkCollectionId,523			nft_id: RmrkNftId,524		) -> DispatchResult {525			let sender = ensure_signed(origin)?;526			let cross_sender = T::CrossAccountId::from_sub(sender);527528			let collection_id = Self::unique_collection_id(collection_id)?;529			let nft_id = nft_id.into();530531			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532				if err == <CommonError<T>>::NoPermission.into()533					|| err == <CommonError<T>>::ApprovedValueTooLow.into()534				{535					<Error<T>>::CannotRejectNonOwnedNft.into()536				} else {537					Self::map_unique_err_to_proxy(err)538				}539			})?;540541			Ok(())542		}543544		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545		#[transactional]546		pub fn accept_resource(547			origin: OriginFor<T>,548			rmrk_collection_id: RmrkCollectionId,549			rmrk_nft_id: RmrkNftId,550			rmrk_resource_id: RmrkResourceId,551		) -> DispatchResult {552			let sender = ensure_signed(origin)?;553			let cross_sender = T::CrossAccountId::from_sub(sender);554555			let collection_id = Self::unique_collection_id(rmrk_collection_id)556				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558			let nft_id = rmrk_nft_id.into();559			let resource_id = rmrk_resource_id.into();560561			let budget = budget::Value::new(NESTING_BUDGET);562563			let nft_owner =564				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)565					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567			let resource_collection_id: CollectionId =568				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571			let is_pending: bool = Self::get_nft_property_decoded(572				resource_collection_id,573				resource_id,574				PendingResourceAccept,575			)576			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578			ensure!(is_pending, <Error<T>>::ResourceNotPending);579580			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582			<PalletNft<T>>::set_scoped_token_property(583				resource_collection_id,584				rmrk_resource_id.into(),585				PropertyScope::Rmrk,586				Self::rmrk_property(PendingResourceAccept, &false)?,587			)?;588589			Ok(())590		}591592		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593		#[transactional]594		pub fn accept_resource_removal(595			origin: OriginFor<T>,596			rmrk_collection_id: RmrkCollectionId,597			rmrk_nft_id: RmrkNftId,598			rmrk_resource_id: RmrkResourceId,599		) -> DispatchResult {600			let sender = ensure_signed(origin)?;601			let cross_sender = T::CrossAccountId::from_sub(sender);602603			let collection_id = Self::unique_collection_id(rmrk_collection_id)604				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606			let nft_id = rmrk_nft_id.into();607			let resource_id = rmrk_resource_id.into();608609			let budget = budget::Value::new(NESTING_BUDGET);610611			let nft_owner =612				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617			let resource_collection_id: CollectionId =618				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621			let is_pending: bool = Self::get_nft_property_decoded(622				resource_collection_id,623				resource_id,624				PendingResourceRemoval,625			)626			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628			ensure!(is_pending, <Error<T>>::ResourceNotPending);629630			let resource_collection = Self::get_typed_nft_collection(631				resource_collection_id,632				misc::CollectionType::Resource,633			)?;634635			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636				.map_err(Self::map_unique_err_to_proxy)?;637638			Ok(())639		}640641		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642		#[transactional]643		pub fn set_property(644			origin: OriginFor<T>,645			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,646			maybe_nft_id: Option<RmrkNftId>,647			key: RmrkKeyString,648			value: RmrkValueString,649		) -> DispatchResult {650			let sender = ensure_signed(origin)?;651			let sender = T::CrossAccountId::from_sub(sender);652653			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654			let budget = budget::Value::new(NESTING_BUDGET);655656			match maybe_nft_id {657				Some(nft_id) => {658					let token_id: TokenId = nft_id.into();659660					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;661					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;662663					<PalletNft<T>>::set_scoped_token_property(664						collection_id,665						token_id,666						PropertyScope::Rmrk,667						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668					)?;669				}670				None => {671					let collection = Self::get_typed_nft_collection(672						collection_id,673						misc::CollectionType::Regular,674					)?;675676					Self::check_collection_owner(&collection, &sender)?;677678					<PalletCommon<T>>::set_scoped_collection_property(679						collection_id,680						PropertyScope::Rmrk,681						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682					)?;683				}684			}685686			Self::deposit_event(Event::PropertySet {687				collection_id: rmrk_collection_id,688				maybe_nft_id,689				key,690				value,691			});692693			Ok(())694		}695696		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697		#[transactional]698		pub fn add_basic_resource(699			origin: OriginFor<T>,700			collection_id: RmrkCollectionId,701			nft_id: RmrkNftId,702			resource: RmrkBasicResource,703		) -> DispatchResult {704			let sender = ensure_signed(origin.clone())?;705706			let resource_id = Self::resource_add(707				sender,708				Self::unique_collection_id(collection_id)?,709				nft_id.into(),710				[711					Self::rmrk_property(TokenType, &NftType::Resource)?,712					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,713					Self::rmrk_property(Src, &resource.src)?,714					Self::rmrk_property(Metadata, &resource.metadata)?,715					Self::rmrk_property(License, &resource.license)?,716					Self::rmrk_property(Thumb, &resource.thumb)?,717				]718				.into_iter(),719			)?;720721			Self::deposit_event(Event::ResourceAdded {722				nft_id,723				resource_id,724			});725			Ok(())726		}727728		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]729		#[transactional]730		pub fn add_composable_resource(731			origin: OriginFor<T>,732			collection_id: RmrkCollectionId,733			nft_id: RmrkNftId,734			_resource_id: RmrkBoundedResource,735			resource: RmrkComposableResource,736		) -> DispatchResult {737			let sender = ensure_signed(origin.clone())?;738739			let resource_id = Self::resource_add(740				sender,741				Self::unique_collection_id(collection_id)?,742				nft_id.into(),743				[744					Self::rmrk_property(TokenType, &NftType::Resource)?,745					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,746					Self::rmrk_property(Parts, &resource.parts)?,747					Self::rmrk_property(Base, &resource.base)?,748					Self::rmrk_property(Src, &resource.src)?,749					Self::rmrk_property(Metadata, &resource.metadata)?,750					Self::rmrk_property(License, &resource.license)?,751					Self::rmrk_property(Thumb, &resource.thumb)?,752				]753				.into_iter(),754			)?;755756			Self::deposit_event(Event::ResourceAdded {757				nft_id,758				resource_id,759			});760			Ok(())761		}762763		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]764		#[transactional]765		pub fn add_slot_resource(766			origin: OriginFor<T>,767			collection_id: RmrkCollectionId,768			nft_id: RmrkNftId,769			resource: RmrkSlotResource,770		) -> DispatchResult {771			let sender = ensure_signed(origin.clone())?;772773			let resource_id = Self::resource_add(774				sender,775				Self::unique_collection_id(collection_id)?,776				nft_id.into(),777				[778					Self::rmrk_property(TokenType, &NftType::Resource)?,779					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,780					Self::rmrk_property(Base, &resource.base)?,781					Self::rmrk_property(Src, &resource.src)?,782					Self::rmrk_property(Metadata, &resource.metadata)?,783					Self::rmrk_property(Slot, &resource.slot)?,784					Self::rmrk_property(License, &resource.license)?,785					Self::rmrk_property(Thumb, &resource.thumb)?,786				]787				.into_iter(),788			)?;789790			Self::deposit_event(Event::ResourceAdded {791				nft_id,792				resource_id,793			});794			Ok(())795		}796797		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]798		#[transactional]799		pub fn remove_resource(800			origin: OriginFor<T>,801			collection_id: RmrkCollectionId,802			nft_id: RmrkNftId,803			resource_id: RmrkResourceId,804		) -> DispatchResult {805			let sender = ensure_signed(origin.clone())?;806807			Self::resource_remove(808				sender,809				Self::unique_collection_id(collection_id)?,810				nft_id.into(),811				resource_id.into(),812			)?;813814			Self::deposit_event(Event::ResourceRemoval {815				nft_id,816				resource_id,817			});818			Ok(())819		}820	}821}822823impl<T: Config> Pallet<T> {824	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {825		let key = rmrk_key.to_key::<T>()?;826827		let scoped_key = PropertyScope::Rmrk828			.apply(key)829			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;830831		Ok(scoped_key)832	}833834	// todo think about renaming these835	pub fn rmrk_property<E: Encode>(836		rmrk_key: RmrkProperty,837		value: &E,838	) -> Result<Property, DispatchError> {839		let key = rmrk_key.to_key::<T>()?;840841		let value = value842			.encode()843			.try_into()844			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;845846		let property = Property { key, value };847848		Ok(property)849	}850851	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {852		vec.decode()853			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())854	}855856	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>857	where858		BoundedVec<u8, S>: TryFrom<Vec<u8>>,859	{860		vec.rebind()861			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())862	}863864	fn init_collection(865		sender: T::CrossAccountId,866		data: CreateCollectionData<T::AccountId>,867		properties: impl Iterator<Item = Property>,868	) -> Result<CollectionId, DispatchError> {869		let collection_id = <PalletNft<T>>::init_collection(sender, data);870871		if let Err(DispatchError::Arithmetic(_)) = &collection_id {872			return Err(<Error<T>>::NoAvailableCollectionId.into());873		}874875		<PalletCommon<T>>::set_scoped_collection_properties(876			collection_id?,877			PropertyScope::Rmrk,878			properties,879		)?;880881		collection_id882	}883884	pub fn create_nft(885		sender: &T::CrossAccountId,886		owner: &T::CrossAccountId,887		collection: &NonfungibleHandle<T>,888		properties: impl Iterator<Item = Property>,889	) -> Result<TokenId, DispatchError> {890		let data = CreateNftExData {891			properties: BoundedVec::default(),892			owner: owner.clone(),893		};894895		let budget = budget::Value::new(NESTING_BUDGET);896897		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;898899		let nft_id = <PalletNft<T>>::current_token_id(collection.id);900901		<PalletNft<T>>::set_scoped_token_properties(902			collection.id,903			nft_id,904			PropertyScope::Rmrk,905			properties,906		)?;907908		Ok(nft_id)909	}910911	fn destroy_nft(912		sender: T::CrossAccountId,913		collection_id: CollectionId,914		token_id: TokenId,915	) -> DispatchResult {916		let collection =917			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;918919		let token_data =920			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;921922		let from = token_data.owner;923924		let budget = budget::Value::new(NESTING_BUDGET);925926		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)927	}928929	fn resource_add(930		sender: T::AccountId,931		collection_id: CollectionId,932		token_id: TokenId,933		resource_properties: impl Iterator<Item = Property>,934	) -> Result<RmrkResourceId, DispatchError> {935		let collection =936			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;937		ensure!(collection.owner == sender, Error::<T>::NoPermission);938939		let sender = T::CrossAccountId::from_sub(sender);940		let budget = budget::Value::new(NESTING_BUDGET);941942		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)943			.map_err(Self::map_unique_err_to_proxy)?;944945		let pending = sender != nft_owner;946947		let resource_collection_id: CollectionId =948			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;949		let resource_collection =950			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;951952		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them953954		let resource_id = Self::create_nft(955			&sender,956			&nft_owner,957			&resource_collection,958			resource_properties.chain(959				[960					Self::rmrk_property(PendingResourceAccept, &pending)?,961					Self::rmrk_property(PendingResourceRemoval, &false)?,962				]963				.into_iter(),964			),965		)966		.map_err(|err| match err {967			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),968			err => Self::map_unique_err_to_proxy(err),969		})?;970971		Ok(resource_id.0)972	}973974	fn resource_remove(975		sender: T::AccountId,976		collection_id: CollectionId,977		nft_id: TokenId,978		resource_id: TokenId,979	) -> DispatchResult {980		let collection =981			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982		ensure!(collection.owner == sender, Error::<T>::NoPermission);983984		let resource_collection_id: CollectionId =985			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;986		let resource_collection =987			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;988		ensure!(989			<PalletNft<T>>::token_exists(&resource_collection, resource_id),990			Error::<T>::ResourceDoesntExist991		);992993		let budget = up_data_structs::budget::Value::new(10);994		let topmost_owner =995			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;996997		let sender = T::CrossAccountId::from_sub(sender);998		if topmost_owner == sender {999			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1000				.map_err(Self::map_unique_err_to_proxy)?;1001		} else {1002			<PalletNft<T>>::set_scoped_token_property(1003				resource_collection_id,1004				resource_id,1005				PropertyScope::Rmrk,1006				Self::rmrk_property(PendingResourceRemoval, &true)?,1007			)?;1008		}10091010		Ok(())1011	}10121013	fn change_collection_owner(1014		collection_id: CollectionId,1015		collection_type: misc::CollectionType,1016		sender: T::AccountId,1017		new_owner: T::AccountId,1018	) -> DispatchResult {1019		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1020		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10211022		let mut collection = collection.into_inner();10231024		collection.owner = new_owner;1025		collection.save()1026	}10271028	fn check_collection_owner(1029		collection: &NonfungibleHandle<T>,1030		account: &T::CrossAccountId,1031	) -> DispatchResult {1032		collection1033			.check_is_owner(account)1034			.map_err(Self::map_unique_err_to_proxy)1035	}10361037	pub fn last_collection_idx() -> RmrkCollectionId {1038		<CollectionIndex<T>>::get()1039	}10401041	pub fn unique_collection_id(1042		rmrk_collection_id: RmrkCollectionId,1043	) -> Result<CollectionId, DispatchError> {1044		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1045			.map_err(|_| <Error<T>>::CollectionUnknown.into())1046	}10471048	pub fn rmrk_collection_id(1049		unique_collection_id: CollectionId,1050	) -> Result<RmrkCollectionId, DispatchError> {1051		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1052			.map_err(|_| <Error<T>>::CollectionUnknown.into())1053	}10541055	pub fn get_nft_collection(1056		collection_id: CollectionId,1057	) -> Result<NonfungibleHandle<T>, DispatchError> {1058		let collection = <CollectionHandle<T>>::try_get(collection_id)1059			.map_err(|_| <Error<T>>::CollectionUnknown)?;10601061		match collection.mode {1062			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1063			_ => Err(<Error<T>>::CollectionUnknown.into()),1064		}1065	}10661067	pub fn collection_exists(collection_id: CollectionId) -> bool {1068		<CollectionHandle<T>>::try_get(collection_id).is_ok()1069	}10701071	pub fn get_collection_property(1072		collection_id: CollectionId,1073		key: RmrkProperty,1074	) -> Result<PropertyValue, DispatchError> {1075		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1076			.get(&Self::rmrk_property_key(key)?)1077			.ok_or(<Error<T>>::CollectionUnknown)?1078			.clone();10791080		Ok(collection_property)1081	}10821083	pub fn get_collection_property_decoded<V: Decode>(1084		collection_id: CollectionId,1085		key: RmrkProperty,1086	) -> Result<V, DispatchError> {1087		Self::decode_property(Self::get_collection_property(collection_id, key)?)1088	}10891090	pub fn get_collection_type(1091		collection_id: CollectionId,1092	) -> Result<misc::CollectionType, DispatchError> {1093		Self::get_collection_property_decoded(collection_id, CollectionType)1094			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1095	}10961097	pub fn ensure_collection_type(1098		collection_id: CollectionId,1099		collection_type: misc::CollectionType,1100	) -> DispatchResult {1101		let actual_type = Self::get_collection_type(collection_id)?;1102		ensure!(1103			actual_type == collection_type,1104			<CommonError<T>>::NoPermission1105		);11061107		Ok(())1108	}11091110	pub fn get_typed_nft_collection(1111		collection_id: CollectionId,1112		collection_type: misc::CollectionType,1113	) -> Result<NonfungibleHandle<T>, DispatchError> {1114		Self::ensure_collection_type(collection_id, collection_type)?;11151116		Self::get_nft_collection(collection_id)1117	}11181119	pub fn get_typed_nft_collection_mapped(1120		rmrk_collection_id: RmrkCollectionId,1121		collection_type: misc::CollectionType,1122	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1123		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11241125		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11261127		Ok((collection, unique_collection_id))1128	}11291130	pub fn get_nft_property(1131		collection_id: CollectionId,1132		nft_id: TokenId,1133		key: RmrkProperty,1134	) -> Result<PropertyValue, DispatchError> {1135		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1136			.get(&Self::rmrk_property_key(key)?)1137			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1138			.clone();11391140		Ok(nft_property)1141	}11421143	pub fn get_nft_property_decoded<V: Decode>(1144		collection_id: CollectionId,1145		nft_id: TokenId,1146		key: RmrkProperty,1147	) -> Result<V, DispatchError> {1148		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1149	}11501151	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1152		<TokenData<T>>::contains_key((collection_id, nft_id))1153	}11541155	pub fn get_nft_type(1156		collection_id: CollectionId,1157		token_id: TokenId,1158	) -> Result<NftType, DispatchError> {1159		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1160			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1161	}11621163	pub fn ensure_nft_type(1164		collection_id: CollectionId,1165		token_id: TokenId,1166		nft_type: NftType,1167	) -> DispatchResult {1168		let actual_type = Self::get_nft_type(collection_id, token_id)?;1169		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11701171		Ok(())1172	}11731174	pub fn ensure_nft_owner(1175		collection_id: CollectionId,1176		token_id: TokenId,1177		possible_owner: &T::CrossAccountId,1178		nesting_budget: &dyn budget::Budget,1179	) -> DispatchResult {1180		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1181			possible_owner.clone(),1182			collection_id,1183			token_id,1184			None,1185			nesting_budget,1186		)?;11871188		ensure!(is_owned, <Error<T>>::NoPermission);11891190		Ok(())1191	}11921193	pub fn filter_user_properties<Key, Value, R, Mapper>(1194		collection_id: CollectionId,1195		token_id: Option<TokenId>,1196		filter_keys: Option<Vec<RmrkPropertyKey>>,1197		mapper: Mapper,1198	) -> Result<Vec<R>, DispatchError>1199	where1200		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1201		Value: Decode + Default,1202		Mapper: Fn(Key, Value) -> R,1203	{1204		filter_keys1205			.map(|keys| {1206				let properties = keys1207					.into_iter()1208					.filter_map(|key| {1209						let key: Key = key.try_into().ok()?;12101211						let value = match token_id {1212							Some(token_id) => Self::get_nft_property_decoded(1213								collection_id,1214								token_id,1215								UserProperty(key.as_ref()),1216							),1217							None => Self::get_collection_property_decoded(1218								collection_id,1219								UserProperty(key.as_ref()),1220							),1221						}1222						.ok()?;12231224						Some(mapper(key, value))1225					})1226					.collect();12271228				Ok(properties)1229			})1230			.unwrap_or_else(|| {1231				let properties =1232					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12331234				Ok(properties)1235			})1236	}12371238	pub fn iterate_user_properties<Key, Value, R, Mapper>(1239		collection_id: CollectionId,1240		token_id: Option<TokenId>,1241		mapper: Mapper,1242	) -> Result<impl Iterator<Item = R>, DispatchError>1243	where1244		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1245		Value: Decode + Default,1246		Mapper: Fn(Key, Value) -> R,1247	{1248		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12491250		let properties = match token_id {1251			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1252			None => <PalletCommon<T>>::collection_properties(collection_id),1253		};12541255		let properties = properties.into_iter().filter_map(move |(key, value)| {1256			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12571258			let key: Key = key.to_vec().try_into().ok()?;1259			let value: Value = value.decode().ok()?;12601261			Some(mapper(key, value))1262		});12631264		Ok(properties)1265	}12661267	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1268		map_unique_err_to_proxy! {1269			match err {1270				CommonError::NoPermission => NoPermission,1271				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1272				CommonError::PublicMintingNotAllowed => NoPermission,1273				CommonError::TokenNotFound => NoAvailableNftId,1274				CommonError::ApprovedValueTooLow => NoPermission,1275				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1276				StructureError::TokenNotFound => NoAvailableNftId,1277				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1278			}1279		}1280	}1281}
modifiedpallets/proxy-rmrk-equip/Cargo.tomldiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/Cargo.toml
+++ b/pallets/proxy-rmrk-equip/Cargo.toml
@@ -21,6 +21,7 @@
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.22" }
 frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
+rmrk-traits = { default-features = false, path = "../../primitives/rmrk-traits" }
 scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
 pallet-rmrk-core = { default-features = false, path = "../proxy-rmrk-core" }
 
@@ -32,6 +33,7 @@
     "sp-runtime/std",
     "sp-std/std",
     "up-data-structs/std",
+    "rmrk-traits/std",
     "pallet-common/std",
     "pallet-nonfungible/std",
     "pallet-rmrk-core/std",
modifiedprimitives/data-structs/Cargo.tomldiffbeforeafterboth
--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -26,6 +26,7 @@
 derivative = { version = "2.2.0", features = ["use_core"] }
 struct-versioning = { path = "../../crates/struct-versioning" }
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.22" }
+rmrk-traits = { default-features = false, path = "../rmrk-traits" }
 
 [features]
 default = ["std"]
@@ -39,7 +40,8 @@
   "sp-core/std",
   "sp-std/std",
   "pallet-evm/std",
+  "rmrk-traits/std",
 ]
 serde1 = ["serde/alloc"]
 limit-testing = []
-runtime-benchmarks = []
\ No newline at end of file
+runtime-benchmarks = []
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -36,14 +36,12 @@
 use derivative::Derivative;
 use scale_info::TypeInfo;
 
-pub mod rmrk;
-
 // RMRK
-use rmrk::{
+use rmrk_traits::{
 	CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme, ThemeProperty,
 	ResourceTypes, BasicResource, ComposableResource, SlotResource,
 };
-pub use rmrk::{
+pub use rmrk_traits::{
 	primitives::{
 		CollectionId as RmrkCollectionId, NftId as RmrkNftId, BaseId as RmrkBaseId,
 		PartId as RmrkPartId, ResourceId as RmrkResourceId,
deletedprimitives/data-structs/src/rmrk.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/rmrk.rs
+++ /dev/null
@@ -1,435 +0,0 @@
-use codec::{Decode, Encode, MaxEncodedLen};
-use scale_info::TypeInfo;
-
-#[cfg(feature = "std")]
-use serde::Serialize;
-
-use primitives::*;
-
-pub mod primitives {
-	pub type CollectionId = u32;
-	pub type ResourceId = u32;
-	pub type NftId = u32;
-	pub type BaseId = u32;
-	pub type SlotId = u32;
-	pub type PartId = u32;
-	pub type ZIndex = u32;
-}
-
-#[cfg(feature = "std")]
-mod serialize {
-	use core::convert::AsRef;
-	use serde::ser::{self, Serialize};
-
-	pub mod vec {
-		use super::*;
-
-		pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
-		where
-			D: ser::Serializer,
-			V: Serialize,
-			C: AsRef<[V]>,
-		{
-			value.as_ref().serialize(serializer)
-		}
-	}
-
-	pub mod opt_vec {
-		use super::*;
-
-		pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
-		where
-			D: ser::Serializer,
-			V: Serialize,
-			C: AsRef<[V]>,
-		{
-			match value {
-				Some(value) => super::vec::serialize(value, serializer),
-				None => serializer.serialize_none(),
-			}
-		}
-	}
-}
-
-/// Collection info.
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			AccountId: Serialize,
-			BoundedString: AsRef<[u8]>,
-			BoundedSymbol: AsRef<[u8]>
-		"#)
-)]
-pub struct CollectionInfo<BoundedString, BoundedSymbol, AccountId> {
-	/// Current bidder and bid price.
-	pub issuer: AccountId,
-
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub metadata: BoundedString,
-	pub max: Option<u32>,
-
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub symbol: BoundedSymbol,
-	pub nfts_count: u32,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-pub enum AccountIdOrCollectionNftTuple<AccountId> {
-	AccountId(AccountId),
-	CollectionAndNftTuple(CollectionId, NftId),
-}
-
-/// Royalty information (recipient and amount)
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-pub struct RoyaltyInfo<AccountId, RoyaltyAmount> {
-	/// Recipient (AccountId) of the royalty
-	pub recipient: AccountId,
-	/// Amount (Permill) of the royalty
-	pub amount: RoyaltyAmount,
-}
-
-/// Nft info.
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			AccountId: Serialize,
-			RoyaltyAmount: Serialize,
-			BoundedString: AsRef<[u8]>
-		"#)
-)]
-pub struct NftInfo<AccountId, RoyaltyAmount, BoundedString> {
-	/// The owner of the NFT, can be either an Account or a tuple (CollectionId, NftId)
-	pub owner: AccountIdOrCollectionNftTuple<AccountId>,
-	/// Royalty (optional)
-	pub royalty: Option<RoyaltyInfo<AccountId, RoyaltyAmount>>,
-
-	/// Arbitrary data about an instance, e.g. IPFS hash
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub metadata: BoundedString,
-
-	/// Equipped state
-	pub equipped: bool,
-	/// Pending state (if sent to NFT)
-	pub pending: bool,
-}
-
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
-pub struct NftChild {
-	pub collection_id: CollectionId,
-	pub nft_id: NftId,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, PartialEq, TypeInfo)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedKey: AsRef<[u8]>,
-			BoundedValue: AsRef<[u8]>
-		"#)
-)]
-pub struct PropertyInfo<BoundedKey, BoundedValue> {
-	/// Key of the property
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub key: BoundedKey,
-
-	/// Value of the property
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub value: BoundedValue,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct BasicResource<BoundedString> {
-	/// If the resource is Media, the base property is absent. Media src should be a URI like an
-	/// IPFS hash.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub src: Option<BoundedString>,
-
-	/// Reference to IPFS location of metadata
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub metadata: Option<BoundedString>,
-
-	/// Optional location or identier of license
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub license: Option<BoundedString>,
-
-	/// If the resource has the thumb property, this will be a URI to a thumbnail of the given
-	/// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
-	/// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
-	/// another bird, showing the full render of one bird inside the other's inventory might be a
-	/// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
-	/// image that is lighter and faster to load but representative of this resource.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub thumb: Option<BoundedString>,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedString: AsRef<[u8]>,
-			BoundedParts: AsRef<[PartId]>
-		"#)
-)]
-pub struct ComposableResource<BoundedString, BoundedParts> {
-	/// If a resource is composed, it will have an array of parts that compose it
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub parts: BoundedParts,
-
-	/// A Base is uniquely identified by the combination of the word `base`, its minting block
-	/// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
-	/// base-4477293-kanaria_superbird.
-	pub base: BaseId,
-
-	/// If the resource is Media, the base property is absent. Media src should be a URI like an
-	/// IPFS hash.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub src: Option<BoundedString>,
-
-	/// Reference to IPFS location of metadata
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub metadata: Option<BoundedString>,
-
-	/// If the resource has the slot property, it was designed to fit into a specific Base's slot.
-	/// The baseslot will be composed of two dot-delimited values, like so:
-	/// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
-	/// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
-
-	/// Optional location or identier of license
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub license: Option<BoundedString>,
-
-	/// If the resource has the thumb property, this will be a URI to a thumbnail of the given
-	/// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
-	/// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
-	/// another bird, showing the full render of one bird inside the other's inventory might be a
-	/// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
-	/// image that is lighter and faster to load but representative of this resource.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub thumb: Option<BoundedString>,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct SlotResource<BoundedString> {
-	/// A Base is uniquely identified by the combination of the word `base`, its minting block
-	/// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
-	/// base-4477293-kanaria_superbird.
-	pub base: BaseId,
-
-	/// If the resource is Media, the base property is absent. Media src should be a URI like an
-	/// IPFS hash.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub src: Option<BoundedString>,
-
-	/// Reference to IPFS location of metadata
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub metadata: Option<BoundedString>,
-
-	/// If the resource has the slot property, it was designed to fit into a specific Base's slot.
-	/// The baseslot will be composed of two dot-delimited values, like so:
-	/// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
-	/// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
-	pub slot: SlotId,
-
-	/// The license field, if present, should contain a link to a license (IPFS or static HTTP
-	/// url), or an identifier, like RMRK_nocopy or ipfs://ipfs/someHashOfLicense.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub license: Option<BoundedString>,
-
-	/// If the resource has the thumb property, this will be a URI to a thumbnail of the given
-	/// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
-	/// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
-	/// another bird, showing the full render of one bird inside the other's inventory might be a
-	/// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
-	/// image that is lighter and faster to load but representative of this resource.
-	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
-	pub thumb: Option<BoundedString>,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedString: AsRef<[u8]>,
-			BoundedParts: AsRef<[PartId]>
-		"#)
-)]
-pub enum ResourceTypes<BoundedString, BoundedParts> {
-	Basic(BasicResource<BoundedString>),
-	Composable(ComposableResource<BoundedString, BoundedParts>),
-	Slot(SlotResource<BoundedString>),
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedString: AsRef<[u8]>,
-			BoundedParts: AsRef<[PartId]>
-		"#)
-)]
-pub struct ResourceInfo<BoundedString, BoundedParts> {
-	/// id is a 5-character string of reasonable uniqueness.
-	/// The combination of base ID and resource id should be unique across the entire RMRK
-	/// ecosystem which
-	//#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub id: ResourceId,
-
-	/// Resource
-	pub resource: ResourceTypes<BoundedString, BoundedParts>,
-
-	/// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted
-	pub pending: bool,
-
-	/// If resource removal request is sent by non-rootowned NFT, pending will be true and need to be accepted
-	pub pending_removal: bool,
-}
-
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			AccountId: Serialize,
-			BoundedString: AsRef<[u8]>
-		"#)
-)]
-pub struct BaseInfo<AccountId, BoundedString> {
-	/// Original creator of the Base
-	pub issuer: AccountId,
-
-	/// Specifies how an NFT should be rendered, ie "svg"
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub base_type: BoundedString,
-
-	/// User provided symbol during Base creation
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub symbol: BoundedString,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct FixedPart<BoundedString> {
-	pub id: PartId,
-	pub z: ZIndex,
-
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub src: BoundedString,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = "BoundedCollectionList: AsRef<[CollectionId]>")
-)]
-pub enum EquippableList<BoundedCollectionList> {
-	All,
-	Empty,
-	Custom(#[cfg_attr(feature = "std", serde(with = "serialize::vec"))] BoundedCollectionList),
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedString: AsRef<[u8]>,
-			BoundedCollectionList: AsRef<[CollectionId]>
-		"#)
-)]
-pub struct SlotPart<BoundedString, BoundedCollectionList> {
-	pub id: PartId,
-	pub equippable: EquippableList<BoundedCollectionList>,
-
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub src: BoundedString,
-
-	pub z: ZIndex,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedString: AsRef<[u8]>,
-			BoundedCollectionList: AsRef<[CollectionId]>
-		"#)
-)]
-pub enum PartType<BoundedString, BoundedCollectionList> {
-	FixedPart(FixedPart<BoundedString>),
-	SlotPart(SlotPart<BoundedString, BoundedCollectionList>),
-}
-
-impl<BoundedString, BoundedCollectionList> PartType<BoundedString, BoundedCollectionList> {
-	pub fn id(&self) -> PartId {
-		match self {
-			Self::FixedPart(part) => part.id,
-			Self::SlotPart(part) => part.id,
-		}
-	}
-
-	pub fn src(&self) -> &BoundedString {
-		match self {
-			Self::FixedPart(part) => &part.src,
-			Self::SlotPart(part) => &part.src,
-		}
-	}
-
-	pub fn z_index(&self) -> ZIndex {
-		match self {
-			Self::FixedPart(part) => part.z,
-			Self::SlotPart(part) => part.z,
-		}
-	}
-}
-
-#[cfg_attr(feature = "std", derive(Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
-#[cfg_attr(
-	feature = "std",
-	serde(bound = r#"
-			BoundedString: AsRef<[u8]>,
-			PropertyList: AsRef<[ThemeProperty<BoundedString>]>,
-		"#)
-)]
-pub struct Theme<BoundedString, PropertyList> {
-	/// Name of the theme
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub name: BoundedString,
-
-	/// Theme properties
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub properties: PropertyList,
-	/// Inheritability
-	pub inherit: bool,
-}
-
-#[cfg_attr(feature = "std", derive(Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct ThemeProperty<BoundedString> {
-	/// Key of the property
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub key: BoundedString,
-
-	/// Value of the property
-	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
-	pub value: BoundedString,
-}
modifiedprimitives/rmrk-rpc/Cargo.tomldiffbeforeafterboth
--- a/primitives/rmrk-rpc/Cargo.toml
+++ b/primitives/rmrk-rpc/Cargo.toml
@@ -11,7 +11,7 @@
 sp-api = { default-features = false, git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.22' }
 sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.22' }
 serde = { version = "1.0.130", default-features = false, features = ["derive"] }
-up-data-structs = { default-features = false, path = '../data-structs' }
+rmrk-traits = { default-features = false, path = "../rmrk-traits" }
 
 [features]
 default = ["std"]
@@ -22,5 +22,5 @@
 	"sp-api/std",
 	"sp-runtime/std",
 	"serde/std",
-	"up-data-structs/std",
+	"rmrk-traits/std",
 ]
modifiedprimitives/rmrk-rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rmrk-rpc/src/lib.rs
+++ b/primitives/rmrk-rpc/src/lib.rs
@@ -3,7 +3,7 @@
 use sp_api::{Encode, Decode};
 use sp_std::vec::Vec;
 use sp_runtime::DispatchError;
-use up_data_structs::rmrk::{primitives::*, NftChild};
+use rmrk_traits::{primitives::*, NftChild};
 
 pub type Result<T> = core::result::Result<T, DispatchError>;
 
addedprimitives/rmrk-traits/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "rmrk-traits"
+authors = ["Unique Network <support@uniquenetwork.io>"]
+description = "RMRK proxy data structs definitions"
+edition = "2021"
+license = 'GPLv3'
+homepage = "https://unique.network"
+repository = 'https://github.com/UniqueNetwork/unique-chain'
+version = '0.1.0'
+
+[dependencies]
+scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
+codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive"] }
+serde = { version = "1.0.130", features = ["derive"], default-features = false, optional = true }
+
+[features]
+default = ["std"]
+std = [
+  "serde1",
+  "serde/std",
+  "codec/std",
+]
+serde1 = ["serde/alloc"]
addedprimitives/rmrk-traits/src/base.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/base.rs
@@ -0,0 +1,30 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			AccountId: Serialize,
+			BoundedString: AsRef<[u8]>
+		"#)
+)]
+pub struct BaseInfo<AccountId, BoundedString> {
+	/// Original creator of the Base
+	pub issuer: AccountId,
+
+	/// Specifies how an NFT should be rendered, ie "svg"
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub base_type: BoundedString,
+
+	/// User provided symbol during Base creation
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub symbol: BoundedString,
+}
addedprimitives/rmrk-traits/src/collection.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/collection.rs
@@ -0,0 +1,31 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			AccountId: Serialize,
+			BoundedString: AsRef<[u8]>,
+			BoundedSymbol: AsRef<[u8]>
+		"#)
+)]
+pub struct CollectionInfo<BoundedString, BoundedSymbol, AccountId> {
+	/// Current bidder and bid price.
+	pub issuer: AccountId,
+
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub metadata: BoundedString,
+	pub max: Option<u32>,
+
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub symbol: BoundedSymbol,
+	pub nfts_count: u32,
+}
addedprimitives/rmrk-traits/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/lib.rs
@@ -0,0 +1,31 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod base;
+pub mod collection;
+pub mod nft;
+pub mod part;
+pub mod property;
+pub mod resource;
+pub mod theme;
+
+#[cfg(feature = "std")]
+mod serialize;
+
+pub use base::BaseInfo;
+pub use part::{EquippableList, FixedPart, PartType, SlotPart};
+pub use theme::{Theme, ThemeProperty};
+pub use collection::CollectionInfo;
+pub use nft::{AccountIdOrCollectionNftTuple, NftInfo, RoyaltyInfo, NftChild};
+pub use property::PropertyInfo;
+pub use resource::{
+	BasicResource, ComposableResource, ResourceInfo, ResourceTypes, SlotResource,
+};
+pub mod primitives {
+	pub type CollectionId = u32;
+	pub type ResourceId = u32;
+	pub type NftId = u32;
+	pub type BaseId = u32;
+	pub type SlotId = u32;
+	pub type PartId = u32;
+	pub type ZIndex = u32;
+}
addedprimitives/rmrk-traits/src/nft.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/nft.rs
@@ -0,0 +1,61 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+use crate::primitives::*;
+
+#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+pub enum AccountIdOrCollectionNftTuple<AccountId> {
+	AccountId(AccountId),
+	CollectionAndNftTuple(CollectionId, NftId),
+}
+
+/// Royalty information (recipient and amount)
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+pub struct RoyaltyInfo<AccountId, RoyaltyAmount> {
+	/// Recipient (AccountId) of the royalty
+	pub recipient: AccountId,
+	/// Amount (Permill) of the royalty
+	pub amount: RoyaltyAmount,
+}
+
+/// Nft info.
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			AccountId: Serialize,
+			RoyaltyAmount: Serialize,
+			BoundedString: AsRef<[u8]>
+		"#)
+)]
+pub struct NftInfo<AccountId, RoyaltyAmount, BoundedString> {
+	/// The owner of the NFT, can be either an Account or a tuple (CollectionId, NftId)
+	pub owner: AccountIdOrCollectionNftTuple<AccountId>,
+	/// Royalty (optional)
+	pub royalty: Option<RoyaltyInfo<AccountId, RoyaltyAmount>>,
+
+	/// Arbitrary data about an instance, e.g. IPFS hash
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub metadata: BoundedString,
+
+	/// Equipped state
+	pub equipped: bool,
+	/// Pending state (if sent to NFT)
+	pub pending: bool,
+}
+
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
+pub struct NftChild {
+	pub collection_id: CollectionId,
+	pub nft_id: NftId,
+}
addedprimitives/rmrk-traits/src/part.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/part.rs
@@ -0,0 +1,89 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+use crate::primitives::*;
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct FixedPart<BoundedString> {
+	pub id: PartId,
+	pub z: ZIndex,
+
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub src: BoundedString,
+}
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = "BoundedCollectionList: AsRef<[CollectionId]>")
+)]
+pub enum EquippableList<BoundedCollectionList> {
+	All,
+	Empty,
+	Custom(#[cfg_attr(feature = "std", serde(with = "serialize::vec"))] BoundedCollectionList),
+}
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedString: AsRef<[u8]>,
+			BoundedCollectionList: AsRef<[CollectionId]>
+		"#)
+)]
+pub struct SlotPart<BoundedString, BoundedCollectionList> {
+	pub id: PartId,
+	pub equippable: EquippableList<BoundedCollectionList>,
+
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub src: BoundedString,
+
+	pub z: ZIndex,
+}
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedString: AsRef<[u8]>,
+			BoundedCollectionList: AsRef<[CollectionId]>
+		"#)
+)]
+pub enum PartType<BoundedString, BoundedCollectionList> {
+	FixedPart(FixedPart<BoundedString>),
+	SlotPart(SlotPart<BoundedString, BoundedCollectionList>),
+}
+
+impl<BoundedString, BoundedCollectionList> PartType<BoundedString, BoundedCollectionList> {
+	pub fn id(&self) -> PartId {
+		match self {
+			Self::FixedPart(part) => part.id,
+			Self::SlotPart(part) => part.id,
+		}
+	}
+
+	pub fn src(&self) -> &BoundedString {
+		match self {
+			Self::FixedPart(part) => &part.src,
+			Self::SlotPart(part) => &part.src,
+		}
+	}
+
+	pub fn z_index(&self) -> ZIndex {
+		match self {
+			Self::FixedPart(part) => part.z,
+			Self::SlotPart(part) => part.z,
+		}
+	}
+}
addedprimitives/rmrk-traits/src/property.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/property.rs
@@ -0,0 +1,27 @@
+use codec::{Decode, Encode};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, PartialEq, TypeInfo)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedKey: AsRef<[u8]>,
+			BoundedValue: AsRef<[u8]>
+		"#)
+)]
+pub struct PropertyInfo<BoundedKey, BoundedValue> {
+	/// Key of the property
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub key: BoundedKey,
+
+	/// Value of the property
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub value: BoundedValue,
+}
addedprimitives/rmrk-traits/src/resource.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/resource.rs
@@ -0,0 +1,164 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+use crate::primitives::*;
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct BasicResource<BoundedString> {
+	/// If the resource is Media, the base property is absent. Media src should be a URI like an
+	/// IPFS hash.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub src: Option<BoundedString>,
+
+	/// Reference to IPFS location of metadata
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub metadata: Option<BoundedString>,
+
+	/// Optional location or identier of license
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub license: Option<BoundedString>,
+
+	/// If the resource has the thumb property, this will be a URI to a thumbnail of the given
+	/// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
+	/// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
+	/// another bird, showing the full render of one bird inside the other's inventory might be a
+	/// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
+	/// image that is lighter and faster to load but representative of this resource.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub thumb: Option<BoundedString>,
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedString: AsRef<[u8]>,
+			BoundedParts: AsRef<[PartId]>
+		"#)
+)]
+pub struct ComposableResource<BoundedString, BoundedParts> {
+	/// If a resource is composed, it will have an array of parts that compose it
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub parts: BoundedParts,
+
+	/// A Base is uniquely identified by the combination of the word `base`, its minting block
+	/// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
+	/// base-4477293-kanaria_superbird.
+	pub base: BaseId,
+
+	/// If the resource is Media, the base property is absent. Media src should be a URI like an
+	/// IPFS hash.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub src: Option<BoundedString>,
+
+	/// Reference to IPFS location of metadata
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub metadata: Option<BoundedString>,
+
+	/// If the resource has the slot property, it was designed to fit into a specific Base's slot.
+	/// The baseslot will be composed of two dot-delimited values, like so:
+	/// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
+	/// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
+
+	/// Optional location or identier of license
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub license: Option<BoundedString>,
+
+	/// If the resource has the thumb property, this will be a URI to a thumbnail of the given
+	/// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
+	/// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
+	/// another bird, showing the full render of one bird inside the other's inventory might be a
+	/// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
+	/// image that is lighter and faster to load but representative of this resource.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub thumb: Option<BoundedString>,
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct SlotResource<BoundedString> {
+	/// A Base is uniquely identified by the combination of the word `base`, its minting block
+	/// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
+	/// base-4477293-kanaria_superbird.
+	pub base: BaseId,
+
+	/// If the resource is Media, the base property is absent. Media src should be a URI like an
+	/// IPFS hash.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub src: Option<BoundedString>,
+
+	/// Reference to IPFS location of metadata
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub metadata: Option<BoundedString>,
+
+	/// If the resource has the slot property, it was designed to fit into a specific Base's slot.
+	/// The baseslot will be composed of two dot-delimited values, like so:
+	/// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
+	/// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
+	pub slot: SlotId,
+
+	/// The license field, if present, should contain a link to a license (IPFS or static HTTP
+	/// url), or an identifier, like RMRK_nocopy or ipfs://ipfs/someHashOfLicense.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub license: Option<BoundedString>,
+
+	/// If the resource has the thumb property, this will be a URI to a thumbnail of the given
+	/// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
+	/// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
+	/// another bird, showing the full render of one bird inside the other's inventory might be a
+	/// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
+	/// image that is lighter and faster to load but representative of this resource.
+	#[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+	pub thumb: Option<BoundedString>,
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedString: AsRef<[u8]>,
+			BoundedParts: AsRef<[PartId]>
+		"#)
+)]
+pub enum ResourceTypes<BoundedString, BoundedParts> {
+	Basic(BasicResource<BoundedString>),
+	Composable(ComposableResource<BoundedString, BoundedParts>),
+	Slot(SlotResource<BoundedString>),
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedString: AsRef<[u8]>,
+			BoundedParts: AsRef<[PartId]>
+		"#)
+)]
+pub struct ResourceInfo<BoundedString, BoundedParts> {
+	/// id is a 5-character string of reasonable uniqueness.
+	/// The combination of base ID and resource id should be unique across the entire RMRK
+	/// ecosystem which
+	//#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub id: ResourceId,
+
+	/// Resource
+	pub resource: ResourceTypes<BoundedString, BoundedParts>,
+
+	/// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted
+	pub pending: bool,
+
+	/// If resource removal request is sent by non-rootowned NFT, pending will be true and need to be accepted
+	pub pending_removal: bool,
+}
addedprimitives/rmrk-traits/src/serialize.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/serialize.rs
@@ -0,0 +1,31 @@
+use core::convert::AsRef;
+use serde::ser::{self, Serialize};
+
+pub mod vec {
+    use super::*;
+
+    pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
+    where
+        D: ser::Serializer,
+        V: Serialize,
+        C: AsRef<[V]>,
+    {
+        value.as_ref().serialize(serializer)
+    }
+}
+
+pub mod opt_vec {
+    use super::*;
+
+    pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
+    where
+        D: ser::Serializer,
+        V: Serialize,
+        C: AsRef<[V]>,
+    {
+        match value {
+            Some(value) => super::vec::serialize(value, serializer),
+            None => serializer.serialize_none(),
+        }
+    }
+}
addedprimitives/rmrk-traits/src/theme.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/rmrk-traits/src/theme.rs
@@ -0,0 +1,42 @@
+use codec::{Decode, Encode};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
+#[cfg_attr(
+	feature = "std",
+	serde(bound = r#"
+			BoundedString: AsRef<[u8]>,
+			PropertyList: AsRef<[ThemeProperty<BoundedString>]>,
+		"#)
+)]
+pub struct Theme<BoundedString, PropertyList> {
+	/// Name of the theme
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub name: BoundedString,
+
+	/// Theme properties
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub properties: PropertyList,
+	/// Inheritability
+	pub inherit: bool,
+}
+
+#[cfg_attr(feature = "std", derive(Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct ThemeProperty<BoundedString> {
+	/// Key of the property
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub key: BoundedString,
+
+	/// Value of the property
+	#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+	pub value: BoundedString,
+}