git.delta.rocks / unique-network / refs/commits / c77858a53dbb

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs42.7 KiBsourcehistory
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		UnableToDecodeRmrkData,158159		/* RMRK compatible events */160		CollectionNotEmpty,161		NoAvailableCollectionId,162		NoAvailableNftId,163		CollectionUnknown,164		NoPermission,165		NonTransferable,166		CollectionFullOrLocked,167		ResourceDoesntExist,168		CannotSendToDescendentOrSelf,169		CannotAcceptNonOwnedNft,170		CannotRejectNonOwnedNft,171		ResourceNotPending,172	}173174	#[pallet::call]175	impl<T: Config> Pallet<T> {176		/// Create a collection177		#[transactional]178		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]179		pub fn create_collection(180			origin: OriginFor<T>,181			metadata: RmrkString,182			max: Option<u32>,183			symbol: RmrkCollectionSymbol,184		) -> DispatchResult {185			let sender = ensure_signed(origin)?;186187			let limits = CollectionLimits {188				owner_can_transfer: Some(false),189				token_limit: max,190				..Default::default()191			};192193			let data = CreateCollectionData {194				limits: Some(limits),195				token_prefix: symbol196					.into_inner()197					.try_into()198					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,199				permissions: Some(CollectionPermissions {200					nesting: Some(NestingPermissions {201						token_owner: true,202						admin: false,203						restricted: None,204205						permissive: false,206					}),207					..Default::default()208				}),209				..Default::default()210			};211212			let unique_collection_id = Self::init_collection(213				T::CrossAccountId::from_sub(sender.clone()),214				data,215				[216					Self::rmrk_property(Metadata, &metadata)?,217					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,218				]219				.into_iter(),220			)?;221			let rmrk_collection_id = <CollectionIndex<T>>::get();222223			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);224225			<PalletCommon<T>>::set_scoped_collection_property(226				unique_collection_id,227				PropertyScope::Rmrk,228				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?229			)?;230231			<CollectionIndex<T>>::mutate(|n| *n += 1);232233			Self::deposit_event(Event::CollectionCreated {234				issuer: sender,235				collection_id: rmrk_collection_id,236			});237238			Ok(())239		}240241		/// destroy collection242		#[transactional]243		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]244		pub fn destroy_collection(245			origin: OriginFor<T>,246			collection_id: RmrkCollectionId,247		) -> DispatchResult {248			let sender = ensure_signed(origin)?;249			let cross_sender = T::CrossAccountId::from_sub(sender.clone());250251			let collection = Self::get_typed_nft_collection(252				Self::unique_collection_id(collection_id)?,253				misc::CollectionType::Regular,254			)?;255			collection.check_is_external()?;256257			<PalletNft<T>>::destroy_collection(collection, &cross_sender)258				.map_err(Self::map_unique_err_to_proxy)?;259260			Self::deposit_event(Event::CollectionDestroyed {261				issuer: sender,262				collection_id,263			});264265			Ok(())266		}267268		/// Change the issuer of a collection269		///270		/// Parameters:271		/// - `origin`: sender of the transaction272		/// - `collection_id`: collection id of the nft to change issuer of273		/// - `new_issuer`: Collection's new issuer274		#[transactional]275		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]276		pub fn change_collection_issuer(277			origin: OriginFor<T>,278			collection_id: RmrkCollectionId,279			new_issuer: <T::Lookup as StaticLookup>::Source,280		) -> DispatchResult {281			let sender = ensure_signed(origin)?;282283			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;284			collection.check_is_external()?;285286			let new_issuer = T::Lookup::lookup(new_issuer)?;287288			Self::change_collection_owner(289				Self::unique_collection_id(collection_id)?,290				misc::CollectionType::Regular,291				sender.clone(),292				new_issuer.clone(),293			)?;294295			Self::deposit_event(Event::IssuerChanged {296				old_issuer: sender,297				new_issuer,298				collection_id,299			});300301			Ok(())302		}303304		/// lock collection305		#[transactional]306		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]307		pub fn lock_collection(308			origin: OriginFor<T>,309			collection_id: RmrkCollectionId,310		) -> DispatchResult {311			let sender = ensure_signed(origin)?;312			let cross_sender = T::CrossAccountId::from_sub(sender.clone());313314			let collection = Self::get_typed_nft_collection(315				Self::unique_collection_id(collection_id)?,316				misc::CollectionType::Regular,317			)?;318			collection.check_is_external()?;319320			Self::check_collection_owner(&collection, &cross_sender)?;321322			let token_count = collection.total_supply();323324			let mut collection = collection.into_inner();325			collection.limits.token_limit = Some(token_count);326			collection.save()?;327328			Self::deposit_event(Event::CollectionLocked {329				issuer: sender,330				collection_id,331			});332333			Ok(())334		}335336		/// Mints an NFT in the specified collection337		/// Sets metadata and the royalty attribute338		///339		/// Parameters:340		/// - `collection_id`: The class of the asset to be minted.341		/// - `nft_id`: The nft value of the asset to be minted.342		/// - `recipient`: Receiver of the royalty343		/// - `royalty`: Permillage reward from each trade for the Recipient344		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash345		/// - `transferable`: Ability to transfer this NFT346		#[transactional]347		#[pallet::weight(<SelfWeightOf<T>>::mint_nft())]348		pub fn mint_nft(349			origin: OriginFor<T>,350			owner: T::AccountId,351			collection_id: RmrkCollectionId,352			recipient: Option<T::AccountId>,353			royalty_amount: Option<Permill>,354			metadata: RmrkString,355			transferable: bool,356		) -> DispatchResult {357			let sender = ensure_signed(origin)?;358			let sender = T::CrossAccountId::from_sub(sender);359			let cross_owner = T::CrossAccountId::from_sub(owner.clone());360361			let collection = Self::get_typed_nft_collection(362				Self::unique_collection_id(collection_id)?,363				misc::CollectionType::Regular,364			)?;365			collection.check_is_external()?;366367			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {368				recipient: recipient.unwrap_or_else(|| owner.clone()),369				amount,370			});371372			let nft_id = Self::create_nft(373				&sender,374				&cross_owner,375				&collection,376				[377					Self::rmrk_property(TokenType, &NftType::Regular)?,378					Self::rmrk_property(Transferable, &transferable)?,379					Self::rmrk_property(PendingNftAccept, &false)?,380					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,381					Self::rmrk_property(Metadata, &metadata)?,382					Self::rmrk_property(Equipped, &false)?,383					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,384					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,385				]386				.into_iter(),387			)388			.map_err(|err| match err {389				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),390				err => Self::map_unique_err_to_proxy(err),391			})?;392393			Self::deposit_event(Event::NftMinted {394				owner,395				collection_id,396				nft_id: nft_id.0,397			});398399			Ok(())400		}401402		/// burn nft403		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]404		#[transactional]405		pub fn burn_nft(406			origin: OriginFor<T>,407			collection_id: RmrkCollectionId,408			nft_id: RmrkNftId,409			max_burns: u32,410		) -> DispatchResult {411			let sender = ensure_signed(origin)?;412			let cross_sender = T::CrossAccountId::from_sub(sender.clone());413414			let collection = Self::get_typed_nft_collection(415				Self::unique_collection_id(collection_id)?,416				misc::CollectionType::Regular,417			)?;418			collection.check_is_external()?;419420			Self::destroy_nft(421				cross_sender,422				Self::unique_collection_id(collection_id)?,423				nft_id.into(),424				max_burns,425				<Error<T>>::NoPermission,426			)427			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;428429			Self::deposit_event(Event::NFTBurned {430				owner: sender,431				nft_id,432			});433434			Ok(())435		}436437		/// Transfers a NFT from an Account or NFT A to another Account or NFT B438		///439		/// Parameters:440		/// - `origin`: sender of the transaction441		/// - `rmrk_collection_id`: collection id of the nft to be transferred442		/// - `rmrk_nft_id`: nft id of the nft to be transferred443		/// - `new_owner`: new owner of the nft which can be either an account or a NFT444		#[transactional]445		#[pallet::weight(<SelfWeightOf<T>>::send())]446		pub fn send(447			origin: OriginFor<T>,448			rmrk_collection_id: RmrkCollectionId,449			rmrk_nft_id: RmrkNftId,450			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,451		) -> DispatchResult {452			let sender = ensure_signed(origin.clone())?;453			let cross_sender = T::CrossAccountId::from_sub(sender.clone());454455			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;456			let nft_id = rmrk_nft_id.into();457458			let collection =459				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;460			collection.check_is_external()?;461462			let token_data =463				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;464465			let from = token_data.owner;466467			ensure!(468				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,469				<Error<T>>::NonTransferable470			);471472			ensure!(473				!Self::get_nft_property_decoded(474					collection_id,475					nft_id,476					RmrkProperty::PendingNftAccept477				)?,478				<Error<T>>::NoPermission479			);480481			let target_owner;482			let approval_required;483484			match new_owner {485				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {486					target_owner = T::CrossAccountId::from_sub(account_id.clone());487					approval_required = false;488				}489				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(490					target_collection_id,491					target_nft_id,492				) => {493					let target_collection_id = Self::unique_collection_id(target_collection_id)?;494495					let target_nft_budget = budget::Value::new(NESTING_BUDGET);496497					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(498						target_collection_id,499						target_nft_id.into(),500						Some((collection_id, nft_id)),501						&target_nft_budget,502					)503					.map_err(Self::map_unique_err_to_proxy)?;504505					approval_required = cross_sender != target_nft_owner;506507					if approval_required {508						target_owner = target_nft_owner;509510						<PalletNft<T>>::set_scoped_token_property(511							collection.id,512							nft_id,513							PropertyScope::Rmrk,514							Self::rmrk_property(PendingNftAccept, &approval_required)?,515						)?;516					} else {517						target_owner = T::CrossTokenAddressMapping::token_to_address(518							target_collection_id,519							target_nft_id.into(),520						);521					}522				}523			}524525			let src_nft_budget = budget::Value::new(NESTING_BUDGET);526527			<PalletNft<T>>::transfer_from(528				&collection,529				&cross_sender,530				&from,531				&target_owner,532				nft_id,533				&src_nft_budget,534			)535			.map_err(Self::map_unique_err_to_proxy)?;536537			Self::deposit_event(Event::NFTSent {538				sender,539				recipient: new_owner,540				collection_id: rmrk_collection_id,541				nft_id: rmrk_nft_id,542				approval_required,543			});544545			Ok(())546		}547548		/// Accepts an NFT sent from another account to self or owned NFT549		///550		/// Parameters:551		/// - `origin`: sender of the transaction552		/// - `rmrk_collection_id`: collection id of the nft to be accepted553		/// - `rmrk_nft_id`: nft id of the nft to be accepted554		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was555		///   sent to556		#[transactional]557		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]558		pub fn accept_nft(559			origin: OriginFor<T>,560			rmrk_collection_id: RmrkCollectionId,561			rmrk_nft_id: RmrkNftId,562			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,563		) -> DispatchResult {564			let sender = ensure_signed(origin.clone())?;565			let cross_sender = T::CrossAccountId::from_sub(sender.clone());566567			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;568			let nft_id = rmrk_nft_id.into();569570			let collection =571				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;572			collection.check_is_external()?;573574			let new_cross_owner = match new_owner {575				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {576					T::CrossAccountId::from_sub(account_id.clone())577				}578				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(579					target_collection_id,580					target_nft_id,581				) => {582					let target_collection_id = Self::unique_collection_id(target_collection_id)?;583584					T::CrossTokenAddressMapping::token_to_address(585						target_collection_id,586						TokenId(target_nft_id),587					)588				}589			};590591			let budget = budget::Value::new(NESTING_BUDGET);592593			<PalletNft<T>>::transfer(594				&collection,595				&cross_sender,596				&new_cross_owner,597				nft_id,598				&budget,599			)600			.map_err(|err| {601				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {602					<Error<T>>::CannotAcceptNonOwnedNft.into()603				} else {604					Self::map_unique_err_to_proxy(err)605				}606			})?;607608			<PalletNft<T>>::set_scoped_token_property(609				collection.id,610				nft_id,611				PropertyScope::Rmrk,612				Self::rmrk_property(PendingNftAccept, &false)?,613			)?;614615			Self::deposit_event(Event::NFTAccepted {616				sender,617				recipient: new_owner,618				collection_id: rmrk_collection_id,619				nft_id: rmrk_nft_id,620			});621622			Ok(())623		}624625		/// Rejects an NFT sent from another account to self or owned NFT626		///627		/// Parameters:628		/// - `origin`: sender of the transaction629		/// - `rmrk_collection_id`: collection id of the nft to be accepted630		/// - `rmrk_nft_id`: nft id of the nft to be accepted631		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]632		#[transactional]633		pub fn reject_nft(634			origin: OriginFor<T>,635			rmrk_collection_id: RmrkCollectionId,636			rmrk_nft_id: RmrkNftId,637		) -> DispatchResult {638			let sender = ensure_signed(origin)?;639			let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;642			let nft_id = rmrk_nft_id.into();643644			let collection =645				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;646			collection.check_is_external()?;647648			Self::destroy_nft(649				cross_sender,650				collection_id,651				nft_id,652				NESTING_BUDGET,653				<Error<T>>::CannotRejectNonOwnedNft,654			)655			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;656657			Self::deposit_event(Event::NFTRejected {658				sender,659				collection_id: rmrk_collection_id,660				nft_id: rmrk_nft_id,661			});662663			Ok(())664		}665666		/// accept the addition of a new resource to an existing NFT667		#[transactional]668		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]669		pub fn accept_resource(670			origin: OriginFor<T>,671			rmrk_collection_id: RmrkCollectionId,672			rmrk_nft_id: RmrkNftId,673			rmrk_resource_id: RmrkResourceId,674		) -> DispatchResult {675			let sender = ensure_signed(origin)?;676			let cross_sender = T::CrossAccountId::from_sub(sender);677678			let collection_id = Self::unique_collection_id(rmrk_collection_id)679				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;680			let collection =681				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;682			collection.check_is_external()?;683684			let nft_id = rmrk_nft_id.into();685			let resource_id = rmrk_resource_id.into();686687			let budget = budget::Value::new(NESTING_BUDGET);688689			let nft_owner =690				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)691					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;692693			let resource_collection_id: Option<CollectionId> =694				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)695					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;696697			let resource_collection_id =698				resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;699700			let is_pending: bool = Self::get_nft_property_decoded(701				resource_collection_id,702				resource_id,703				PendingResourceAccept,704			)705			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707			ensure!(is_pending, <Error<T>>::ResourceNotPending);708709			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711			<PalletNft<T>>::set_scoped_token_property(712				resource_collection_id,713				rmrk_resource_id.into(),714				PropertyScope::Rmrk,715				Self::rmrk_property(PendingResourceAccept, &false)?,716			)?;717718			Self::deposit_event(Event::<T>::ResourceAccepted {719				nft_id: rmrk_nft_id,720				resource_id: rmrk_resource_id,721			});722723			Ok(())724		}725726		/// accept the removal of a resource of an existing NFT727		#[transactional]728		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]729		pub fn accept_resource_removal(730			origin: OriginFor<T>,731			rmrk_collection_id: RmrkCollectionId,732			rmrk_nft_id: RmrkNftId,733			rmrk_resource_id: RmrkResourceId,734		) -> DispatchResult {735			let sender = ensure_signed(origin)?;736			let cross_sender = T::CrossAccountId::from_sub(sender);737738			let collection_id = Self::unique_collection_id(rmrk_collection_id)739				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;740			let collection =741				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742			collection.check_is_external()?;743744			let nft_id = rmrk_nft_id.into();745			let resource_id = rmrk_resource_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755			let resource_collection_id: Option<CollectionId> =756				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759			let resource_collection_id =760				resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;761762			let is_pending: bool = Self::get_nft_property_decoded(763				resource_collection_id,764				resource_id,765				PendingResourceRemoval,766			)767			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;768769			ensure!(is_pending, <Error<T>>::ResourceNotPending);770771			let resource_collection = Self::get_typed_nft_collection(772				resource_collection_id,773				misc::CollectionType::Resource,774			)?;775776			let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))777				.ok_or(<Error<T>>::ResourceDoesntExist)?;778779			let resource_owner = resource_data.owner;780781			<PalletNft<T>>::burn(782				&resource_collection,783				&resource_owner,784				rmrk_resource_id.into(),785			)786			.map_err(Self::map_unique_err_to_proxy)?;787788			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {789				nft_id: rmrk_nft_id,790				resource_id: rmrk_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				[916					Self::rmrk_property(TokenType, &NftType::Resource)?,917					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,918					Self::rmrk_property(Src, &resource.src)?,919					Self::rmrk_property(Metadata, &resource.metadata)?,920					Self::rmrk_property(License, &resource.license)?,921					Self::rmrk_property(Thumb, &resource.thumb)?,922				]923				.into_iter(),924			)?;925926			Self::deposit_event(Event::ResourceAdded {927				nft_id,928				resource_id,929			});930			Ok(())931		}932933		/// Create composable resource934		#[transactional]935		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]936		pub fn add_composable_resource(937			origin: OriginFor<T>,938			rmrk_collection_id: RmrkCollectionId,939			nft_id: RmrkNftId,940			resource: RmrkComposableResource,941		) -> DispatchResult {942			let sender = ensure_signed(origin.clone())?;943944			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;945			let collection =946				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;947			collection.check_is_external()?;948949			let resource_id = Self::resource_add(950				sender,951				collection_id,952				nft_id.into(),953				[954					Self::rmrk_property(TokenType, &NftType::Resource)?,955					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,956					Self::rmrk_property(Parts, &resource.parts)?,957					Self::rmrk_property(Base, &resource.base)?,958					Self::rmrk_property(Src, &resource.src)?,959					Self::rmrk_property(Metadata, &resource.metadata)?,960					Self::rmrk_property(License, &resource.license)?,961					Self::rmrk_property(Thumb, &resource.thumb)?,962				]963				.into_iter(),964			)?;965966			Self::deposit_event(Event::ResourceAdded {967				nft_id,968				resource_id,969			});970			Ok(())971		}972973		/// Create slot resource974		#[transactional]975		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]976		pub fn add_slot_resource(977			origin: OriginFor<T>,978			rmrk_collection_id: RmrkCollectionId,979			nft_id: RmrkNftId,980			resource: RmrkSlotResource,981		) -> DispatchResult {982			let sender = ensure_signed(origin.clone())?;983984			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;985			let collection =986				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;987			collection.check_is_external()?;988989			let resource_id = Self::resource_add(990				sender,991				collection_id,992				nft_id.into(),993				[994					Self::rmrk_property(TokenType, &NftType::Resource)?,995					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,996					Self::rmrk_property(Base, &resource.base)?,997					Self::rmrk_property(Src, &resource.src)?,998					Self::rmrk_property(Metadata, &resource.metadata)?,999					Self::rmrk_property(Slot, &resource.slot)?,1000					Self::rmrk_property(License, &resource.license)?,1001					Self::rmrk_property(Thumb, &resource.thumb)?,1002				]1003				.into_iter(),1004			)?;10051006			Self::deposit_event(Event::ResourceAdded {1007				nft_id,1008				resource_id,1009			});1010			Ok(())1011		}10121013		/// remove resource1014		#[transactional]1015		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1016		pub fn remove_resource(1017			origin: OriginFor<T>,1018			rmrk_collection_id: RmrkCollectionId,1019			nft_id: RmrkNftId,1020			resource_id: RmrkResourceId,1021		) -> DispatchResult {1022			let sender = ensure_signed(origin.clone())?;10231024			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1025			let collection =1026				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1027			collection.check_is_external()?;10281029			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10301031			Self::deposit_event(Event::ResourceRemoval {1032				nft_id,1033				resource_id,1034			});1035			Ok(())1036		}1037	}1038}10391040impl<T: Config> Pallet<T> {1041	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1042		let key = rmrk_key.to_key::<T>()?;10431044		let scoped_key = PropertyScope::Rmrk1045			.apply(key)1046			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10471048		Ok(scoped_key)1049	}10501051	// todo think about renaming these1052	pub fn rmrk_property<E: Encode>(1053		rmrk_key: RmrkProperty,1054		value: &E,1055	) -> Result<Property, DispatchError> {1056		let key = rmrk_key.to_key::<T>()?;10571058		let value = value1059			.encode()1060			.try_into()1061			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10621063		let property = Property { key, value };10641065		Ok(property)1066	}10671068	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1069		vec.decode()1070			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1071	}10721073	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1074	where1075		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1076	{1077		vec.rebind()1078			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1079	}10801081	fn init_collection(1082		sender: T::CrossAccountId,1083		data: CreateCollectionData<T::AccountId>,1084		properties: impl Iterator<Item = Property>,1085	) -> Result<CollectionId, DispatchError> {1086		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10871088		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1089			return Err(<Error<T>>::NoAvailableCollectionId.into());1090		}10911092		<PalletCommon<T>>::set_scoped_collection_properties(1093			collection_id?,1094			PropertyScope::Rmrk,1095			properties,1096		)?;10971098		collection_id1099	}11001101	pub fn create_nft(1102		sender: &T::CrossAccountId,1103		owner: &T::CrossAccountId,1104		collection: &NonfungibleHandle<T>,1105		properties: impl Iterator<Item = Property>,1106	) -> Result<TokenId, DispatchError> {1107		let data = CreateNftExData {1108			properties: BoundedVec::default(),1109			owner: owner.clone(),1110		};11111112		let budget = budget::Value::new(NESTING_BUDGET);11131114		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11151116		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11171118		<PalletNft<T>>::set_scoped_token_properties(1119			collection.id,1120			nft_id,1121			PropertyScope::Rmrk,1122			properties,1123		)?;11241125		Ok(nft_id)1126	}11271128	fn destroy_nft(1129		sender: T::CrossAccountId,1130		collection_id: CollectionId,1131		token_id: TokenId,1132		max_burns: u32,1133		error_if_not_owned: Error<T>,1134	) -> DispatchResultWithPostInfo {1135		let collection =1136			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11371138		let token_data =1139			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11401141		let from = token_data.owner;11421143		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11441145		ensure!(1146			<PalletStructure<T>>::check_indirectly_owned(1147				sender.clone(),1148				collection_id,1149				token_id,1150				None,1151				&owner_check_budget1152			)?,1153			error_if_not_owned,1154		);11551156		let burns_budget = budget::Value::new(max_burns);1157		let breadth_budget = budget::Value::new(max_burns);11581159		<PalletNft<T>>::burn_recursively(1160			&collection,1161			&from,1162			token_id,1163			&burns_budget,1164			&breadth_budget,1165		)1166	}11671168	fn resource_add(1169		sender: T::AccountId,1170		collection_id: CollectionId,1171		token_id: TokenId,1172		resource_properties: impl Iterator<Item = Property>,1173	) -> Result<RmrkResourceId, DispatchError> {1174		let collection =1175			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1176		ensure!(collection.owner == sender, Error::<T>::NoPermission);11771178		let sender = T::CrossAccountId::from_sub(sender);1179		let budget = budget::Value::new(NESTING_BUDGET);11801181		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1182			.map_err(Self::map_unique_err_to_proxy)?;11831184		let pending = sender != nft_owner;11851186		let resource_collection_id: Option<CollectionId> =1187			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11881189		let resource_collection_id = match resource_collection_id {1190			Some(id) => id,1191			None => {1192				let resource_collection_id = Self::init_collection(1193					sender.clone(),1194					CreateCollectionData {1195						..Default::default()1196					},1197					[Self::rmrk_property(1198						CollectionType,1199						&misc::CollectionType::Resource,1200					)?]1201					.into_iter(),1202				)?;12031204				<PalletNft<T>>::set_scoped_token_property(1205					collection_id,1206					token_id,1207					PropertyScope::Rmrk,1208					Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1209				)?;12101211				resource_collection_id1212			}1213		};12141215		let resource_collection =1216			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12171218		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12191220		let resource_id = Self::create_nft(1221			&sender,1222			&nft_owner,1223			&resource_collection,1224			resource_properties.chain(1225				[1226					Self::rmrk_property(PendingResourceAccept, &pending)?,1227					Self::rmrk_property(PendingResourceRemoval, &false)?,1228				]1229				.into_iter(),1230			),1231		)1232		.map_err(|err| match err {1233			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1234			err => Self::map_unique_err_to_proxy(err),1235		})?;12361237		Ok(resource_id.0)1238	}12391240	fn resource_remove(1241		sender: T::AccountId,1242		collection_id: CollectionId,1243		nft_id: TokenId,1244		resource_id: TokenId,1245	) -> DispatchResult {1246		let collection =1247			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1248		ensure!(collection.owner == sender, Error::<T>::NoPermission);12491250		let resource_collection_id: Option<CollectionId> =1251			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12521253		let resource_collection_id =1254			resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;12551256		let resource_collection =1257			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1258		ensure!(1259			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1260			Error::<T>::ResourceDoesntExist1261		);12621263		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1264		let topmost_owner =1265			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12661267		let sender = T::CrossAccountId::from_sub(sender);1268		if topmost_owner == sender {1269			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1270				.map_err(Self::map_unique_err_to_proxy)?;1271		} else {1272			<PalletNft<T>>::set_scoped_token_property(1273				resource_collection_id,1274				resource_id,1275				PropertyScope::Rmrk,1276				Self::rmrk_property(PendingResourceRemoval, &true)?,1277			)?;1278		}12791280		Ok(())1281	}12821283	fn change_collection_owner(1284		collection_id: CollectionId,1285		collection_type: misc::CollectionType,1286		sender: T::AccountId,1287		new_owner: T::AccountId,1288	) -> DispatchResult {1289		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1290		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12911292		let mut collection = collection.into_inner();12931294		collection.owner = new_owner;1295		collection.save()1296	}12971298	fn check_collection_owner(1299		collection: &NonfungibleHandle<T>,1300		account: &T::CrossAccountId,1301	) -> DispatchResult {1302		collection1303			.check_is_owner(account)1304			.map_err(Self::map_unique_err_to_proxy)1305	}13061307	pub fn last_collection_idx() -> RmrkCollectionId {1308		<CollectionIndex<T>>::get()1309	}13101311	pub fn unique_collection_id(1312		rmrk_collection_id: RmrkCollectionId,1313	) -> Result<CollectionId, DispatchError> {1314		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1315			.map_err(|_| <Error<T>>::CollectionUnknown.into())1316	}13171318	pub fn rmrk_collection_id(1319		unique_collection_id: CollectionId,1320	) -> Result<RmrkCollectionId, DispatchError> {1321		Self::get_collection_property_decoded(1322			unique_collection_id,1323			RmrkInternalCollectionId1324		)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>>::NoAvailableNftId)? // todo replace with better error?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}