git.delta.rocks / unique-network / refs/commits / 960c1089697d

difftreelog

refactor move RMRK rpc impl to proxy pallets

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

6 files changed

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::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748pub const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52	use super::*;53	use pallet_evm::account;5455	#[pallet::config]56	pub trait Config:57		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58	{59		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60		type WeightInfo: WeightInfo;61	}6263	#[pallet::storage]64	#[pallet::getter(fn collection_index)]65	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667	#[pallet::storage]68	pub type UniqueCollectionId<T: Config> =69		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071	#[pallet::pallet]72	#[pallet::generate_store(pub(super) trait Store)]73	pub struct Pallet<T>(_);7475	#[pallet::event]76	#[pallet::generate_deposit(pub(super) fn deposit_event)]77	pub enum Event<T: Config> {78		CollectionCreated {79			issuer: T::AccountId,80			collection_id: RmrkCollectionId,81		},82		CollectionDestroyed {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		IssuerChanged {87			old_issuer: T::AccountId,88			new_issuer: T::AccountId,89			collection_id: RmrkCollectionId,90		},91		CollectionLocked {92			issuer: T::AccountId,93			collection_id: RmrkCollectionId,94		},95		NftMinted {96			owner: T::AccountId,97			collection_id: RmrkCollectionId,98			nft_id: RmrkNftId,99		},100		NFTBurned {101			owner: T::AccountId,102			nft_id: RmrkNftId,103		},104		NFTSent {105			sender: T::AccountId,106			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,107			collection_id: RmrkCollectionId,108			nft_id: RmrkNftId,109			approval_required: bool,110		},111		NFTAccepted {112			sender: T::AccountId,113			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,114			collection_id: RmrkCollectionId,115			nft_id: RmrkNftId,116		},117		NFTRejected {118			sender: T::AccountId,119			collection_id: RmrkCollectionId,120			nft_id: RmrkNftId,121		},122		PropertySet {123			collection_id: RmrkCollectionId,124			maybe_nft_id: Option<RmrkNftId>,125			key: RmrkKeyString,126			value: RmrkValueString,127		},128		ResourceAdded {129			nft_id: RmrkNftId,130			resource_id: RmrkResourceId,131		},132		ResourceRemoval {133			nft_id: RmrkNftId,134			resource_id: RmrkResourceId,135		},136		ResourceAccepted {137			nft_id: RmrkNftId,138			resource_id: RmrkResourceId,139		},140		ResourceRemovalAccepted {141			nft_id: RmrkNftId,142			resource_id: RmrkResourceId,143		},144		PrioritySet {145			collection_id: RmrkCollectionId,146			nft_id: RmrkNftId,147		},148	}149150	#[pallet::error]151	pub enum Error<T> {152		/* Unique-specific events */153		CorruptedCollectionType,154		NftTypeEncodeError,155		RmrkPropertyKeyIsTooLong,156		RmrkPropertyValueIsTooLong,157		RmrkPropertyIsNotFound,158		UnableToDecodeRmrkData,159160		/* RMRK compatible events */161		CollectionNotEmpty,162		NoAvailableCollectionId,163		NoAvailableNftId,164		CollectionUnknown,165		NoPermission,166		NonTransferable,167		CollectionFullOrLocked,168		ResourceDoesntExist,169		CannotSendToDescendentOrSelf,170		CannotAcceptNonOwnedNft,171		CannotRejectNonOwnedNft,172		CannotRejectNonPendingNft,173		ResourceNotPending,174		NoAvailableResourceId,175	}176177	#[pallet::call]178	impl<T: Config> Pallet<T> {179		/// Create a collection180		#[transactional]181		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]182		pub fn create_collection(183			origin: OriginFor<T>,184			metadata: RmrkString,185			max: Option<u32>,186			symbol: RmrkCollectionSymbol,187		) -> DispatchResult {188			let sender = ensure_signed(origin)?;189190			let limits = CollectionLimits {191				owner_can_transfer: Some(false),192				token_limit: max,193				..Default::default()194			};195196			let data = CreateCollectionData {197				limits: Some(limits),198				token_prefix: symbol199					.into_inner()200					.try_into()201					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202				permissions: Some(CollectionPermissions {203					nesting: Some(NestingPermissions {204						token_owner: true,205						collection_admin: false,206						restricted: None,207208						permissive: false,209					}),210					..Default::default()211				}),212				..Default::default()213			};214215			let unique_collection_id = Self::init_collection(216				T::CrossAccountId::from_sub(sender.clone()),217				data,218				[219					Self::rmrk_property(Metadata, &metadata)?,220					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221				]222				.into_iter(),223			)?;224			let rmrk_collection_id = <CollectionIndex<T>>::get();225226			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227228			<PalletCommon<T>>::set_scoped_collection_property(229				unique_collection_id,230				PropertyScope::Rmrk,231				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,232			)?;233234			<CollectionIndex<T>>::mutate(|n| *n += 1);235236			Self::deposit_event(Event::CollectionCreated {237				issuer: sender,238				collection_id: rmrk_collection_id,239			});240241			Ok(())242		}243244		/// destroy collection245		#[transactional]246		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]247		pub fn destroy_collection(248			origin: OriginFor<T>,249			collection_id: RmrkCollectionId,250		) -> DispatchResult {251			let sender = ensure_signed(origin)?;252			let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254			let collection = Self::get_typed_nft_collection(255				Self::unique_collection_id(collection_id)?,256				misc::CollectionType::Regular,257			)?;258			collection.check_is_external()?;259260			<PalletNft<T>>::destroy_collection(collection, &cross_sender)261				.map_err(Self::map_unique_err_to_proxy)?;262263			Self::deposit_event(Event::CollectionDestroyed {264				issuer: sender,265				collection_id,266			});267268			Ok(())269		}270271		/// Change the issuer of a collection272		///273		/// Parameters:274		/// - `origin`: sender of the transaction275		/// - `collection_id`: collection id of the nft to change issuer of276		/// - `new_issuer`: Collection's new issuer277		#[transactional]278		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]279		pub fn change_collection_issuer(280			origin: OriginFor<T>,281			collection_id: RmrkCollectionId,282			new_issuer: <T::Lookup as StaticLookup>::Source,283		) -> DispatchResult {284			let sender = ensure_signed(origin)?;285286			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;287			collection.check_is_external()?;288289			let new_issuer = T::Lookup::lookup(new_issuer)?;290291			Self::change_collection_owner(292				Self::unique_collection_id(collection_id)?,293				misc::CollectionType::Regular,294				sender.clone(),295				new_issuer.clone(),296			)?;297298			Self::deposit_event(Event::IssuerChanged {299				old_issuer: sender,300				new_issuer,301				collection_id,302			});303304			Ok(())305		}306307		/// lock collection308		#[transactional]309		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]310		pub fn lock_collection(311			origin: OriginFor<T>,312			collection_id: RmrkCollectionId,313		) -> DispatchResult {314			let sender = ensure_signed(origin)?;315			let cross_sender = T::CrossAccountId::from_sub(sender.clone());316317			let collection = Self::get_typed_nft_collection(318				Self::unique_collection_id(collection_id)?,319				misc::CollectionType::Regular,320			)?;321			collection.check_is_external()?;322323			Self::check_collection_owner(&collection, &cross_sender)?;324325			let token_count = collection.total_supply();326327			let mut collection = collection.into_inner();328			collection.limits.token_limit = Some(token_count);329			collection.save()?;330331			Self::deposit_event(Event::CollectionLocked {332				issuer: sender,333				collection_id,334			});335336			Ok(())337		}338339		/// Mints an NFT in the specified collection340		/// Sets metadata and the royalty attribute341		///342		/// Parameters:343		/// - `collection_id`: The class of the asset to be minted.344		/// - `nft_id`: The nft value of the asset to be minted.345		/// - `recipient`: Receiver of the royalty346		/// - `royalty`: Permillage reward from each trade for the Recipient347		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash348		/// - `transferable`: Ability to transfer this NFT349		#[transactional]350		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]351		pub fn mint_nft(352			origin: OriginFor<T>,353			owner: Option<T::AccountId>,354			collection_id: RmrkCollectionId,355			recipient: Option<T::AccountId>,356			royalty_amount: Option<Permill>,357			metadata: RmrkString,358			transferable: bool,359			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,360		) -> DispatchResult {361			let sender = ensure_signed(origin)?;362			let cross_sender = T::CrossAccountId::from_sub(sender.clone());363364			let owner = owner.unwrap_or(sender.clone());365			let cross_owner = T::CrossAccountId::from_sub(owner.clone());366367			let collection = Self::get_typed_nft_collection(368				Self::unique_collection_id(collection_id)?,369				misc::CollectionType::Regular,370			)?;371			collection.check_is_external()?;372373			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {374				recipient: recipient.unwrap_or_else(|| owner.clone()),375				amount,376			});377378			let nft_id = Self::create_nft(379				&cross_sender,380				&cross_owner,381				&collection,382				[383					Self::rmrk_property(TokenType, &NftType::Regular)?,384					Self::rmrk_property(Transferable, &transferable)?,385					Self::rmrk_property(PendingNftAccept, &false)?,386					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,387					Self::rmrk_property(Metadata, &metadata)?,388					Self::rmrk_property(Equipped, &false)?,389					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,390					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,391				]392				.into_iter(),393			)394			.map_err(|err| match err {395				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),396				err => Self::map_unique_err_to_proxy(err),397			})?;398399			if let Some(resources) = resources {400				for resource in resources {401					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;402				}403			}404405			Self::deposit_event(Event::NftMinted {406				owner,407				collection_id,408				nft_id: nft_id.0,409			});410411			Ok(())412		}413414		/// burn nft415		#[transactional]416		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]417		pub fn burn_nft(418			origin: OriginFor<T>,419			collection_id: RmrkCollectionId,420			nft_id: RmrkNftId,421			max_burns: u32,422		) -> DispatchResult {423			let sender = ensure_signed(origin)?;424			let cross_sender = T::CrossAccountId::from_sub(sender.clone());425426			let collection = Self::get_typed_nft_collection(427				Self::unique_collection_id(collection_id)?,428				misc::CollectionType::Regular,429			)?;430			collection.check_is_external()?;431432			Self::destroy_nft(433				cross_sender,434				Self::unique_collection_id(collection_id)?,435				nft_id.into(),436				max_burns,437				<Error<T>>::NoPermission,438			)439			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;440441			Self::deposit_event(Event::NFTBurned {442				owner: sender,443				nft_id,444			});445446			Ok(())447		}448449		/// Transfers a NFT from an Account or NFT A to another Account or NFT B450		///451		/// Parameters:452		/// - `origin`: sender of the transaction453		/// - `rmrk_collection_id`: collection id of the nft to be transferred454		/// - `rmrk_nft_id`: nft id of the nft to be transferred455		/// - `new_owner`: new owner of the nft which can be either an account or a NFT456		#[transactional]457		#[pallet::weight(<SelfWeightOf<T>>::send())]458		pub fn send(459			origin: OriginFor<T>,460			rmrk_collection_id: RmrkCollectionId,461			rmrk_nft_id: RmrkNftId,462			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,463		) -> DispatchResult {464			let sender = ensure_signed(origin.clone())?;465			let cross_sender = T::CrossAccountId::from_sub(sender.clone());466467			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;468			let nft_id = rmrk_nft_id.into();469470			let collection =471				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;472			collection.check_is_external()?;473474			let token_data =475				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;476477			let from = token_data.owner;478479			ensure!(480				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,481				<Error<T>>::NonTransferable482			);483484			ensure!(485				!Self::get_nft_property_decoded(486					collection_id,487					nft_id,488					RmrkProperty::PendingNftAccept489				)?,490				<Error<T>>::NoPermission491			);492493			let target_owner;494			let approval_required;495496			match new_owner {497				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {498					target_owner = T::CrossAccountId::from_sub(account_id.clone());499					approval_required = false;500				}501				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(502					target_collection_id,503					target_nft_id,504				) => {505					let target_collection_id = Self::unique_collection_id(target_collection_id)?;506507					let target_nft_budget = budget::Value::new(NESTING_BUDGET);508509					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(510						target_collection_id,511						target_nft_id.into(),512						Some((collection_id, nft_id)),513						&target_nft_budget,514					)515					.map_err(Self::map_unique_err_to_proxy)?;516517					approval_required = cross_sender != target_nft_owner;518519					if approval_required {520						target_owner = target_nft_owner;521522						<PalletNft<T>>::set_scoped_token_property(523							collection.id,524							nft_id,525							PropertyScope::Rmrk,526							Self::rmrk_property(PendingNftAccept, &approval_required)?,527						)?;528					} else {529						target_owner = T::CrossTokenAddressMapping::token_to_address(530							target_collection_id,531							target_nft_id.into(),532						);533					}534				}535			}536537			let src_nft_budget = budget::Value::new(NESTING_BUDGET);538539			<PalletNft<T>>::transfer_from(540				&collection,541				&cross_sender,542				&from,543				&target_owner,544				nft_id,545				&src_nft_budget,546			)547			.map_err(Self::map_unique_err_to_proxy)?;548549			Self::deposit_event(Event::NFTSent {550				sender,551				recipient: new_owner,552				collection_id: rmrk_collection_id,553				nft_id: rmrk_nft_id,554				approval_required,555			});556557			Ok(())558		}559560		/// Accepts an NFT sent from another account to self or owned NFT561		///562		/// Parameters:563		/// - `origin`: sender of the transaction564		/// - `rmrk_collection_id`: collection id of the nft to be accepted565		/// - `rmrk_nft_id`: nft id of the nft to be accepted566		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was567		///   sent to568		#[transactional]569		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]570		pub fn accept_nft(571			origin: OriginFor<T>,572			rmrk_collection_id: RmrkCollectionId,573			rmrk_nft_id: RmrkNftId,574			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,575		) -> DispatchResult {576			let sender = ensure_signed(origin.clone())?;577			let cross_sender = T::CrossAccountId::from_sub(sender.clone());578579			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;580			let nft_id = rmrk_nft_id.into();581582			let collection =583				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;584			collection.check_is_external()?;585586			let new_cross_owner = match new_owner {587				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {588					T::CrossAccountId::from_sub(account_id.clone())589				}590				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(591					target_collection_id,592					target_nft_id,593				) => {594					let target_collection_id = Self::unique_collection_id(target_collection_id)?;595596					T::CrossTokenAddressMapping::token_to_address(597						target_collection_id,598						TokenId(target_nft_id),599					)600				}601			};602603			let budget = budget::Value::new(NESTING_BUDGET);604605			<PalletNft<T>>::transfer(606				&collection,607				&cross_sender,608				&new_cross_owner,609				nft_id,610				&budget,611			)612			.map_err(|err| {613				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {614					<Error<T>>::CannotAcceptNonOwnedNft.into()615				} else {616					Self::map_unique_err_to_proxy(err)617				}618			})?;619620			<PalletNft<T>>::set_scoped_token_property(621				collection.id,622				nft_id,623				PropertyScope::Rmrk,624				Self::rmrk_property(PendingNftAccept, &false)?,625			)?;626627			Self::deposit_event(Event::NFTAccepted {628				sender,629				recipient: new_owner,630				collection_id: rmrk_collection_id,631				nft_id: rmrk_nft_id,632			});633634			Ok(())635		}636637		/// Rejects an NFT sent from another account to self or owned NFT638		///639		/// Parameters:640		/// - `origin`: sender of the transaction641		/// - `rmrk_collection_id`: collection id of the nft to be accepted642		/// - `rmrk_nft_id`: nft id of the nft to be accepted643		#[transactional]644		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]645		pub fn reject_nft(646			origin: OriginFor<T>,647			rmrk_collection_id: RmrkCollectionId,648			rmrk_nft_id: RmrkNftId,649		) -> DispatchResult {650			let sender = ensure_signed(origin)?;651			let cross_sender = T::CrossAccountId::from_sub(sender.clone());652653			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654			let nft_id = rmrk_nft_id.into();655656			let collection =657				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;658			collection.check_is_external()?;659660			ensure!(661				<TokenData<T>>::get((collection_id, nft_id)).is_some(),662				<Error<T>>::NoAvailableNftId663			);664665			ensure!(666				Self::get_nft_property_decoded(667					collection_id,668					nft_id,669					RmrkProperty::PendingNftAccept670				)?,671				<Error<T>>::CannotRejectNonPendingNft672			);673674			Self::destroy_nft(675				cross_sender,676				collection_id,677				nft_id,678				NESTING_BUDGET,679				<Error<T>>::CannotRejectNonOwnedNft,680			)681			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;682683			Self::deposit_event(Event::NFTRejected {684				sender,685				collection_id: rmrk_collection_id,686				nft_id: rmrk_nft_id,687			});688689			Ok(())690		}691692		/// accept the addition of a new resource to an existing NFT693		#[transactional]694		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]695		pub fn accept_resource(696			origin: OriginFor<T>,697			rmrk_collection_id: RmrkCollectionId,698			rmrk_nft_id: RmrkNftId,699			resource_id: RmrkResourceId,700		) -> DispatchResult {701			let sender = ensure_signed(origin)?;702			let cross_sender = T::CrossAccountId::from_sub(sender);703704			let collection_id = Self::unique_collection_id(rmrk_collection_id)705				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;706			let collection =707				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;708			collection.check_is_external()?;709710			let nft_id = rmrk_nft_id.into();711712			let budget = budget::Value::new(NESTING_BUDGET);713714			let nft_owner =715				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)716					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;717718			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {719				ensure!(res.pending, <Error<T>>::ResourceNotPending);720				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);721722				res.pending = false;723724				Ok(())725			})?;726727			Self::deposit_event(Event::<T>::ResourceAccepted {728				nft_id: rmrk_nft_id,729				resource_id,730			});731732			Ok(())733		}734735		/// accept the removal of a resource of an existing NFT736		#[transactional]737		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]738		pub fn accept_resource_removal(739			origin: OriginFor<T>,740			rmrk_collection_id: RmrkCollectionId,741			rmrk_nft_id: RmrkNftId,742			resource_id: RmrkResourceId,743		) -> DispatchResult {744			let sender = ensure_signed(origin)?;745			let cross_sender = T::CrossAccountId::from_sub(sender);746747			let collection_id = Self::unique_collection_id(rmrk_collection_id)748				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;749			let collection =750				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;751			collection.check_is_external()?;752753			let nft_id = rmrk_nft_id.into();754755			let budget = budget::Value::new(NESTING_BUDGET);756757			let nft_owner =758				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)759					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;760761			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);762763			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;764765			let resource_info = <PalletNft<T>>::token_aux_property((766				collection_id,767				nft_id,768				PropertyScope::Rmrk,769				resource_id_key.clone(),770			))771			.ok_or(<Error<T>>::ResourceDoesntExist)?;772773			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;774775			ensure!(776				resource_info.pending_removal,777				<Error<T>>::ResourceNotPending778			);779780			<PalletNft<T>>::remove_token_aux_property(781				collection_id,782				nft_id,783				PropertyScope::Rmrk,784				resource_id_key,785			);786787			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {788				nft_id: rmrk_nft_id,789				resource_id,790			});791792			Ok(())793		}794795		/// set a custom value on an NFT796		#[transactional]797		#[pallet::weight(<SelfWeightOf<T>>::set_property())]798		pub fn set_property(799			origin: OriginFor<T>,800			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,801			maybe_nft_id: Option<RmrkNftId>,802			key: RmrkKeyString,803			value: RmrkValueString,804		) -> DispatchResult {805			let sender = ensure_signed(origin)?;806			let sender = T::CrossAccountId::from_sub(sender);807808			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;809			let collection =810				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;811			collection.check_is_external()?;812813			let budget = budget::Value::new(NESTING_BUDGET);814815			match maybe_nft_id {816				Some(nft_id) => {817					let token_id: TokenId = nft_id.into();818819					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;820					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;821822					<PalletNft<T>>::set_scoped_token_property(823						collection_id,824						token_id,825						PropertyScope::Rmrk,826						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,827					)?;828				}829				None => {830					let collection = Self::get_typed_nft_collection(831						collection_id,832						misc::CollectionType::Regular,833					)?;834835					Self::check_collection_owner(&collection, &sender)?;836837					<PalletCommon<T>>::set_scoped_collection_property(838						collection_id,839						PropertyScope::Rmrk,840						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,841					)?;842				}843			}844845			Self::deposit_event(Event::PropertySet {846				collection_id: rmrk_collection_id,847				maybe_nft_id,848				key,849				value,850			});851852			Ok(())853		}854855		/// set a different order of resource priority856		#[transactional]857		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]858		pub fn set_priority(859			origin: OriginFor<T>,860			rmrk_collection_id: RmrkCollectionId,861			rmrk_nft_id: RmrkNftId,862			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,863		) -> DispatchResult {864			let sender = ensure_signed(origin)?;865			let sender = T::CrossAccountId::from_sub(sender);866867			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;868			let nft_id = rmrk_nft_id.into();869870			let collection =871				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;872			collection.check_is_external()?;873874			let budget = budget::Value::new(NESTING_BUDGET);875876			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;877			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;878879			<PalletNft<T>>::set_scoped_token_property(880				collection_id,881				nft_id,882				PropertyScope::Rmrk,883				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,884			)?;885886			Self::deposit_event(Event::<T>::PrioritySet {887				collection_id: rmrk_collection_id,888				nft_id: rmrk_nft_id,889			});890891			Ok(())892		}893894		/// Create basic resource895		#[transactional]896		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]897		pub fn add_basic_resource(898			origin: OriginFor<T>,899			rmrk_collection_id: RmrkCollectionId,900			nft_id: RmrkNftId,901			resource: RmrkBasicResource,902		) -> DispatchResult {903			let sender = ensure_signed(origin.clone())?;904905			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;906			let collection =907				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;908			collection.check_is_external()?;909910			let resource_id = Self::resource_add(911				sender,912				collection_id,913				nft_id.into(),914				RmrkResourceTypes::Basic(resource),915			)?;916917			Self::deposit_event(Event::ResourceAdded {918				nft_id,919				resource_id,920			});921			Ok(())922		}923924		/// Create composable resource925		#[transactional]926		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]927		pub fn add_composable_resource(928			origin: OriginFor<T>,929			rmrk_collection_id: RmrkCollectionId,930			nft_id: RmrkNftId,931			resource: RmrkComposableResource,932		) -> DispatchResult {933			let sender = ensure_signed(origin.clone())?;934935			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;936			let collection =937				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;938			collection.check_is_external()?;939940			let resource_id = Self::resource_add(941				sender,942				collection_id,943				nft_id.into(),944				RmrkResourceTypes::Composable(resource),945			)?;946947			Self::deposit_event(Event::ResourceAdded {948				nft_id,949				resource_id,950			});951			Ok(())952		}953954		/// Create slot resource955		#[transactional]956		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]957		pub fn add_slot_resource(958			origin: OriginFor<T>,959			rmrk_collection_id: RmrkCollectionId,960			nft_id: RmrkNftId,961			resource: RmrkSlotResource,962		) -> DispatchResult {963			let sender = ensure_signed(origin.clone())?;964965			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;966			let collection =967				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;968			collection.check_is_external()?;969970			let resource_id = Self::resource_add(971				sender,972				collection_id,973				nft_id.into(),974				RmrkResourceTypes::Slot(resource),975			)?;976977			Self::deposit_event(Event::ResourceAdded {978				nft_id,979				resource_id,980			});981			Ok(())982		}983984		/// remove resource985		#[transactional]986		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]987		pub fn remove_resource(988			origin: OriginFor<T>,989			rmrk_collection_id: RmrkCollectionId,990			nft_id: RmrkNftId,991			resource_id: RmrkResourceId,992		) -> DispatchResult {993			let sender = ensure_signed(origin.clone())?;994995			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;996			let collection =997				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;998			collection.check_is_external()?;9991000			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10011002			Self::deposit_event(Event::ResourceRemoval {1003				nft_id,1004				resource_id,1005			});1006			Ok(())1007		}1008	}1009}10101011impl<T: Config> Pallet<T> {1012	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1013		let key = rmrk_key.to_key::<T>()?;10141015		let scoped_key = PropertyScope::Rmrk1016			.apply(key)1017			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10181019		Ok(scoped_key)1020	}10211022	// todo think about renaming these1023	pub fn rmrk_property<E: Encode>(1024		rmrk_key: RmrkProperty,1025		value: &E,1026	) -> Result<Property, DispatchError> {1027		let key = rmrk_key.to_key::<T>()?;10281029		let value = Self::encode_property(value)?;10301031		let property = Property { key, value };10321033		Ok(property)1034	}10351036	pub fn encode_property<E: Encode, S: Get<u32>>(1037		value: &E,1038	) -> Result<BoundedBytes<S>, DispatchError> {1039		let value = value1040			.encode()1041			.try_into()1042			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10431044		Ok(value)1045	}10461047	pub fn decode_property<D: Decode, S: Get<u32>>(1048		vec: &BoundedBytes<S>,1049	) -> Result<D, DispatchError> {1050		vec.decode()1051			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1052	}10531054	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1055	where1056		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1057	{1058		vec.rebind()1059			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1060	}10611062	fn init_collection(1063		sender: T::CrossAccountId,1064		data: CreateCollectionData<T::AccountId>,1065		properties: impl Iterator<Item = Property>,1066	) -> Result<CollectionId, DispatchError> {1067		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10681069		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1070			return Err(<Error<T>>::NoAvailableCollectionId.into());1071		}10721073		<PalletCommon<T>>::set_scoped_collection_properties(1074			collection_id?,1075			PropertyScope::Rmrk,1076			properties,1077		)?;10781079		collection_id1080	}10811082	pub fn create_nft(1083		sender: &T::CrossAccountId,1084		owner: &T::CrossAccountId,1085		collection: &NonfungibleHandle<T>,1086		properties: impl Iterator<Item = Property>,1087	) -> Result<TokenId, DispatchError> {1088		let data = CreateNftExData {1089			properties: BoundedVec::default(),1090			owner: owner.clone(),1091		};10921093		let budget = budget::Value::new(NESTING_BUDGET);10941095		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10961097		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10981099		<PalletNft<T>>::set_scoped_token_properties(1100			collection.id,1101			nft_id,1102			PropertyScope::Rmrk,1103			properties,1104		)?;11051106		Ok(nft_id)1107	}11081109	fn destroy_nft(1110		sender: T::CrossAccountId,1111		collection_id: CollectionId,1112		token_id: TokenId,1113		max_burns: u32,1114		error_if_not_owned: Error<T>,1115	) -> DispatchResultWithPostInfo {1116		let collection =1117			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11181119		let token_data =1120			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11211122		let from = token_data.owner;11231124		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11251126		ensure!(1127			<PalletStructure<T>>::check_indirectly_owned(1128				sender.clone(),1129				collection_id,1130				token_id,1131				None,1132				&owner_check_budget1133			)?,1134			error_if_not_owned,1135		);11361137		let burns_budget = budget::Value::new(max_burns);1138		let breadth_budget = budget::Value::new(max_burns);11391140		<PalletNft<T>>::burn_recursively(1141			&collection,1142			&from,1143			token_id,1144			&burns_budget,1145			&breadth_budget,1146		)1147	}11481149	fn acquire_next_resource_id(1150		collection_id: CollectionId,1151		nft_id: TokenId,1152	) -> Result<RmrkResourceId, DispatchError> {1153		let resource_id: RmrkResourceId =1154			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11551156		let next_id = resource_id1157			.checked_add(1)1158			.ok_or(<Error<T>>::NoAvailableResourceId)?;11591160		<PalletNft<T>>::set_scoped_token_property(1161			collection_id,1162			nft_id,1163			PropertyScope::Rmrk,1164			Self::rmrk_property(NextResourceId, &next_id)?,1165		)?;11661167		Ok(resource_id)1168	}11691170	fn resource_add(1171		sender: T::AccountId,1172		collection_id: CollectionId,1173		nft_id: TokenId,1174		resource: RmrkResourceTypes,1175	) -> Result<RmrkResourceId, DispatchError> {1176		let collection =1177			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1178		ensure!(collection.owner == sender, Error::<T>::NoPermission);11791180		let sender = T::CrossAccountId::from_sub(sender);1181		let budget = budget::Value::new(NESTING_BUDGET);11821183		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1184			.map_err(Self::map_unique_err_to_proxy)?;11851186		let pending = sender != nft_owner;11871188		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11891190		let resource_info = RmrkResourceInfo {1191			id,1192			resource,1193			pending,1194			pending_removal: false,1195		};11961197		<PalletNft<T>>::try_mutate_token_aux_property(1198			collection_id,1199			nft_id,1200			PropertyScope::Rmrk,1201			Self::rmrk_property_key(ResourceId(id))?,1202			|value| -> DispatchResult {1203				*value = Some(Self::encode_property(&resource_info)?);12041205				Ok(())1206			},1207		)?;12081209		Ok(id)1210	}12111212	fn resource_remove(1213		sender: T::AccountId,1214		collection_id: CollectionId,1215		nft_id: TokenId,1216		resource_id: RmrkResourceId,1217	) -> DispatchResult {1218		let collection =1219			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1220		ensure!(collection.owner == sender, Error::<T>::NoPermission);12211222		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1223		let scope = PropertyScope::Rmrk;12241225		ensure!(1226			<PalletNft<T>>::token_aux_property((1227				collection_id,1228				nft_id,1229				scope,1230				resource_id_key.clone()1231			))1232			.is_some(),1233			<Error<T>>::ResourceDoesntExist1234		);12351236		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1237		let topmost_owner =1238			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12391240		let sender = T::CrossAccountId::from_sub(sender);1241		if topmost_owner == sender {1242			<PalletNft<T>>::remove_token_aux_property(1243				collection_id,1244				nft_id,1245				PropertyScope::Rmrk,1246				Self::rmrk_property_key(ResourceId(resource_id))?,1247			);1248		} else {1249			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1250				res.pending_removal = true;12511252				Ok(())1253			})?;1254		}12551256		Ok(())1257	}12581259	fn try_mutate_resource_info(1260		collection_id: CollectionId,1261		nft_id: TokenId,1262		resource_id: RmrkResourceId,1263		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1264	) -> DispatchResult {1265		<PalletNft<T>>::try_mutate_token_aux_property(1266			collection_id,1267			nft_id,1268			PropertyScope::Rmrk,1269			Self::rmrk_property_key(ResourceId(resource_id))?,1270			|value| match value {1271				Some(value) => {1272					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12731274					f(&mut resource_info)?;12751276					*value = Self::encode_property(&resource_info)?;12771278					Ok(())1279				}1280				None => Err(<Error<T>>::ResourceDoesntExist.into()),1281			},1282		)1283	}12841285	fn change_collection_owner(1286		collection_id: CollectionId,1287		collection_type: misc::CollectionType,1288		sender: T::AccountId,1289		new_owner: T::AccountId,1290	) -> DispatchResult {1291		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1292		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12931294		let mut collection = collection.into_inner();12951296		collection.owner = new_owner;1297		collection.save()1298	}12991300	fn check_collection_owner(1301		collection: &NonfungibleHandle<T>,1302		account: &T::CrossAccountId,1303	) -> DispatchResult {1304		collection1305			.check_is_owner(account)1306			.map_err(Self::map_unique_err_to_proxy)1307	}13081309	pub fn last_collection_idx() -> RmrkCollectionId {1310		<CollectionIndex<T>>::get()1311	}13121313	pub fn unique_collection_id(1314		rmrk_collection_id: RmrkCollectionId,1315	) -> Result<CollectionId, DispatchError> {1316		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1317			.map_err(|_| <Error<T>>::CollectionUnknown.into())1318	}13191320	pub fn rmrk_collection_id(1321		unique_collection_id: CollectionId,1322	) -> Result<RmrkCollectionId, DispatchError> {1323		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1324	}13251326	pub fn get_nft_collection(1327		collection_id: CollectionId,1328	) -> Result<NonfungibleHandle<T>, DispatchError> {1329		let collection = <CollectionHandle<T>>::try_get(collection_id)1330			.map_err(|_| <Error<T>>::CollectionUnknown)?;13311332		match collection.mode {1333			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1334			_ => Err(<Error<T>>::CollectionUnknown.into()),1335		}1336	}13371338	pub fn collection_exists(collection_id: CollectionId) -> bool {1339		<CollectionHandle<T>>::try_get(collection_id).is_ok()1340	}13411342	pub fn get_collection_property(1343		collection_id: CollectionId,1344		key: RmrkProperty,1345	) -> Result<PropertyValue, DispatchError> {1346		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1347			.get(&Self::rmrk_property_key(key)?)1348			.ok_or(<Error<T>>::CollectionUnknown)?1349			.clone();13501351		Ok(collection_property)1352	}13531354	pub fn get_collection_property_decoded<V: Decode>(1355		collection_id: CollectionId,1356		key: RmrkProperty,1357	) -> Result<V, DispatchError> {1358		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1359	}13601361	pub fn get_collection_type(1362		collection_id: CollectionId,1363	) -> Result<misc::CollectionType, DispatchError> {1364		Self::get_collection_property_decoded(collection_id, CollectionType)1365			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1366	}13671368	pub fn ensure_collection_type(1369		collection_id: CollectionId,1370		collection_type: misc::CollectionType,1371	) -> DispatchResult {1372		let actual_type = Self::get_collection_type(collection_id)?;1373		ensure!(1374			actual_type == collection_type,1375			<CommonError<T>>::NoPermission1376		);13771378		Ok(())1379	}13801381	pub fn get_typed_nft_collection(1382		collection_id: CollectionId,1383		collection_type: misc::CollectionType,1384	) -> Result<NonfungibleHandle<T>, DispatchError> {1385		Self::ensure_collection_type(collection_id, collection_type)?;13861387		Self::get_nft_collection(collection_id)1388	}13891390	pub fn get_typed_nft_collection_mapped(1391		rmrk_collection_id: RmrkCollectionId,1392		collection_type: misc::CollectionType,1393	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1394		let unique_collection_id = match collection_type {1395			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1396			_ => rmrk_collection_id.into(),1397		};13981399		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14001401		Ok((collection, unique_collection_id))1402	}14031404	pub fn get_nft_property(1405		collection_id: CollectionId,1406		nft_id: TokenId,1407		key: RmrkProperty,1408	) -> Result<PropertyValue, DispatchError> {1409		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1410			.get(&Self::rmrk_property_key(key)?)1411			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1412			.clone();14131414		Ok(nft_property)1415	}14161417	pub fn get_nft_property_decoded<V: Decode>(1418		collection_id: CollectionId,1419		nft_id: TokenId,1420		key: RmrkProperty,1421	) -> Result<V, DispatchError> {1422		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1423	}14241425	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1426		<TokenData<T>>::contains_key((collection_id, nft_id))1427	}14281429	pub fn get_nft_type(1430		collection_id: CollectionId,1431		token_id: TokenId,1432	) -> Result<NftType, DispatchError> {1433		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1434			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1435	}14361437	pub fn ensure_nft_type(1438		collection_id: CollectionId,1439		token_id: TokenId,1440		nft_type: NftType,1441	) -> DispatchResult {1442		let actual_type = Self::get_nft_type(collection_id, token_id)?;1443		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14441445		Ok(())1446	}14471448	pub fn ensure_nft_owner(1449		collection_id: CollectionId,1450		token_id: TokenId,1451		possible_owner: &T::CrossAccountId,1452		nesting_budget: &dyn budget::Budget,1453	) -> DispatchResult {1454		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1455			possible_owner.clone(),1456			collection_id,1457			token_id,1458			None,1459			nesting_budget,1460		)1461		.map_err(Self::map_unique_err_to_proxy)?;14621463		ensure!(is_owned, <Error<T>>::NoPermission);14641465		Ok(())1466	}14671468	pub fn filter_user_properties<Key, Value, R, Mapper>(1469		collection_id: CollectionId,1470		token_id: Option<TokenId>,1471		filter_keys: Option<Vec<RmrkPropertyKey>>,1472		mapper: Mapper,1473	) -> Result<Vec<R>, DispatchError>1474	where1475		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1476		Value: Decode + Default,1477		Mapper: Fn(Key, Value) -> R,1478	{1479		filter_keys1480			.map(|keys| {1481				let properties = keys1482					.into_iter()1483					.filter_map(|key| {1484						let key: Key = key.try_into().ok()?;14851486						let value = match token_id {1487							Some(token_id) => Self::get_nft_property_decoded(1488								collection_id,1489								token_id,1490								UserProperty(key.as_ref()),1491							),1492							None => Self::get_collection_property_decoded(1493								collection_id,1494								UserProperty(key.as_ref()),1495							),1496						}1497						.ok()?;14981499						Some(mapper(key, value))1500					})1501					.collect();15021503				Ok(properties)1504			})1505			.unwrap_or_else(|| {1506				let properties =1507					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15081509				Ok(properties)1510			})1511	}15121513	pub fn iterate_user_properties<Key, Value, R, Mapper>(1514		collection_id: CollectionId,1515		token_id: Option<TokenId>,1516		mapper: Mapper,1517	) -> Result<impl Iterator<Item = R>, DispatchError>1518	where1519		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1520		Value: Decode + Default,1521		Mapper: Fn(Key, Value) -> R,1522	{1523		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15241525		let properties = match token_id {1526			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1527			None => <PalletCommon<T>>::collection_properties(collection_id),1528		};15291530		let properties = properties.into_iter().filter_map(move |(key, value)| {1531			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15321533			let key: Key = key.to_vec().try_into().ok()?;1534			let value: Value = value.decode().ok()?;15351536			Some(mapper(key, value))1537		});15381539		Ok(properties)1540	}15411542	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1543		map_unique_err_to_proxy! {1544			match err {1545				CommonError::NoPermission => NoPermission,1546				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1547				CommonError::PublicMintingNotAllowed => NoPermission,1548				CommonError::TokenNotFound => NoAvailableNftId,1549				CommonError::ApprovedValueTooLow => NoPermission,1550				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1551				StructureError::TokenNotFound => NoAvailableNftId,1552				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1553			}1554		}1555	}1556}
after · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod rpc;39pub mod weights;4041pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4243use weights::WeightInfo;44use misc::*;45pub use property::*;4647use RmrkProperty::*;4849pub const NESTING_BUDGET: u32 = 5;5051#[frame_support::pallet]52pub mod pallet {53	use super::*;54	use pallet_evm::account;5556	#[pallet::config]57	pub trait Config:58		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config59	{60		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;61		type WeightInfo: WeightInfo;62	}6364	#[pallet::storage]65	#[pallet::getter(fn collection_index)]66	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6768	#[pallet::storage]69	pub type UniqueCollectionId<T: Config> =70		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7172	#[pallet::pallet]73	#[pallet::generate_store(pub(super) trait Store)]74	pub struct Pallet<T>(_);7576	#[pallet::event]77	#[pallet::generate_deposit(pub(super) fn deposit_event)]78	pub enum Event<T: Config> {79		CollectionCreated {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		CollectionDestroyed {84			issuer: T::AccountId,85			collection_id: RmrkCollectionId,86		},87		IssuerChanged {88			old_issuer: T::AccountId,89			new_issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		CollectionLocked {93			issuer: T::AccountId,94			collection_id: RmrkCollectionId,95		},96		NftMinted {97			owner: T::AccountId,98			collection_id: RmrkCollectionId,99			nft_id: RmrkNftId,100		},101		NFTBurned {102			owner: T::AccountId,103			nft_id: RmrkNftId,104		},105		NFTSent {106			sender: T::AccountId,107			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,108			collection_id: RmrkCollectionId,109			nft_id: RmrkNftId,110			approval_required: bool,111		},112		NFTAccepted {113			sender: T::AccountId,114			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,115			collection_id: RmrkCollectionId,116			nft_id: RmrkNftId,117		},118		NFTRejected {119			sender: T::AccountId,120			collection_id: RmrkCollectionId,121			nft_id: RmrkNftId,122		},123		PropertySet {124			collection_id: RmrkCollectionId,125			maybe_nft_id: Option<RmrkNftId>,126			key: RmrkKeyString,127			value: RmrkValueString,128		},129		ResourceAdded {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceRemoval {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141		ResourceRemovalAccepted {142			nft_id: RmrkNftId,143			resource_id: RmrkResourceId,144		},145		PrioritySet {146			collection_id: RmrkCollectionId,147			nft_id: RmrkNftId,148		},149	}150151	#[pallet::error]152	pub enum Error<T> {153		/* Unique-specific events */154		CorruptedCollectionType,155		NftTypeEncodeError,156		RmrkPropertyKeyIsTooLong,157		RmrkPropertyValueIsTooLong,158		RmrkPropertyIsNotFound,159		UnableToDecodeRmrkData,160161		/* RMRK compatible events */162		CollectionNotEmpty,163		NoAvailableCollectionId,164		NoAvailableNftId,165		CollectionUnknown,166		NoPermission,167		NonTransferable,168		CollectionFullOrLocked,169		ResourceDoesntExist,170		CannotSendToDescendentOrSelf,171		CannotAcceptNonOwnedNft,172		CannotRejectNonOwnedNft,173		CannotRejectNonPendingNft,174		ResourceNotPending,175		NoAvailableResourceId,176	}177178	#[pallet::call]179	impl<T: Config> Pallet<T> {180		/// Create a collection181		#[transactional]182		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]183		pub fn create_collection(184			origin: OriginFor<T>,185			metadata: RmrkString,186			max: Option<u32>,187			symbol: RmrkCollectionSymbol,188		) -> DispatchResult {189			let sender = ensure_signed(origin)?;190191			let limits = CollectionLimits {192				owner_can_transfer: Some(false),193				token_limit: max,194				..Default::default()195			};196197			let data = CreateCollectionData {198				limits: Some(limits),199				token_prefix: symbol200					.into_inner()201					.try_into()202					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,203				permissions: Some(CollectionPermissions {204					nesting: Some(NestingPermissions {205						token_owner: true,206						collection_admin: false,207						restricted: None,208209						permissive: false,210					}),211					..Default::default()212				}),213				..Default::default()214			};215216			let unique_collection_id = Self::init_collection(217				T::CrossAccountId::from_sub(sender.clone()),218				data,219				[220					Self::rmrk_property(Metadata, &metadata)?,221					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,222				]223				.into_iter(),224			)?;225			let rmrk_collection_id = <CollectionIndex<T>>::get();226227			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);228229			<PalletCommon<T>>::set_scoped_collection_property(230				unique_collection_id,231				PropertyScope::Rmrk,232				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,233			)?;234235			<CollectionIndex<T>>::mutate(|n| *n += 1);236237			Self::deposit_event(Event::CollectionCreated {238				issuer: sender,239				collection_id: rmrk_collection_id,240			});241242			Ok(())243		}244245		/// destroy collection246		#[transactional]247		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]248		pub fn destroy_collection(249			origin: OriginFor<T>,250			collection_id: RmrkCollectionId,251		) -> DispatchResult {252			let sender = ensure_signed(origin)?;253			let cross_sender = T::CrossAccountId::from_sub(sender.clone());254255			let collection = Self::get_typed_nft_collection(256				Self::unique_collection_id(collection_id)?,257				misc::CollectionType::Regular,258			)?;259			collection.check_is_external()?;260261			<PalletNft<T>>::destroy_collection(collection, &cross_sender)262				.map_err(Self::map_unique_err_to_proxy)?;263264			Self::deposit_event(Event::CollectionDestroyed {265				issuer: sender,266				collection_id,267			});268269			Ok(())270		}271272		/// Change the issuer of a collection273		///274		/// Parameters:275		/// - `origin`: sender of the transaction276		/// - `collection_id`: collection id of the nft to change issuer of277		/// - `new_issuer`: Collection's new issuer278		#[transactional]279		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]280		pub fn change_collection_issuer(281			origin: OriginFor<T>,282			collection_id: RmrkCollectionId,283			new_issuer: <T::Lookup as StaticLookup>::Source,284		) -> DispatchResult {285			let sender = ensure_signed(origin)?;286287			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;288			collection.check_is_external()?;289290			let new_issuer = T::Lookup::lookup(new_issuer)?;291292			Self::change_collection_owner(293				Self::unique_collection_id(collection_id)?,294				misc::CollectionType::Regular,295				sender.clone(),296				new_issuer.clone(),297			)?;298299			Self::deposit_event(Event::IssuerChanged {300				old_issuer: sender,301				new_issuer,302				collection_id,303			});304305			Ok(())306		}307308		/// lock collection309		#[transactional]310		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]311		pub fn lock_collection(312			origin: OriginFor<T>,313			collection_id: RmrkCollectionId,314		) -> DispatchResult {315			let sender = ensure_signed(origin)?;316			let cross_sender = T::CrossAccountId::from_sub(sender.clone());317318			let collection = Self::get_typed_nft_collection(319				Self::unique_collection_id(collection_id)?,320				misc::CollectionType::Regular,321			)?;322			collection.check_is_external()?;323324			Self::check_collection_owner(&collection, &cross_sender)?;325326			let token_count = collection.total_supply();327328			let mut collection = collection.into_inner();329			collection.limits.token_limit = Some(token_count);330			collection.save()?;331332			Self::deposit_event(Event::CollectionLocked {333				issuer: sender,334				collection_id,335			});336337			Ok(())338		}339340		/// Mints an NFT in the specified collection341		/// Sets metadata and the royalty attribute342		///343		/// Parameters:344		/// - `collection_id`: The class of the asset to be minted.345		/// - `nft_id`: The nft value of the asset to be minted.346		/// - `recipient`: Receiver of the royalty347		/// - `royalty`: Permillage reward from each trade for the Recipient348		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash349		/// - `transferable`: Ability to transfer this NFT350		#[transactional]351		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]352		pub fn mint_nft(353			origin: OriginFor<T>,354			owner: Option<T::AccountId>,355			collection_id: RmrkCollectionId,356			recipient: Option<T::AccountId>,357			royalty_amount: Option<Permill>,358			metadata: RmrkString,359			transferable: bool,360			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,361		) -> DispatchResult {362			let sender = ensure_signed(origin)?;363			let cross_sender = T::CrossAccountId::from_sub(sender.clone());364365			let owner = owner.unwrap_or(sender.clone());366			let cross_owner = T::CrossAccountId::from_sub(owner.clone());367368			let collection = Self::get_typed_nft_collection(369				Self::unique_collection_id(collection_id)?,370				misc::CollectionType::Regular,371			)?;372			collection.check_is_external()?;373374			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {375				recipient: recipient.unwrap_or_else(|| owner.clone()),376				amount,377			});378379			let nft_id = Self::create_nft(380				&cross_sender,381				&cross_owner,382				&collection,383				[384					Self::rmrk_property(TokenType, &NftType::Regular)?,385					Self::rmrk_property(Transferable, &transferable)?,386					Self::rmrk_property(PendingNftAccept, &false)?,387					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,388					Self::rmrk_property(Metadata, &metadata)?,389					Self::rmrk_property(Equipped, &false)?,390					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,391					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,392				]393				.into_iter(),394			)395			.map_err(|err| match err {396				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),397				err => Self::map_unique_err_to_proxy(err),398			})?;399400			if let Some(resources) = resources {401				for resource in resources {402					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;403				}404			}405406			Self::deposit_event(Event::NftMinted {407				owner,408				collection_id,409				nft_id: nft_id.0,410			});411412			Ok(())413		}414415		/// burn nft416		#[transactional]417		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]418		pub fn burn_nft(419			origin: OriginFor<T>,420			collection_id: RmrkCollectionId,421			nft_id: RmrkNftId,422			max_burns: u32,423		) -> DispatchResult {424			let sender = ensure_signed(origin)?;425			let cross_sender = T::CrossAccountId::from_sub(sender.clone());426427			let collection = Self::get_typed_nft_collection(428				Self::unique_collection_id(collection_id)?,429				misc::CollectionType::Regular,430			)?;431			collection.check_is_external()?;432433			Self::destroy_nft(434				cross_sender,435				Self::unique_collection_id(collection_id)?,436				nft_id.into(),437				max_burns,438				<Error<T>>::NoPermission,439			)440			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;441442			Self::deposit_event(Event::NFTBurned {443				owner: sender,444				nft_id,445			});446447			Ok(())448		}449450		/// Transfers a NFT from an Account or NFT A to another Account or NFT B451		///452		/// Parameters:453		/// - `origin`: sender of the transaction454		/// - `rmrk_collection_id`: collection id of the nft to be transferred455		/// - `rmrk_nft_id`: nft id of the nft to be transferred456		/// - `new_owner`: new owner of the nft which can be either an account or a NFT457		#[transactional]458		#[pallet::weight(<SelfWeightOf<T>>::send())]459		pub fn send(460			origin: OriginFor<T>,461			rmrk_collection_id: RmrkCollectionId,462			rmrk_nft_id: RmrkNftId,463			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,464		) -> DispatchResult {465			let sender = ensure_signed(origin.clone())?;466			let cross_sender = T::CrossAccountId::from_sub(sender.clone());467468			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469			let nft_id = rmrk_nft_id.into();470471			let collection =472				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;473			collection.check_is_external()?;474475			let token_data =476				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;477478			let from = token_data.owner;479480			ensure!(481				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,482				<Error<T>>::NonTransferable483			);484485			ensure!(486				!Self::get_nft_property_decoded(487					collection_id,488					nft_id,489					RmrkProperty::PendingNftAccept490				)?,491				<Error<T>>::NoPermission492			);493494			let target_owner;495			let approval_required;496497			match new_owner {498				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {499					target_owner = T::CrossAccountId::from_sub(account_id.clone());500					approval_required = false;501				}502				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(503					target_collection_id,504					target_nft_id,505				) => {506					let target_collection_id = Self::unique_collection_id(target_collection_id)?;507508					let target_nft_budget = budget::Value::new(NESTING_BUDGET);509510					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(511						target_collection_id,512						target_nft_id.into(),513						Some((collection_id, nft_id)),514						&target_nft_budget,515					)516					.map_err(Self::map_unique_err_to_proxy)?;517518					approval_required = cross_sender != target_nft_owner;519520					if approval_required {521						target_owner = target_nft_owner;522523						<PalletNft<T>>::set_scoped_token_property(524							collection.id,525							nft_id,526							PropertyScope::Rmrk,527							Self::rmrk_property(PendingNftAccept, &approval_required)?,528						)?;529					} else {530						target_owner = T::CrossTokenAddressMapping::token_to_address(531							target_collection_id,532							target_nft_id.into(),533						);534					}535				}536			}537538			let src_nft_budget = budget::Value::new(NESTING_BUDGET);539540			<PalletNft<T>>::transfer_from(541				&collection,542				&cross_sender,543				&from,544				&target_owner,545				nft_id,546				&src_nft_budget,547			)548			.map_err(Self::map_unique_err_to_proxy)?;549550			Self::deposit_event(Event::NFTSent {551				sender,552				recipient: new_owner,553				collection_id: rmrk_collection_id,554				nft_id: rmrk_nft_id,555				approval_required,556			});557558			Ok(())559		}560561		/// Accepts an NFT sent from another account to self or owned NFT562		///563		/// Parameters:564		/// - `origin`: sender of the transaction565		/// - `rmrk_collection_id`: collection id of the nft to be accepted566		/// - `rmrk_nft_id`: nft id of the nft to be accepted567		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was568		///   sent to569		#[transactional]570		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]571		pub fn accept_nft(572			origin: OriginFor<T>,573			rmrk_collection_id: RmrkCollectionId,574			rmrk_nft_id: RmrkNftId,575			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,576		) -> DispatchResult {577			let sender = ensure_signed(origin.clone())?;578			let cross_sender = T::CrossAccountId::from_sub(sender.clone());579580			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;581			let nft_id = rmrk_nft_id.into();582583			let collection =584				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;585			collection.check_is_external()?;586587			let new_cross_owner = match new_owner {588				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {589					T::CrossAccountId::from_sub(account_id.clone())590				}591				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(592					target_collection_id,593					target_nft_id,594				) => {595					let target_collection_id = Self::unique_collection_id(target_collection_id)?;596597					T::CrossTokenAddressMapping::token_to_address(598						target_collection_id,599						TokenId(target_nft_id),600					)601				}602			};603604			let budget = budget::Value::new(NESTING_BUDGET);605606			<PalletNft<T>>::transfer(607				&collection,608				&cross_sender,609				&new_cross_owner,610				nft_id,611				&budget,612			)613			.map_err(|err| {614				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {615					<Error<T>>::CannotAcceptNonOwnedNft.into()616				} else {617					Self::map_unique_err_to_proxy(err)618				}619			})?;620621			<PalletNft<T>>::set_scoped_token_property(622				collection.id,623				nft_id,624				PropertyScope::Rmrk,625				Self::rmrk_property(PendingNftAccept, &false)?,626			)?;627628			Self::deposit_event(Event::NFTAccepted {629				sender,630				recipient: new_owner,631				collection_id: rmrk_collection_id,632				nft_id: rmrk_nft_id,633			});634635			Ok(())636		}637638		/// Rejects an NFT sent from another account to self or owned NFT639		///640		/// Parameters:641		/// - `origin`: sender of the transaction642		/// - `rmrk_collection_id`: collection id of the nft to be accepted643		/// - `rmrk_nft_id`: nft id of the nft to be accepted644		#[transactional]645		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]646		pub fn reject_nft(647			origin: OriginFor<T>,648			rmrk_collection_id: RmrkCollectionId,649			rmrk_nft_id: RmrkNftId,650		) -> DispatchResult {651			let sender = ensure_signed(origin)?;652			let cross_sender = T::CrossAccountId::from_sub(sender.clone());653654			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;655			let nft_id = rmrk_nft_id.into();656657			let collection =658				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;659			collection.check_is_external()?;660661			ensure!(662				<TokenData<T>>::get((collection_id, nft_id)).is_some(),663				<Error<T>>::NoAvailableNftId664			);665666			ensure!(667				Self::get_nft_property_decoded(668					collection_id,669					nft_id,670					RmrkProperty::PendingNftAccept671				)?,672				<Error<T>>::CannotRejectNonPendingNft673			);674675			Self::destroy_nft(676				cross_sender,677				collection_id,678				nft_id,679				NESTING_BUDGET,680				<Error<T>>::CannotRejectNonOwnedNft,681			)682			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;683684			Self::deposit_event(Event::NFTRejected {685				sender,686				collection_id: rmrk_collection_id,687				nft_id: rmrk_nft_id,688			});689690			Ok(())691		}692693		/// accept the addition of a new resource to an existing NFT694		#[transactional]695		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]696		pub fn accept_resource(697			origin: OriginFor<T>,698			rmrk_collection_id: RmrkCollectionId,699			rmrk_nft_id: RmrkNftId,700			resource_id: RmrkResourceId,701		) -> DispatchResult {702			let sender = ensure_signed(origin)?;703			let cross_sender = T::CrossAccountId::from_sub(sender);704705			let collection_id = Self::unique_collection_id(rmrk_collection_id)706				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;707			let collection =708				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;709			collection.check_is_external()?;710711			let nft_id = rmrk_nft_id.into();712713			let budget = budget::Value::new(NESTING_BUDGET);714715			let nft_owner =716				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)717					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;718719			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {720				ensure!(res.pending, <Error<T>>::ResourceNotPending);721				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);722723				res.pending = false;724725				Ok(())726			})?;727728			Self::deposit_event(Event::<T>::ResourceAccepted {729				nft_id: rmrk_nft_id,730				resource_id,731			});732733			Ok(())734		}735736		/// accept the removal of a resource of an existing NFT737		#[transactional]738		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]739		pub fn accept_resource_removal(740			origin: OriginFor<T>,741			rmrk_collection_id: RmrkCollectionId,742			rmrk_nft_id: RmrkNftId,743			resource_id: RmrkResourceId,744		) -> DispatchResult {745			let sender = ensure_signed(origin)?;746			let cross_sender = T::CrossAccountId::from_sub(sender);747748			let collection_id = Self::unique_collection_id(rmrk_collection_id)749				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;750			let collection =751				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;752			collection.check_is_external()?;753754			let nft_id = rmrk_nft_id.into();755756			let budget = budget::Value::new(NESTING_BUDGET);757758			let nft_owner =759				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)760					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;761762			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);763764			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;765766			let resource_info = <PalletNft<T>>::token_aux_property((767				collection_id,768				nft_id,769				PropertyScope::Rmrk,770				resource_id_key.clone(),771			))772			.ok_or(<Error<T>>::ResourceDoesntExist)?;773774			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;775776			ensure!(777				resource_info.pending_removal,778				<Error<T>>::ResourceNotPending779			);780781			<PalletNft<T>>::remove_token_aux_property(782				collection_id,783				nft_id,784				PropertyScope::Rmrk,785				resource_id_key,786			);787788			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {789				nft_id: rmrk_nft_id,790				resource_id,791			});792793			Ok(())794		}795796		/// set a custom value on an NFT797		#[transactional]798		#[pallet::weight(<SelfWeightOf<T>>::set_property())]799		pub fn set_property(800			origin: OriginFor<T>,801			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,802			maybe_nft_id: Option<RmrkNftId>,803			key: RmrkKeyString,804			value: RmrkValueString,805		) -> DispatchResult {806			let sender = ensure_signed(origin)?;807			let sender = T::CrossAccountId::from_sub(sender);808809			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;810			let collection =811				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;812			collection.check_is_external()?;813814			let budget = budget::Value::new(NESTING_BUDGET);815816			match maybe_nft_id {817				Some(nft_id) => {818					let token_id: TokenId = nft_id.into();819820					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;821					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;822823					<PalletNft<T>>::set_scoped_token_property(824						collection_id,825						token_id,826						PropertyScope::Rmrk,827						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,828					)?;829				}830				None => {831					let collection = Self::get_typed_nft_collection(832						collection_id,833						misc::CollectionType::Regular,834					)?;835836					Self::check_collection_owner(&collection, &sender)?;837838					<PalletCommon<T>>::set_scoped_collection_property(839						collection_id,840						PropertyScope::Rmrk,841						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,842					)?;843				}844			}845846			Self::deposit_event(Event::PropertySet {847				collection_id: rmrk_collection_id,848				maybe_nft_id,849				key,850				value,851			});852853			Ok(())854		}855856		/// set a different order of resource priority857		#[transactional]858		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]859		pub fn set_priority(860			origin: OriginFor<T>,861			rmrk_collection_id: RmrkCollectionId,862			rmrk_nft_id: RmrkNftId,863			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,864		) -> DispatchResult {865			let sender = ensure_signed(origin)?;866			let sender = T::CrossAccountId::from_sub(sender);867868			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;869			let nft_id = rmrk_nft_id.into();870871			let collection =872				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;873			collection.check_is_external()?;874875			let budget = budget::Value::new(NESTING_BUDGET);876877			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;878			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;879880			<PalletNft<T>>::set_scoped_token_property(881				collection_id,882				nft_id,883				PropertyScope::Rmrk,884				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,885			)?;886887			Self::deposit_event(Event::<T>::PrioritySet {888				collection_id: rmrk_collection_id,889				nft_id: rmrk_nft_id,890			});891892			Ok(())893		}894895		/// Create basic resource896		#[transactional]897		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]898		pub fn add_basic_resource(899			origin: OriginFor<T>,900			rmrk_collection_id: RmrkCollectionId,901			nft_id: RmrkNftId,902			resource: RmrkBasicResource,903		) -> DispatchResult {904			let sender = ensure_signed(origin.clone())?;905906			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;907			let collection =908				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;909			collection.check_is_external()?;910911			let resource_id = Self::resource_add(912				sender,913				collection_id,914				nft_id.into(),915				RmrkResourceTypes::Basic(resource),916			)?;917918			Self::deposit_event(Event::ResourceAdded {919				nft_id,920				resource_id,921			});922			Ok(())923		}924925		/// Create composable resource926		#[transactional]927		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]928		pub fn add_composable_resource(929			origin: OriginFor<T>,930			rmrk_collection_id: RmrkCollectionId,931			nft_id: RmrkNftId,932			resource: RmrkComposableResource,933		) -> DispatchResult {934			let sender = ensure_signed(origin.clone())?;935936			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;937			let collection =938				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;939			collection.check_is_external()?;940941			let resource_id = Self::resource_add(942				sender,943				collection_id,944				nft_id.into(),945				RmrkResourceTypes::Composable(resource),946			)?;947948			Self::deposit_event(Event::ResourceAdded {949				nft_id,950				resource_id,951			});952			Ok(())953		}954955		/// Create slot resource956		#[transactional]957		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]958		pub fn add_slot_resource(959			origin: OriginFor<T>,960			rmrk_collection_id: RmrkCollectionId,961			nft_id: RmrkNftId,962			resource: RmrkSlotResource,963		) -> DispatchResult {964			let sender = ensure_signed(origin.clone())?;965966			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;967			let collection =968				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;969			collection.check_is_external()?;970971			let resource_id = Self::resource_add(972				sender,973				collection_id,974				nft_id.into(),975				RmrkResourceTypes::Slot(resource),976			)?;977978			Self::deposit_event(Event::ResourceAdded {979				nft_id,980				resource_id,981			});982			Ok(())983		}984985		/// remove resource986		#[transactional]987		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]988		pub fn remove_resource(989			origin: OriginFor<T>,990			rmrk_collection_id: RmrkCollectionId,991			nft_id: RmrkNftId,992			resource_id: RmrkResourceId,993		) -> DispatchResult {994			let sender = ensure_signed(origin.clone())?;995996			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;997			let collection =998				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;999			collection.check_is_external()?;10001001			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10021003			Self::deposit_event(Event::ResourceRemoval {1004				nft_id,1005				resource_id,1006			});1007			Ok(())1008		}1009	}1010}10111012impl<T: Config> Pallet<T> {1013	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1014		let key = rmrk_key.to_key::<T>()?;10151016		let scoped_key = PropertyScope::Rmrk1017			.apply(key)1018			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10191020		Ok(scoped_key)1021	}10221023	// todo think about renaming these1024	pub fn rmrk_property<E: Encode>(1025		rmrk_key: RmrkProperty,1026		value: &E,1027	) -> Result<Property, DispatchError> {1028		let key = rmrk_key.to_key::<T>()?;10291030		let value = Self::encode_property(value)?;10311032		let property = Property { key, value };10331034		Ok(property)1035	}10361037	pub fn encode_property<E: Encode, S: Get<u32>>(1038		value: &E,1039	) -> Result<BoundedBytes<S>, DispatchError> {1040		let value = value1041			.encode()1042			.try_into()1043			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10441045		Ok(value)1046	}10471048	pub fn decode_property<D: Decode, S: Get<u32>>(1049		vec: &BoundedBytes<S>,1050	) -> Result<D, DispatchError> {1051		vec.decode()1052			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1053	}10541055	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1056	where1057		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1058	{1059		vec.rebind()1060			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1061	}10621063	fn init_collection(1064		sender: T::CrossAccountId,1065		data: CreateCollectionData<T::AccountId>,1066		properties: impl Iterator<Item = Property>,1067	) -> Result<CollectionId, DispatchError> {1068		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10691070		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1071			return Err(<Error<T>>::NoAvailableCollectionId.into());1072		}10731074		<PalletCommon<T>>::set_scoped_collection_properties(1075			collection_id?,1076			PropertyScope::Rmrk,1077			properties,1078		)?;10791080		collection_id1081	}10821083	pub fn create_nft(1084		sender: &T::CrossAccountId,1085		owner: &T::CrossAccountId,1086		collection: &NonfungibleHandle<T>,1087		properties: impl Iterator<Item = Property>,1088	) -> Result<TokenId, DispatchError> {1089		let data = CreateNftExData {1090			properties: BoundedVec::default(),1091			owner: owner.clone(),1092		};10931094		let budget = budget::Value::new(NESTING_BUDGET);10951096		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10971098		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10991100		<PalletNft<T>>::set_scoped_token_properties(1101			collection.id,1102			nft_id,1103			PropertyScope::Rmrk,1104			properties,1105		)?;11061107		Ok(nft_id)1108	}11091110	fn destroy_nft(1111		sender: T::CrossAccountId,1112		collection_id: CollectionId,1113		token_id: TokenId,1114		max_burns: u32,1115		error_if_not_owned: Error<T>,1116	) -> DispatchResultWithPostInfo {1117		let collection =1118			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11191120		let token_data =1121			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11221123		let from = token_data.owner;11241125		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11261127		ensure!(1128			<PalletStructure<T>>::check_indirectly_owned(1129				sender.clone(),1130				collection_id,1131				token_id,1132				None,1133				&owner_check_budget1134			)?,1135			error_if_not_owned,1136		);11371138		let burns_budget = budget::Value::new(max_burns);1139		let breadth_budget = budget::Value::new(max_burns);11401141		<PalletNft<T>>::burn_recursively(1142			&collection,1143			&from,1144			token_id,1145			&burns_budget,1146			&breadth_budget,1147		)1148	}11491150	fn acquire_next_resource_id(1151		collection_id: CollectionId,1152		nft_id: TokenId,1153	) -> Result<RmrkResourceId, DispatchError> {1154		let resource_id: RmrkResourceId =1155			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11561157		let next_id = resource_id1158			.checked_add(1)1159			.ok_or(<Error<T>>::NoAvailableResourceId)?;11601161		<PalletNft<T>>::set_scoped_token_property(1162			collection_id,1163			nft_id,1164			PropertyScope::Rmrk,1165			Self::rmrk_property(NextResourceId, &next_id)?,1166		)?;11671168		Ok(resource_id)1169	}11701171	fn resource_add(1172		sender: T::AccountId,1173		collection_id: CollectionId,1174		nft_id: TokenId,1175		resource: RmrkResourceTypes,1176	) -> Result<RmrkResourceId, DispatchError> {1177		let collection =1178			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1179		ensure!(collection.owner == sender, Error::<T>::NoPermission);11801181		let sender = T::CrossAccountId::from_sub(sender);1182		let budget = budget::Value::new(NESTING_BUDGET);11831184		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1185			.map_err(Self::map_unique_err_to_proxy)?;11861187		let pending = sender != nft_owner;11881189		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11901191		let resource_info = RmrkResourceInfo {1192			id,1193			resource,1194			pending,1195			pending_removal: false,1196		};11971198		<PalletNft<T>>::try_mutate_token_aux_property(1199			collection_id,1200			nft_id,1201			PropertyScope::Rmrk,1202			Self::rmrk_property_key(ResourceId(id))?,1203			|value| -> DispatchResult {1204				*value = Some(Self::encode_property(&resource_info)?);12051206				Ok(())1207			},1208		)?;12091210		Ok(id)1211	}12121213	fn resource_remove(1214		sender: T::AccountId,1215		collection_id: CollectionId,1216		nft_id: TokenId,1217		resource_id: RmrkResourceId,1218	) -> DispatchResult {1219		let collection =1220			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1221		ensure!(collection.owner == sender, Error::<T>::NoPermission);12221223		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1224		let scope = PropertyScope::Rmrk;12251226		ensure!(1227			<PalletNft<T>>::token_aux_property((1228				collection_id,1229				nft_id,1230				scope,1231				resource_id_key.clone()1232			))1233			.is_some(),1234			<Error<T>>::ResourceDoesntExist1235		);12361237		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1238		let topmost_owner =1239			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12401241		let sender = T::CrossAccountId::from_sub(sender);1242		if topmost_owner == sender {1243			<PalletNft<T>>::remove_token_aux_property(1244				collection_id,1245				nft_id,1246				PropertyScope::Rmrk,1247				Self::rmrk_property_key(ResourceId(resource_id))?,1248			);1249		} else {1250			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1251				res.pending_removal = true;12521253				Ok(())1254			})?;1255		}12561257		Ok(())1258	}12591260	fn try_mutate_resource_info(1261		collection_id: CollectionId,1262		nft_id: TokenId,1263		resource_id: RmrkResourceId,1264		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1265	) -> DispatchResult {1266		<PalletNft<T>>::try_mutate_token_aux_property(1267			collection_id,1268			nft_id,1269			PropertyScope::Rmrk,1270			Self::rmrk_property_key(ResourceId(resource_id))?,1271			|value| match value {1272				Some(value) => {1273					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12741275					f(&mut resource_info)?;12761277					*value = Self::encode_property(&resource_info)?;12781279					Ok(())1280				}1281				None => Err(<Error<T>>::ResourceDoesntExist.into()),1282			},1283		)1284	}12851286	fn change_collection_owner(1287		collection_id: CollectionId,1288		collection_type: misc::CollectionType,1289		sender: T::AccountId,1290		new_owner: T::AccountId,1291	) -> DispatchResult {1292		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1293		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12941295		let mut collection = collection.into_inner();12961297		collection.owner = new_owner;1298		collection.save()1299	}13001301	fn check_collection_owner(1302		collection: &NonfungibleHandle<T>,1303		account: &T::CrossAccountId,1304	) -> DispatchResult {1305		collection1306			.check_is_owner(account)1307			.map_err(Self::map_unique_err_to_proxy)1308	}13091310	pub fn last_collection_idx() -> RmrkCollectionId {1311		<CollectionIndex<T>>::get()1312	}13131314	pub fn unique_collection_id(1315		rmrk_collection_id: RmrkCollectionId,1316	) -> Result<CollectionId, DispatchError> {1317		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1318			.map_err(|_| <Error<T>>::CollectionUnknown.into())1319	}13201321	pub fn rmrk_collection_id(1322		unique_collection_id: CollectionId,1323	) -> Result<RmrkCollectionId, DispatchError> {1324		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1325	}13261327	pub fn get_nft_collection(1328		collection_id: CollectionId,1329	) -> Result<NonfungibleHandle<T>, DispatchError> {1330		let collection = <CollectionHandle<T>>::try_get(collection_id)1331			.map_err(|_| <Error<T>>::CollectionUnknown)?;13321333		match collection.mode {1334			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1335			_ => Err(<Error<T>>::CollectionUnknown.into()),1336		}1337	}13381339	pub fn collection_exists(collection_id: CollectionId) -> bool {1340		<CollectionHandle<T>>::try_get(collection_id).is_ok()1341	}13421343	pub fn get_collection_property(1344		collection_id: CollectionId,1345		key: RmrkProperty,1346	) -> Result<PropertyValue, DispatchError> {1347		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1348			.get(&Self::rmrk_property_key(key)?)1349			.ok_or(<Error<T>>::CollectionUnknown)?1350			.clone();13511352		Ok(collection_property)1353	}13541355	pub fn get_collection_property_decoded<V: Decode>(1356		collection_id: CollectionId,1357		key: RmrkProperty,1358	) -> Result<V, DispatchError> {1359		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1360	}13611362	pub fn get_collection_type(1363		collection_id: CollectionId,1364	) -> Result<misc::CollectionType, DispatchError> {1365		Self::get_collection_property_decoded(collection_id, CollectionType)1366			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1367	}13681369	pub fn ensure_collection_type(1370		collection_id: CollectionId,1371		collection_type: misc::CollectionType,1372	) -> DispatchResult {1373		let actual_type = Self::get_collection_type(collection_id)?;1374		ensure!(1375			actual_type == collection_type,1376			<CommonError<T>>::NoPermission1377		);13781379		Ok(())1380	}13811382	pub fn get_typed_nft_collection(1383		collection_id: CollectionId,1384		collection_type: misc::CollectionType,1385	) -> Result<NonfungibleHandle<T>, DispatchError> {1386		Self::ensure_collection_type(collection_id, collection_type)?;13871388		Self::get_nft_collection(collection_id)1389	}13901391	pub fn get_typed_nft_collection_mapped(1392		rmrk_collection_id: RmrkCollectionId,1393		collection_type: misc::CollectionType,1394	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1395		let unique_collection_id = match collection_type {1396			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1397			_ => rmrk_collection_id.into(),1398		};13991400		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14011402		Ok((collection, unique_collection_id))1403	}14041405	pub fn get_nft_property(1406		collection_id: CollectionId,1407		nft_id: TokenId,1408		key: RmrkProperty,1409	) -> Result<PropertyValue, DispatchError> {1410		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1411			.get(&Self::rmrk_property_key(key)?)1412			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1413			.clone();14141415		Ok(nft_property)1416	}14171418	pub fn get_nft_property_decoded<V: Decode>(1419		collection_id: CollectionId,1420		nft_id: TokenId,1421		key: RmrkProperty,1422	) -> Result<V, DispatchError> {1423		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1424	}14251426	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1427		<TokenData<T>>::contains_key((collection_id, nft_id))1428	}14291430	pub fn get_nft_type(1431		collection_id: CollectionId,1432		token_id: TokenId,1433	) -> Result<NftType, DispatchError> {1434		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1435			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1436	}14371438	pub fn ensure_nft_type(1439		collection_id: CollectionId,1440		token_id: TokenId,1441		nft_type: NftType,1442	) -> DispatchResult {1443		let actual_type = Self::get_nft_type(collection_id, token_id)?;1444		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14451446		Ok(())1447	}14481449	pub fn ensure_nft_owner(1450		collection_id: CollectionId,1451		token_id: TokenId,1452		possible_owner: &T::CrossAccountId,1453		nesting_budget: &dyn budget::Budget,1454	) -> DispatchResult {1455		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1456			possible_owner.clone(),1457			collection_id,1458			token_id,1459			None,1460			nesting_budget,1461		)1462		.map_err(Self::map_unique_err_to_proxy)?;14631464		ensure!(is_owned, <Error<T>>::NoPermission);14651466		Ok(())1467	}14681469	pub fn filter_user_properties<Key, Value, R, Mapper>(1470		collection_id: CollectionId,1471		token_id: Option<TokenId>,1472		filter_keys: Option<Vec<RmrkPropertyKey>>,1473		mapper: Mapper,1474	) -> Result<Vec<R>, DispatchError>1475	where1476		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1477		Value: Decode + Default,1478		Mapper: Fn(Key, Value) -> R,1479	{1480		filter_keys1481			.map(|keys| {1482				let properties = keys1483					.into_iter()1484					.filter_map(|key| {1485						let key: Key = key.try_into().ok()?;14861487						let value = match token_id {1488							Some(token_id) => Self::get_nft_property_decoded(1489								collection_id,1490								token_id,1491								UserProperty(key.as_ref()),1492							),1493							None => Self::get_collection_property_decoded(1494								collection_id,1495								UserProperty(key.as_ref()),1496							),1497						}1498						.ok()?;14991500						Some(mapper(key, value))1501					})1502					.collect();15031504				Ok(properties)1505			})1506			.unwrap_or_else(|| {1507				let properties =1508					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15091510				Ok(properties)1511			})1512	}15131514	pub fn iterate_user_properties<Key, Value, R, Mapper>(1515		collection_id: CollectionId,1516		token_id: Option<TokenId>,1517		mapper: Mapper,1518	) -> Result<impl Iterator<Item = R>, DispatchError>1519	where1520		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1521		Value: Decode + Default,1522		Mapper: Fn(Key, Value) -> R,1523	{1524		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15251526		let properties = match token_id {1527			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1528			None => <PalletCommon<T>>::collection_properties(collection_id),1529		};15301531		let properties = properties.into_iter().filter_map(move |(key, value)| {1532			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15331534			let key: Key = key.to_vec().try_into().ok()?;1535			let value: Value = value.decode().ok()?;15361537			Some(mapper(key, value))1538		});15391540		Ok(properties)1541	}15421543	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1544		map_unique_err_to_proxy! {1545			match err {1546				CommonError::NoPermission => NoPermission,1547				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1548				CommonError::PublicMintingNotAllowed => NoPermission,1549				CommonError::TokenNotFound => NoAvailableNftId,1550				CommonError::ApprovedValueTooLow => NoPermission,1551				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1552				StructureError::TokenNotFound => NoAvailableNftId,1553				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1554			}1555		}1556	}1557}
addedpallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -0,0 +1,265 @@
+use super::*;
+
+pub fn last_collection_idx<T: Config>() -> Result<RmrkCollectionId, DispatchError> {
+	Ok(<Pallet<T>>::last_collection_idx())
+}
+
+pub fn collection_by_id<T: Config>(
+	collection_id: RmrkCollectionId,
+) -> Result<Option<RmrkCollectionInfo<T::AccountId>>, DispatchError> {
+	let (collection, collection_id) = match <Pallet<T>>::get_typed_nft_collection_mapped(
+		collection_id,
+		misc::CollectionType::Regular,
+	) {
+		Ok(c) => c,
+		Err(_) => return Ok(None),
+	};
+
+	let nfts_count = collection.total_supply();
+
+	Ok(Some(RmrkCollectionInfo {
+		issuer: collection.owner.clone(),
+		metadata: <Pallet<T>>::get_collection_property_decoded(
+			collection_id,
+			RmrkProperty::Metadata,
+		)?,
+		max: collection.limits.token_limit,
+		symbol: <Pallet<T>>::rebind(&collection.token_prefix)?,
+		nfts_count,
+	}))
+}
+
+pub fn nft_by_id<T: Config>(
+	collection_id: RmrkCollectionId,
+	nft_by_id: RmrkNftId,
+) -> Result<Option<RmrkInstanceInfo<T::AccountId>>, DispatchError> {
+	let (collection, collection_id) = match <Pallet<T>>::get_typed_nft_collection_mapped(
+		collection_id,
+		misc::CollectionType::Regular,
+	) {
+		Ok(c) => c,
+		Err(_) => return Ok(None),
+	};
+
+	let nft_id = TokenId(nft_by_id);
+	if !<Pallet<T>>::nft_exists(collection_id, nft_id) {
+		return Ok(None);
+	}
+
+	let owner = match collection.token_owner(nft_id) {
+		Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
+			Some((col, tok)) => {
+				let rmrk_collection = <Pallet<T>>::rmrk_collection_id(col)?;
+
+				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
+			}
+			None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone()),
+		},
+		None => return Ok(None),
+	};
+
+	Ok(Some(RmrkInstanceInfo {
+		owner: owner,
+		royalty: <Pallet<T>>::get_nft_property_decoded(
+			collection_id,
+			nft_id,
+			RmrkProperty::RoyaltyInfo,
+		)?,
+		metadata: <Pallet<T>>::get_nft_property_decoded(
+			collection_id,
+			nft_id,
+			RmrkProperty::Metadata,
+		)?,
+		equipped: <Pallet<T>>::get_nft_property_decoded(
+			collection_id,
+			nft_id,
+			RmrkProperty::Equipped,
+		)?,
+		pending: <Pallet<T>>::get_nft_property_decoded(
+			collection_id,
+			nft_id,
+			RmrkProperty::PendingNftAccept,
+		)?,
+	}))
+}
+
+pub fn account_tokens<T: Config>(
+	account_id: T::AccountId,
+	collection_id: RmrkCollectionId,
+) -> Result<Vec<RmrkNftId>, DispatchError> {
+	let cross_account_id = CrossAccountId::from_sub(account_id);
+
+	let (collection, collection_id) = match <Pallet<T>>::get_typed_nft_collection_mapped(
+		collection_id,
+		misc::CollectionType::Regular,
+	) {
+		Ok(c) => c,
+		Err(_) => return Ok(Vec::new()),
+	};
+
+	let tokens = collection
+		.account_tokens(cross_account_id)
+		.into_iter()
+		.filter(|token| {
+			let is_pending = <Pallet<T>>::get_nft_property_decoded(
+				collection_id,
+				*token,
+				RmrkProperty::PendingNftAccept,
+			)
+			.unwrap_or(true);
+
+			!is_pending
+		})
+		.map(|token| token.0)
+		.collect();
+
+	Ok(tokens)
+}
+
+pub fn nft_children<T: Config>(
+	collection_id: RmrkCollectionId,
+	nft_id: RmrkNftId,
+) -> Result<Vec<RmrkNftChild>, DispatchError> {
+	let collection_id = match <Pallet<T>>::unique_collection_id(collection_id) {
+		Ok(id) => id,
+		Err(_) => return Ok(Vec::new()),
+	};
+	let nft_id = TokenId(nft_id);
+	if !<Pallet<T>>::nft_exists(collection_id, nft_id) {
+		return Ok(Vec::new());
+	}
+
+	Ok(
+		pallet_nonfungible::TokenChildren::<T>::iter_prefix((collection_id, nft_id))
+			.filter_map(|((child_collection, child_token), _)| {
+				let is_pending = <Pallet<T>>::get_nft_property_decoded(
+					child_collection,
+					child_token,
+					RmrkProperty::PendingNftAccept,
+				)
+				.ok()?;
+
+				if is_pending {
+					return None;
+				}
+
+				let rmrk_child_collection =
+					<Pallet<T>>::rmrk_collection_id(child_collection).ok()?;
+
+				Some(RmrkNftChild {
+					collection_id: rmrk_child_collection,
+					nft_id: child_token.0,
+				})
+			})
+			.collect(),
+	)
+}
+
+pub fn collection_properties<T: Config>(
+	collection_id: RmrkCollectionId,
+	filter_keys: Option<Vec<RmrkPropertyKey>>,
+) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+	let collection_id = match <Pallet<T>>::unique_collection_id(collection_id) {
+		Ok(id) => id,
+		Err(_) => return Ok(Vec::new()),
+	};
+	if <Pallet<T>>::ensure_collection_type(collection_id, misc::CollectionType::Regular).is_err() {
+		return Ok(Vec::new());
+	}
+
+	let properties = <Pallet<T>>::filter_user_properties(
+		collection_id,
+		/* token_id = */ None,
+		filter_keys,
+		|key, value| RmrkPropertyInfo { key, value },
+	)?;
+
+	Ok(properties)
+}
+
+pub fn nft_properties<T: Config>(
+	collection_id: RmrkCollectionId,
+	nft_id: RmrkNftId,
+	filter_keys: Option<Vec<RmrkPropertyKey>>,
+) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+	let collection_id = match <Pallet<T>>::unique_collection_id(collection_id) {
+		Ok(id) => id,
+		Err(_) => return Ok(Vec::new()),
+	};
+	let token_id = TokenId(nft_id);
+
+	if <Pallet<T>>::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
+		return Ok(Vec::new());
+	}
+
+	let properties = <Pallet<T>>::filter_user_properties(
+		collection_id,
+		Some(token_id),
+		filter_keys,
+		|key, value| RmrkPropertyInfo { key, value },
+	)?;
+
+	Ok(properties)
+}
+
+pub fn nft_resources<T: Config>(
+	collection_id: RmrkCollectionId,
+	nft_id: RmrkNftId,
+) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
+	let collection_id = match <Pallet<T>>::unique_collection_id(collection_id) {
+		Ok(id) => id,
+		Err(_) => return Ok(Vec::new()),
+	};
+	if <Pallet<T>>::ensure_collection_type(collection_id, misc::CollectionType::Regular).is_err() {
+		return Ok(Vec::new());
+	}
+
+	let nft_id = TokenId(nft_id);
+	if <Pallet<T>>::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() {
+		return Ok(Vec::new());
+	}
+
+	let resources = <pallet_nonfungible::Pallet<T>>::iterate_token_aux_properties(
+		collection_id,
+		nft_id,
+		PropertyScope::Rmrk,
+	)
+	.filter_map(|(_, value)| {
+		let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property(&value).ok()?;
+
+		Some(resource_info)
+	})
+	.collect();
+
+	Ok(resources)
+}
+
+pub fn nft_resource_priority<T: Config>(
+	collection_id: RmrkCollectionId,
+	nft_id: RmrkNftId,
+	resource_id: RmrkResourceId,
+) -> Result<Option<u32>, DispatchError> {
+	let collection_id = match <Pallet<T>>::unique_collection_id(collection_id) {
+		Ok(id) => id,
+		Err(_) => return Ok(None),
+	};
+	if <Pallet<T>>::ensure_collection_type(collection_id, misc::CollectionType::Regular).is_err() {
+		return Ok(None);
+	}
+
+	let nft_id = TokenId(nft_id);
+	if <Pallet<T>>::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() {
+		return Ok(None);
+	}
+
+	let priorities: Vec<_> = <Pallet<T>>::get_nft_property_decoded(
+		collection_id,
+		nft_id,
+		RmrkProperty::ResourcePriorities,
+	)?;
+	Ok(priorities
+		.into_iter()
+		.enumerate()
+		.find(|(_, id)| *id == resource_id)
+		.map(|(priority, _): (usize, RmrkResourceId)| priority as u32))
+}
modifiedpallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -34,6 +34,7 @@
 
 #[cfg(feature = "runtime-benchmarks")]
 pub mod benchmarking;
+pub mod rpc;
 pub mod weights;
 
 pub type SelfWeightOf<T> = <T as Config>::WeightInfo;
addedpallets/proxy-rmrk-equip/src/rpc.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/proxy-rmrk-equip/src/rpc.rs
@@ -0,0 +1,186 @@
+use super::*;
+use pallet_rmrk_core::{misc, property::*};
+use sp_std::vec::Vec;
+
+pub fn base<T: Config>(
+	base_id: RmrkBaseId,
+) -> Result<Option<RmrkBaseInfo<T::AccountId>>, DispatchError> {
+	let (collection, collection_id) =
+		match <PalletCore<T>>::get_typed_nft_collection_mapped(base_id, misc::CollectionType::Base)
+		{
+			Ok(c) => c,
+			Err(_) => return Ok(None),
+		};
+
+	Ok(Some(RmrkBaseInfo {
+		issuer: collection.owner.clone(),
+		base_type: <PalletCore<T>>::get_collection_property_decoded(
+			collection_id,
+			RmrkProperty::BaseType,
+		)?,
+		symbol: <PalletCore<T>>::rebind(&collection.token_prefix)?,
+	}))
+}
+
+pub fn base_parts<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
+	use pallet_common::CommonCollectionOperations;
+
+	let (collection, collection_id) =
+		match <PalletCore<T>>::get_typed_nft_collection_mapped(base_id, misc::CollectionType::Base)
+		{
+			Ok(c) => c,
+			Err(_) => return Ok(Vec::new()),
+		};
+
+	let parts = collection
+		.collection_tokens()
+		.into_iter()
+		.filter_map(|token_id| {
+			let nft_type = <PalletCore<T>>::get_nft_type(collection_id, token_id).ok()?;
+
+			match nft_type {
+				NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
+					id: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::ExternalPartId,
+					)
+					.ok()?,
+					src: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::Src,
+					)
+					.ok()?,
+					z: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::ZIndex,
+					)
+					.ok()?,
+				})),
+				NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
+					id: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::ExternalPartId,
+					)
+					.ok()?,
+					src: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::Src,
+					)
+					.ok()?,
+					z: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::ZIndex,
+					)
+					.ok()?,
+					equippable: <PalletCore<T>>::get_nft_property_decoded(
+						collection_id,
+						token_id,
+						RmrkProperty::EquippableList,
+					)
+					.ok()?,
+				})),
+				_ => None,
+			}
+		})
+		.collect();
+
+	Ok(parts)
+}
+
+pub fn theme_names<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
+	use pallet_common::CommonCollectionOperations;
+
+	let (collection, collection_id) =
+		match <PalletCore<T>>::get_typed_nft_collection_mapped(base_id, misc::CollectionType::Base)
+		{
+			Ok(c) => c,
+			Err(_) => return Ok(Vec::new()),
+		};
+
+	let theme_names = collection
+		.collection_tokens()
+		.iter()
+		.filter_map(|token_id| {
+			let nft_type = <PalletCore<T>>::get_nft_type(collection_id, *token_id).ok()?;
+
+			match nft_type {
+				NftType::Theme => <PalletCore<T>>::get_nft_property_decoded(
+					collection_id,
+					*token_id,
+					RmrkProperty::ThemeName,
+				)
+				.ok(),
+				_ => None,
+			}
+		})
+		.collect();
+
+	Ok(theme_names)
+}
+
+pub fn theme<T: Config>(
+	base_id: RmrkBaseId,
+	theme_name: RmrkThemeName,
+	filter_keys: Option<Vec<RmrkPropertyKey>>,
+) -> Result<Option<RmrkTheme>, DispatchError> {
+	use pallet_common::CommonCollectionOperations;
+
+	let (collection, collection_id) =
+		match <PalletCore<T>>::get_typed_nft_collection_mapped(base_id, misc::CollectionType::Base)
+		{
+			Ok(c) => c,
+			Err(_) => return Ok(None),
+		};
+
+	let theme_info = collection
+		.collection_tokens()
+		.into_iter()
+		.find_map(|token_id| {
+			<PalletCore<T>>::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
+
+			let name: RmrkString = <PalletCore<T>>::get_nft_property_decoded(
+				collection_id,
+				token_id,
+				RmrkProperty::ThemeName,
+			)
+			.ok()?;
+
+			if name == theme_name {
+				Some((name, token_id))
+			} else {
+				None
+			}
+		});
+
+	let (name, theme_id) = match theme_info {
+		Some((name, theme_id)) => (name, theme_id),
+		None => return Ok(None),
+	};
+
+	let properties = <PalletCore<T>>::filter_user_properties(
+		collection_id,
+		Some(theme_id),
+		filter_keys,
+		|key, value| RmrkThemeProperty { key, value },
+	)?;
+
+	let inherit = <PalletCore<T>>::get_nft_property_decoded(
+		collection_id,
+		theme_id,
+		RmrkProperty::ThemeInherit,
+	)?;
+
+	let theme = RmrkTheme {
+		name,
+		properties,
+		inherit,
+	};
+
+	Ok(Some(theme))
+}
modifiedruntime/opal/src/lib.rsdiffbeforeafterboth
--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -74,9 +74,8 @@
 	CollectionStats, RpcCollection,
 	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
 	TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
-	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkThemeProperty, RmrkCollectionId,
-	RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceId,
-	RmrkBaseId, RmrkFixedPart, RmrkSlotPart, RmrkString,
+	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId,
+	RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId,
 };
 
 // use pallet_contracts::weights::WeightInfo;
@@ -1330,352 +1329,55 @@
 		RmrkTheme
 	> for Runtime {
 		fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
-			Ok(RmrkCore::last_collection_idx())
+			pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>()
 		}
 
 		fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			let nfts_count = collection.total_supply();
-
-			Ok(Some(RmrkCollectionInfo {
-				issuer: collection.owner.clone(),
-				metadata: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::Metadata)?,
-				max: collection.limits.token_limit,
-				symbol: RmrkCore::rebind(&collection.token_prefix)?,
-				nfts_count
-			}))
+			pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id)
 		}
 
 		fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
-			use up_data_structs::mapping::TokenAddressMapping;
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			let nft_id = TokenId(nft_by_id);
-			if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); }
-
-			let owner = match collection.token_owner(nft_id) {
-				Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
-					Some((col, tok)) => {
-						let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
-
-						RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
-					}
-					None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
-				},
-				None => return Ok(None)
-			};
-
-			Ok(Some(RmrkInstanceInfo {
-				owner: owner,
-				royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
-				metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
-				equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
-				pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
-			}))
+			pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id)
 		}
 
 		fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
-			use pallet_common::CommonCollectionOperations;
-
-			let cross_account_id = CrossAccountId::from_sub(account_id);
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
-				Ok(c) => c,
-				Err(_) => return Ok(Vec::new()),
-			};
-
-			let tokens = collection.account_tokens(cross_account_id)
-				.into_iter()
-				.filter(|token| {
-					let is_pending = RmrkCore::get_nft_property_decoded(
-						collection_id,
-						*token,
-						RmrkProperty::PendingNftAccept
-					).unwrap_or(true);
-
-					!is_pending
-				})
-				.map(|token| token.0)
-				.collect();
-
-			Ok(tokens)
+			pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id)
 		}
 
 		fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-			use pallet_proxy_rmrk_core::RmrkProperty;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			let nft_id = TokenId(nft_id);
-			if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
-
-			Ok(
-				pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
-					.filter_map(|((child_collection, child_token), _)| {
-						let is_pending = RmrkCore::get_nft_property_decoded(
-							child_collection,
-							child_token,
-							RmrkProperty::PendingNftAccept
-						).ok()?;
-
-						if is_pending {
-							return None;
-						}
-
-						let rmrk_child_collection = RmrkCore::rmrk_collection_id(
-							child_collection
-						).ok()?;
-
-						Some(RmrkNftChild {
-							collection_id: rmrk_child_collection,
-							nft_id: child_token.0,
-						})
-					}).collect()
-			)
+			pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id)
 		}
 
 		fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			use pallet_proxy_rmrk_core::misc::CollectionType;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
-				return Ok(Vec::new());
-			}
-
-			let properties = RmrkCore::filter_user_properties(
-				collection_id,
-				/* token_id = */ None,
-				filter_keys,
-				|key, value| RmrkPropertyInfo {
-					key,
-					value
-				}
-			)?;
-
-			Ok(properties)
+			pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys)
 		}
 
 		fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			use pallet_proxy_rmrk_core::misc::NftType;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			let token_id = TokenId(nft_id);
-
-			if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
-				return Ok(Vec::new());
-			}
-
-			let properties = RmrkCore::filter_user_properties(
-				collection_id,
-				Some(token_id),
-				filter_keys,
-				|key, value| RmrkPropertyInfo {
-					key,
-					value
-				}
-			)?;
-
-			Ok(properties)
+			pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys)
 		}
 
 		fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
-			use pallet_proxy_rmrk_core::misc::{CollectionType, NftType};
-			use up_data_structs::PropertyScope;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
-
-			let nft_id = TokenId(nft_id);
-			if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
-
-			let resources = <pallet_nonfungible::Pallet<Runtime>>::iterate_token_aux_properties(
-				collection_id, nft_id, PropertyScope::Rmrk
-			).filter_map(|(_, value)| {
-				let resource_info: RmrkResourceInfo = RmrkCore::decode_property(&value).ok()?;
-
-				Some(resource_info)
-			}).collect();
-
-			Ok(resources)
+			pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id)
 		}
 
 		fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(None)
-			};
-			if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(None); }
-
-			let nft_id = TokenId(nft_id);
-			if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(None); }
-
-			let priorities: Vec<_> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourcePriorities)?;
-			Ok(
-				priorities.into_iter()
-					.enumerate()
-					.find(|(_, id)| *id == resource_id)
-					.map(|(priority, _): (usize, RmrkResourceId)| priority as u32)
-			)
+			pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id)
 		}
 
 		fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
-			use pallet_proxy_rmrk_core::{
-				RmrkProperty, misc::{CollectionType},
-			};
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			Ok(Some(RmrkBaseInfo {
-				issuer: collection.owner.clone(),
-				base_type: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::BaseType)?,
-				symbol: RmrkCore::rebind(&collection.token_prefix)?,
-			}))
+			pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id)
 		}
 
 		fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(Vec::new()),
-			};
-
-			let parts = collection.collection_tokens()
-				.into_iter()
-				.filter_map(|token_id| {
-					let nft_type = RmrkCore::get_nft_type(collection_id, token_id).ok()?;
-
-					match nft_type {
-						NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
-							id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
-							src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
-							z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
-						})),
-						NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
-							id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
-							src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
-							z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
-							equippable: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::EquippableList).ok()?,
-						})),
-						_ => None
-					}
-				})
-				.collect();
-
-			Ok(parts)
+			pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id)
 		}
 
 		fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::{NftType, CollectionType}};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(Vec::new()),
-			};
-
-			let theme_names = collection.collection_tokens()
-				.iter()
-				.filter_map(|token_id| {
-					let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).ok()?;
-
-					match nft_type {
-						NftType::Theme => RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).ok(),
-						_ => None
-					}
-				})
-				.collect();
-
-			Ok(theme_names)
+			pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id)
 		}
 
 		fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
-			use pallet_proxy_rmrk_core::{
-				RmrkProperty,
-				misc::{CollectionType, NftType}
-			};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			let theme_info = collection.collection_tokens()
-				.into_iter()
-				.find_map(|token_id| {
-					RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
-
-					let name: RmrkString = RmrkCore::get_nft_property_decoded(
-						collection_id, token_id, RmrkProperty::ThemeName
-					).ok()?;
-
-					if name == theme_name {
-						Some((name, token_id))
-					} else {
-						None
-					}
-				});
-
-			let (name, theme_id) = match theme_info {
-				Some((name, theme_id)) => (name, theme_id),
-				None => return Ok(None)
-			};
-
-			let properties = RmrkCore::filter_user_properties(
-				collection_id,
-				Some(theme_id),
-				filter_keys,
-				|key, value| RmrkThemeProperty {
-					key,
-					value
-				}
-			)?;
-
-			let inherit = RmrkCore::get_nft_property_decoded(
-				collection_id,
-				theme_id,
-				RmrkProperty::ThemeInherit
-			)?;
-
-			let theme = RmrkTheme {
-				name,
-				properties,
-				inherit,
-			};
-
-			Ok(Some(theme))
+			pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys)
 		}
 	}
 }
modifiedruntime/quartz/src/lib.rsdiffbeforeafterboth
--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -74,9 +74,8 @@
 	CollectionStats, RpcCollection,
 	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
 	TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
-	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkThemeProperty, RmrkCollectionId,
-	RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceId,
-	RmrkBaseId, RmrkFixedPart, RmrkSlotPart, RmrkString,
+	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId,
+	RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId,
 };
 
 // use pallet_contracts::weights::WeightInfo;
@@ -1330,352 +1329,55 @@
 		RmrkTheme
 	> for Runtime {
 		fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
-			Ok(RmrkCore::last_collection_idx())
+			pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>()
 		}
 
 		fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			let nfts_count = collection.total_supply();
-
-			Ok(Some(RmrkCollectionInfo {
-				issuer: collection.owner.clone(),
-				metadata: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::Metadata)?,
-				max: collection.limits.token_limit,
-				symbol: RmrkCore::rebind(&collection.token_prefix)?,
-				nfts_count
-			}))
+			pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id)
 		}
 
 		fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
-			use up_data_structs::mapping::TokenAddressMapping;
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			let nft_id = TokenId(nft_by_id);
-			if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); }
-
-			let owner = match collection.token_owner(nft_id) {
-				Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
-					Some((col, tok)) => {
-						let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
-
-						RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
-					}
-					None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
-				},
-				None => return Ok(None)
-			};
-
-			Ok(Some(RmrkInstanceInfo {
-				owner: owner,
-				royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
-				metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
-				equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
-				pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
-			}))
+			pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id)
 		}
 
 		fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
-			use pallet_common::CommonCollectionOperations;
-
-			let cross_account_id = CrossAccountId::from_sub(account_id);
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
-				Ok(c) => c,
-				Err(_) => return Ok(Vec::new()),
-			};
-
-			let tokens = collection.account_tokens(cross_account_id)
-				.into_iter()
-				.filter(|token| {
-					let is_pending = RmrkCore::get_nft_property_decoded(
-						collection_id,
-						*token,
-						RmrkProperty::PendingNftAccept
-					).unwrap_or(true);
-
-					!is_pending
-				})
-				.map(|token| token.0)
-				.collect();
-
-			Ok(tokens)
+			pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id)
 		}
 
 		fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-			use pallet_proxy_rmrk_core::RmrkProperty;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			let nft_id = TokenId(nft_id);
-			if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
-
-			Ok(
-				pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
-					.filter_map(|((child_collection, child_token), _)| {
-						let is_pending = RmrkCore::get_nft_property_decoded(
-							child_collection,
-							child_token,
-							RmrkProperty::PendingNftAccept
-						).ok()?;
-
-						if is_pending {
-							return None;
-						}
-
-						let rmrk_child_collection = RmrkCore::rmrk_collection_id(
-							child_collection
-						).ok()?;
-
-						Some(RmrkNftChild {
-							collection_id: rmrk_child_collection,
-							nft_id: child_token.0,
-						})
-					}).collect()
-			)
+			pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id)
 		}
 
 		fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			use pallet_proxy_rmrk_core::misc::CollectionType;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
-				return Ok(Vec::new());
-			}
-
-			let properties = RmrkCore::filter_user_properties(
-				collection_id,
-				/* token_id = */ None,
-				filter_keys,
-				|key, value| RmrkPropertyInfo {
-					key,
-					value
-				}
-			)?;
-
-			Ok(properties)
+			pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys)
 		}
 
 		fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			use pallet_proxy_rmrk_core::misc::NftType;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			let token_id = TokenId(nft_id);
-
-			if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
-				return Ok(Vec::new());
-			}
-
-			let properties = RmrkCore::filter_user_properties(
-				collection_id,
-				Some(token_id),
-				filter_keys,
-				|key, value| RmrkPropertyInfo {
-					key,
-					value
-				}
-			)?;
-
-			Ok(properties)
+			pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys)
 		}
 
 		fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
-			use pallet_proxy_rmrk_core::misc::{CollectionType, NftType};
-			use up_data_structs::PropertyScope;
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(Vec::new())
-			};
-			if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
-
-			let nft_id = TokenId(nft_id);
-			if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
-
-			let resources = <pallet_nonfungible::Pallet<Runtime>>::iterate_token_aux_properties(
-				collection_id, nft_id, PropertyScope::Rmrk
-			).filter_map(|(_, value)| {
-				let resource_info: RmrkResourceInfo = RmrkCore::decode_property(&value).ok()?;
-
-				Some(resource_info)
-			}).collect();
-
-			Ok(resources)
+			pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id)
 		}
 
 		fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
-
-			let collection_id = match RmrkCore::unique_collection_id(collection_id) {
-				Ok(id) => id,
-				Err(_) => return Ok(None)
-			};
-			if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(None); }
-
-			let nft_id = TokenId(nft_id);
-			if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(None); }
-
-			let priorities: Vec<_> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourcePriorities)?;
-			Ok(
-				priorities.into_iter()
-					.enumerate()
-					.find(|(_, id)| *id == resource_id)
-					.map(|(priority, _): (usize, RmrkResourceId)| priority as u32)
-			)
+			pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id)
 		}
 
 		fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
-			use pallet_proxy_rmrk_core::{
-				RmrkProperty, misc::{CollectionType},
-			};
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			Ok(Some(RmrkBaseInfo {
-				issuer: collection.owner.clone(),
-				base_type: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::BaseType)?,
-				symbol: RmrkCore::rebind(&collection.token_prefix)?,
-			}))
+			pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id)
 		}
 
 		fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(Vec::new()),
-			};
-
-			let parts = collection.collection_tokens()
-				.into_iter()
-				.filter_map(|token_id| {
-					let nft_type = RmrkCore::get_nft_type(collection_id, token_id).ok()?;
-
-					match nft_type {
-						NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
-							id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
-							src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
-							z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
-						})),
-						NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
-							id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
-							src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
-							z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
-							equippable: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::EquippableList).ok()?,
-						})),
-						_ => None
-					}
-				})
-				.collect();
-
-			Ok(parts)
+			pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id)
 		}
 
 		fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
-			use pallet_proxy_rmrk_core::{RmrkProperty, misc::{NftType, CollectionType}};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(Vec::new()),
-			};
-
-			let theme_names = collection.collection_tokens()
-				.iter()
-				.filter_map(|token_id| {
-					let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).ok()?;
-
-					match nft_type {
-						NftType::Theme => RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).ok(),
-						_ => None
-					}
-				})
-				.collect();
-
-			Ok(theme_names)
+			pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id)
 		}
 
 		fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
-			use pallet_proxy_rmrk_core::{
-				RmrkProperty,
-				misc::{CollectionType, NftType}
-			};
-			use pallet_common::CommonCollectionOperations;
-
-			let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
-				Ok(c) => c,
-				Err(_) => return Ok(None),
-			};
-
-			let theme_info = collection.collection_tokens()
-				.into_iter()
-				.find_map(|token_id| {
-					RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
-
-					let name: RmrkString = RmrkCore::get_nft_property_decoded(
-						collection_id, token_id, RmrkProperty::ThemeName
-					).ok()?;
-
-					if name == theme_name {
-						Some((name, token_id))
-					} else {
-						None
-					}
-				});
-
-			let (name, theme_id) = match theme_info {
-				Some((name, theme_id)) => (name, theme_id),
-				None => return Ok(None)
-			};
-
-			let properties = RmrkCore::filter_user_properties(
-				collection_id,
-				Some(theme_id),
-				filter_keys,
-				|key, value| RmrkThemeProperty {
-					key,
-					value
-				}
-			)?;
-
-			let inherit = RmrkCore::get_nft_property_decoded(
-				collection_id,
-				theme_id,
-				RmrkProperty::ThemeInherit
-			)?;
-
-			let theme = RmrkTheme {
-				name,
-				properties,
-				inherit,
-			};
-
-			Ok(Some(theme))
+			pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys)
 		}
 	}
 }