git.delta.rocks / unique-network / refs/commits / 51e15a07c4a1

difftreelog

fix(rmrk) include pending children into nft_children RPC

Daniel Shiposha2022-06-30parent: #66c5541.patch.diff
in: master

3 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 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,208						#[cfg(feature = "runtime-benchmarks")]209						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	pub 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).map_err(|err| {1366			if err != <Error<T>>::CollectionUnknown.into() {1367				<Error<T>>::CorruptedCollectionType.into()1368			} else {1369				err1370			}1371		})1372	}13731374	pub fn ensure_collection_type(1375		collection_id: CollectionId,1376		collection_type: misc::CollectionType,1377	) -> DispatchResult {1378		let actual_type = Self::get_collection_type(collection_id)?;1379		ensure!(1380			actual_type == collection_type,1381			<CommonError<T>>::NoPermission1382		);13831384		Ok(())1385	}13861387	pub fn get_typed_nft_collection(1388		collection_id: CollectionId,1389		collection_type: misc::CollectionType,1390	) -> Result<NonfungibleHandle<T>, DispatchError> {1391		Self::ensure_collection_type(collection_id, collection_type)?;13921393		Self::get_nft_collection(collection_id)1394	}13951396	pub fn get_typed_nft_collection_mapped(1397		rmrk_collection_id: RmrkCollectionId,1398		collection_type: misc::CollectionType,1399	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1400		let unique_collection_id = match collection_type {1401			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1402			_ => rmrk_collection_id.into(),1403		};14041405		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14061407		Ok((collection, unique_collection_id))1408	}14091410	pub fn get_nft_property(1411		collection_id: CollectionId,1412		nft_id: TokenId,1413		key: RmrkProperty,1414	) -> Result<PropertyValue, DispatchError> {1415		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1416			.get(&Self::rmrk_property_key(key)?)1417			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1418			.clone();14191420		Ok(nft_property)1421	}14221423	pub fn get_nft_property_decoded<V: Decode>(1424		collection_id: CollectionId,1425		nft_id: TokenId,1426		key: RmrkProperty,1427	) -> Result<V, DispatchError> {1428		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1429	}14301431	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1432		<TokenData<T>>::contains_key((collection_id, nft_id))1433	}14341435	pub fn get_nft_type(1436		collection_id: CollectionId,1437		token_id: TokenId,1438	) -> Result<NftType, DispatchError> {1439		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1440			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1441	}14421443	pub fn ensure_nft_type(1444		collection_id: CollectionId,1445		token_id: TokenId,1446		nft_type: NftType,1447	) -> DispatchResult {1448		let actual_type = Self::get_nft_type(collection_id, token_id)?;1449		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14501451		Ok(())1452	}14531454	pub fn ensure_nft_owner(1455		collection_id: CollectionId,1456		token_id: TokenId,1457		possible_owner: &T::CrossAccountId,1458		nesting_budget: &dyn budget::Budget,1459	) -> DispatchResult {1460		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1461			possible_owner.clone(),1462			collection_id,1463			token_id,1464			None,1465			nesting_budget,1466		)1467		.map_err(Self::map_unique_err_to_proxy)?;14681469		ensure!(is_owned, <Error<T>>::NoPermission);14701471		Ok(())1472	}14731474	pub fn filter_user_properties<Key, Value, R, Mapper>(1475		collection_id: CollectionId,1476		token_id: Option<TokenId>,1477		filter_keys: Option<Vec<RmrkPropertyKey>>,1478		mapper: Mapper,1479	) -> Result<Vec<R>, DispatchError>1480	where1481		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1482		Value: Decode + Default,1483		Mapper: Fn(Key, Value) -> R,1484	{1485		filter_keys1486			.map(|keys| {1487				let properties = keys1488					.into_iter()1489					.filter_map(|key| {1490						let key: Key = key.try_into().ok()?;14911492						let value = match token_id {1493							Some(token_id) => Self::get_nft_property_decoded(1494								collection_id,1495								token_id,1496								UserProperty(key.as_ref()),1497							),1498							None => Self::get_collection_property_decoded(1499								collection_id,1500								UserProperty(key.as_ref()),1501							),1502						}1503						.ok()?;15041505						Some(mapper(key, value))1506					})1507					.collect();15081509				Ok(properties)1510			})1511			.unwrap_or_else(|| {1512				let properties =1513					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15141515				Ok(properties)1516			})1517	}15181519	pub fn iterate_user_properties<Key, Value, R, Mapper>(1520		collection_id: CollectionId,1521		token_id: Option<TokenId>,1522		mapper: Mapper,1523	) -> Result<impl Iterator<Item = R>, DispatchError>1524	where1525		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1526		Value: Decode + Default,1527		Mapper: Fn(Key, Value) -> R,1528	{1529		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15301531		let properties = match token_id {1532			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1533			None => <PalletCommon<T>>::collection_properties(collection_id),1534		};15351536		let properties = properties.into_iter().filter_map(move |(key, value)| {1537			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15381539			let key: Key = key.to_vec().try_into().ok()?;1540			let value: Value = value.decode().ok()?;15411542			Some(mapper(key, value))1543		});15441545		Ok(properties)1546	}15471548	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1549		map_unique_err_to_proxy! {1550			match err {1551				CommonError::NoPermission => NoPermission,1552				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1553				CommonError::PublicMintingNotAllowed => NoPermission,1554				CommonError::TokenNotFound => NoAvailableNftId,1555				CommonError::ApprovedValueTooLow => NoPermission,1556				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1557				StructureError::TokenNotFound => NoAvailableNftId,1558				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1559			}1560		}1561	}1562}
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::{20	pallet_prelude::*,21	transactional,22	BoundedVec,23	dispatch::DispatchResult,24};25use frame_system::{pallet_prelude::*, ensure_signed};26use sp_runtime::{DispatchError, Permill, traits::StaticLookup};27use sp_std::{vec::Vec, collections::btree_set::BTreeSet};28use up_data_structs::{*, mapping::TokenAddressMapping};29use pallet_common::{30	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,31};32use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};33use pallet_structure::{Pallet as PalletStructure, Error as StructureError};34use pallet_evm::account::CrossAccountId;35use core::convert::AsRef;3637pub use pallet::*;3839#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod misc;42pub mod property;43pub mod rpc;44pub mod weights;4546pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4748use weights::WeightInfo;49use misc::*;50pub use property::*;5152use RmrkProperty::*;5354pub const NESTING_BUDGET: u32 = 5;5556type PendingTarget = (CollectionId, TokenId);57type PendingChild = (RmrkCollectionId, RmrkNftId);58type PendingChildrenMap = BTreeSet<PendingChild>;5960#[frame_support::pallet]61pub mod pallet {62	use super::*;63	use pallet_evm::account;6465	#[pallet::config]66	pub trait Config:67		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config68	{69		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;70		type WeightInfo: WeightInfo;71	}7273	#[pallet::storage]74	#[pallet::getter(fn collection_index)]75	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7677	#[pallet::storage]78	pub type UniqueCollectionId<T: Config> =79		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8081	#[pallet::pallet]82	#[pallet::generate_store(pub(super) trait Store)]83	pub struct Pallet<T>(_);8485	#[pallet::event]86	#[pallet::generate_deposit(pub(super) fn deposit_event)]87	pub enum Event<T: Config> {88		CollectionCreated {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		CollectionDestroyed {93			issuer: T::AccountId,94			collection_id: RmrkCollectionId,95		},96		IssuerChanged {97			old_issuer: T::AccountId,98			new_issuer: T::AccountId,99			collection_id: RmrkCollectionId,100		},101		CollectionLocked {102			issuer: T::AccountId,103			collection_id: RmrkCollectionId,104		},105		NftMinted {106			owner: T::AccountId,107			collection_id: RmrkCollectionId,108			nft_id: RmrkNftId,109		},110		NFTBurned {111			owner: T::AccountId,112			nft_id: RmrkNftId,113		},114		NFTSent {115			sender: T::AccountId,116			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,117			collection_id: RmrkCollectionId,118			nft_id: RmrkNftId,119			approval_required: bool,120		},121		NFTAccepted {122			sender: T::AccountId,123			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,124			collection_id: RmrkCollectionId,125			nft_id: RmrkNftId,126		},127		NFTRejected {128			sender: T::AccountId,129			collection_id: RmrkCollectionId,130			nft_id: RmrkNftId,131		},132		PropertySet {133			collection_id: RmrkCollectionId,134			maybe_nft_id: Option<RmrkNftId>,135			key: RmrkKeyString,136			value: RmrkValueString,137		},138		ResourceAdded {139			nft_id: RmrkNftId,140			resource_id: RmrkResourceId,141		},142		ResourceRemoval {143			nft_id: RmrkNftId,144			resource_id: RmrkResourceId,145		},146		ResourceAccepted {147			nft_id: RmrkNftId,148			resource_id: RmrkResourceId,149		},150		ResourceRemovalAccepted {151			nft_id: RmrkNftId,152			resource_id: RmrkResourceId,153		},154		PrioritySet {155			collection_id: RmrkCollectionId,156			nft_id: RmrkNftId,157		},158	}159160	#[pallet::error]161	pub enum Error<T> {162		/* Unique-specific events */163		CorruptedCollectionType,164		NftTypeEncodeError,165		RmrkPropertyKeyIsTooLong,166		RmrkPropertyValueIsTooLong,167		RmrkPropertyIsNotFound,168		UnableToDecodeRmrkData,169170		/* RMRK compatible events */171		CollectionNotEmpty,172		NoAvailableCollectionId,173		NoAvailableNftId,174		CollectionUnknown,175		NoPermission,176		NonTransferable,177		CollectionFullOrLocked,178		ResourceDoesntExist,179		CannotSendToDescendentOrSelf,180		CannotAcceptNonOwnedNft,181		CannotRejectNonOwnedNft,182		CannotRejectNonPendingNft,183		ResourceNotPending,184		NoAvailableResourceId,185	}186187	#[pallet::call]188	impl<T: Config> Pallet<T> {189		/// Create a collection190		#[transactional]191		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]192		pub fn create_collection(193			origin: OriginFor<T>,194			metadata: RmrkString,195			max: Option<u32>,196			symbol: RmrkCollectionSymbol,197		) -> DispatchResult {198			let sender = ensure_signed(origin)?;199200			let limits = CollectionLimits {201				owner_can_transfer: Some(false),202				token_limit: max,203				..Default::default()204			};205206			let data = CreateCollectionData {207				limits: Some(limits),208				token_prefix: symbol209					.into_inner()210					.try_into()211					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,212				permissions: Some(CollectionPermissions {213					nesting: Some(NestingPermissions {214						token_owner: true,215						collection_admin: false,216						restricted: None,217						#[cfg(feature = "runtime-benchmarks")]218						permissive: false,219					}),220					..Default::default()221				}),222				..Default::default()223			};224225			let unique_collection_id = Self::init_collection(226				T::CrossAccountId::from_sub(sender.clone()),227				data,228				[229					Self::rmrk_property(Metadata, &metadata)?,230					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,231				]232				.into_iter(),233			)?;234			let rmrk_collection_id = <CollectionIndex<T>>::get();235236			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);237238			<PalletCommon<T>>::set_scoped_collection_property(239				unique_collection_id,240				PropertyScope::Rmrk,241				Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,242			)?;243244			<CollectionIndex<T>>::mutate(|n| *n += 1);245246			Self::deposit_event(Event::CollectionCreated {247				issuer: sender,248				collection_id: rmrk_collection_id,249			});250251			Ok(())252		}253254		/// destroy collection255		#[transactional]256		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]257		pub fn destroy_collection(258			origin: OriginFor<T>,259			collection_id: RmrkCollectionId,260		) -> DispatchResult {261			let sender = ensure_signed(origin)?;262			let cross_sender = T::CrossAccountId::from_sub(sender.clone());263264			let collection = Self::get_typed_nft_collection(265				Self::unique_collection_id(collection_id)?,266				misc::CollectionType::Regular,267			)?;268			collection.check_is_external()?;269270			<PalletNft<T>>::destroy_collection(collection, &cross_sender)271				.map_err(Self::map_unique_err_to_proxy)?;272273			Self::deposit_event(Event::CollectionDestroyed {274				issuer: sender,275				collection_id,276			});277278			Ok(())279		}280281		/// Change the issuer of a collection282		///283		/// Parameters:284		/// - `origin`: sender of the transaction285		/// - `collection_id`: collection id of the nft to change issuer of286		/// - `new_issuer`: Collection's new issuer287		#[transactional]288		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]289		pub fn change_collection_issuer(290			origin: OriginFor<T>,291			collection_id: RmrkCollectionId,292			new_issuer: <T::Lookup as StaticLookup>::Source,293		) -> DispatchResult {294			let sender = ensure_signed(origin)?;295296			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;297			collection.check_is_external()?;298299			let new_issuer = T::Lookup::lookup(new_issuer)?;300301			Self::change_collection_owner(302				Self::unique_collection_id(collection_id)?,303				misc::CollectionType::Regular,304				sender.clone(),305				new_issuer.clone(),306			)?;307308			Self::deposit_event(Event::IssuerChanged {309				old_issuer: sender,310				new_issuer,311				collection_id,312			});313314			Ok(())315		}316317		/// lock collection318		#[transactional]319		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]320		pub fn lock_collection(321			origin: OriginFor<T>,322			collection_id: RmrkCollectionId,323		) -> DispatchResult {324			let sender = ensure_signed(origin)?;325			let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327			let collection = Self::get_typed_nft_collection(328				Self::unique_collection_id(collection_id)?,329				misc::CollectionType::Regular,330			)?;331			collection.check_is_external()?;332333			Self::check_collection_owner(&collection, &cross_sender)?;334335			let token_count = collection.total_supply();336337			let mut collection = collection.into_inner();338			collection.limits.token_limit = Some(token_count);339			collection.save()?;340341			Self::deposit_event(Event::CollectionLocked {342				issuer: sender,343				collection_id,344			});345346			Ok(())347		}348349		/// Mints an NFT in the specified collection350		/// Sets metadata and the royalty attribute351		///352		/// Parameters:353		/// - `collection_id`: The class of the asset to be minted.354		/// - `nft_id`: The nft value of the asset to be minted.355		/// - `recipient`: Receiver of the royalty356		/// - `royalty`: Permillage reward from each trade for the Recipient357		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash358		/// - `transferable`: Ability to transfer this NFT359		#[transactional]360		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]361		pub fn mint_nft(362			origin: OriginFor<T>,363			owner: Option<T::AccountId>,364			collection_id: RmrkCollectionId,365			recipient: Option<T::AccountId>,366			royalty_amount: Option<Permill>,367			metadata: RmrkString,368			transferable: bool,369			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,370		) -> DispatchResult {371			let sender = ensure_signed(origin)?;372			let cross_sender = T::CrossAccountId::from_sub(sender.clone());373374			let owner = owner.unwrap_or(sender.clone());375			let cross_owner = T::CrossAccountId::from_sub(owner.clone());376377			let collection = Self::get_typed_nft_collection(378				Self::unique_collection_id(collection_id)?,379				misc::CollectionType::Regular,380			)?;381			collection.check_is_external()?;382383			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {384				recipient: recipient.unwrap_or_else(|| owner.clone()),385				amount,386			});387388			let nft_id = Self::create_nft(389				&cross_sender,390				&cross_owner,391				&collection,392				[393					Self::rmrk_property(TokenType, &NftType::Regular)?,394					Self::rmrk_property(Transferable, &transferable)?,395					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,396					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,397					Self::rmrk_property(Metadata, &metadata)?,398					Self::rmrk_property(Equipped, &false)?,399					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,400					Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,401					Self::rmrk_property(PendingChildren, &PendingChildrenMap::new())?,402				]403				.into_iter(),404			)405			.map_err(|err| match err {406				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),407				err => Self::map_unique_err_to_proxy(err),408			})?;409410			if let Some(resources) = resources {411				for resource in resources {412					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;413				}414			}415416			Self::deposit_event(Event::NftMinted {417				owner,418				collection_id,419				nft_id: nft_id.0,420			});421422			Ok(())423		}424425		/// burn nft426		#[transactional]427		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]428		pub fn burn_nft(429			origin: OriginFor<T>,430			collection_id: RmrkCollectionId,431			nft_id: RmrkNftId,432			max_burns: u32,433		) -> DispatchResult {434			let sender = ensure_signed(origin)?;435			let cross_sender = T::CrossAccountId::from_sub(sender.clone());436437			let collection = Self::get_typed_nft_collection(438				Self::unique_collection_id(collection_id)?,439				misc::CollectionType::Regular,440			)?;441			collection.check_is_external()?;442443			Self::destroy_nft(444				cross_sender,445				Self::unique_collection_id(collection_id)?,446				nft_id.into(),447				max_burns,448				<Error<T>>::NoPermission,449			)450			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;451452			Self::deposit_event(Event::NFTBurned {453				owner: sender,454				nft_id,455			});456457			Ok(())458		}459460		/// Transfers a NFT from an Account or NFT A to another Account or NFT B461		///462		/// Parameters:463		/// - `origin`: sender of the transaction464		/// - `rmrk_collection_id`: collection id of the nft to be transferred465		/// - `rmrk_nft_id`: nft id of the nft to be transferred466		/// - `new_owner`: new owner of the nft which can be either an account or a NFT467		#[transactional]468		#[pallet::weight(<SelfWeightOf<T>>::send())]469		pub fn send(470			origin: OriginFor<T>,471			rmrk_collection_id: RmrkCollectionId,472			rmrk_nft_id: RmrkNftId,473			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,474		) -> DispatchResult {475			let sender = ensure_signed(origin.clone())?;476			let cross_sender = T::CrossAccountId::from_sub(sender.clone());477478			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;479			let nft_id = rmrk_nft_id.into();480481			let collection =482				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;483			collection.check_is_external()?;484485			let token_data =486				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;487488			let from = token_data.owner;489490			ensure!(491				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,492				<Error<T>>::NonTransferable493			);494495			ensure!(496				Self::get_nft_property_decoded::<Option<PendingTarget>>(497					collection_id,498					nft_id,499					RmrkProperty::PendingNftAccept500				)?.is_none(),501				<Error<T>>::NoPermission502			);503504			let target_owner;505			let approval_required;506507			match new_owner {508				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {509					target_owner = T::CrossAccountId::from_sub(account_id.clone());510					approval_required = false;511				}512				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(513					target_collection_id,514					target_nft_id,515				) => {516					let target_collection_id = Self::unique_collection_id(target_collection_id)?;517518					let target_nft_budget = budget::Value::new(NESTING_BUDGET);519520					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(521						target_collection_id,522						target_nft_id.into(),523						Some((collection_id, nft_id)),524						&target_nft_budget,525					)526					.map_err(Self::map_unique_err_to_proxy)?;527528					approval_required = cross_sender != target_nft_owner;529530					if approval_required {531						target_owner = target_nft_owner;532533						<PalletNft<T>>::set_scoped_token_property(534							collection.id,535							nft_id,536							PropertyScope::Rmrk,537							Self::rmrk_property::<Option<PendingTarget>>(538								PendingNftAccept,539								&Some((target_collection_id, target_nft_id.into()))540							)?,541						)?;542543						Self::insert_pending_child(544							(target_collection_id, target_nft_id.into()),545							(rmrk_collection_id, rmrk_nft_id),546						)?;547					} else {548						target_owner = T::CrossTokenAddressMapping::token_to_address(549							target_collection_id,550							target_nft_id.into(),551						);552					}553				}554			}555556			let src_nft_budget = budget::Value::new(NESTING_BUDGET);557558			<PalletNft<T>>::transfer_from(559				&collection,560				&cross_sender,561				&from,562				&target_owner,563				nft_id,564				&src_nft_budget,565			)566			.map_err(Self::map_unique_err_to_proxy)?;567568			Self::deposit_event(Event::NFTSent {569				sender,570				recipient: new_owner,571				collection_id: rmrk_collection_id,572				nft_id: rmrk_nft_id,573				approval_required,574			});575576			Ok(())577		}578579		/// Accepts an NFT sent from another account to self or owned NFT580		///581		/// Parameters:582		/// - `origin`: sender of the transaction583		/// - `rmrk_collection_id`: collection id of the nft to be accepted584		/// - `rmrk_nft_id`: nft id of the nft to be accepted585		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was586		///   sent to587		#[transactional]588		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]589		pub fn accept_nft(590			origin: OriginFor<T>,591			rmrk_collection_id: RmrkCollectionId,592			rmrk_nft_id: RmrkNftId,593			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,594		) -> DispatchResult {595			let sender = ensure_signed(origin.clone())?;596			let cross_sender = T::CrossAccountId::from_sub(sender.clone());597598			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;599			let nft_id = rmrk_nft_id.into();600601			let collection =602				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;603			collection.check_is_external()?;604605			let new_cross_owner = match new_owner {606				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {607					T::CrossAccountId::from_sub(account_id.clone())608				}609				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(610					target_collection_id,611					target_nft_id,612				) => {613					let target_collection_id = Self::unique_collection_id(target_collection_id)?;614615					T::CrossTokenAddressMapping::token_to_address(616						target_collection_id,617						TokenId(target_nft_id),618					)619				}620			};621622			let budget = budget::Value::new(NESTING_BUDGET);623624			<PalletNft<T>>::transfer(625				&collection,626				&cross_sender,627				&new_cross_owner,628				nft_id,629				&budget,630			)631			.map_err(|err| {632				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {633					<Error<T>>::CannotAcceptNonOwnedNft.into()634				} else {635					Self::map_unique_err_to_proxy(err)636				}637			})?;638639			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(640				collection_id,641				nft_id,642				RmrkProperty::PendingNftAccept643			)?;644645			if let Some(pending_target) = pending_target {646				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;647648				<PalletNft<T>>::set_scoped_token_property(649					collection.id,650					nft_id,651					PropertyScope::Rmrk,652					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,653				)?;654			}655656			Self::deposit_event(Event::NFTAccepted {657				sender,658				recipient: new_owner,659				collection_id: rmrk_collection_id,660				nft_id: rmrk_nft_id,661			});662663			Ok(())664		}665666		/// Rejects an NFT sent from another account to self or owned NFT667		///668		/// Parameters:669		/// - `origin`: sender of the transaction670		/// - `rmrk_collection_id`: collection id of the nft to be accepted671		/// - `rmrk_nft_id`: nft id of the nft to be accepted672		#[transactional]673		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]674		pub fn reject_nft(675			origin: OriginFor<T>,676			rmrk_collection_id: RmrkCollectionId,677			rmrk_nft_id: RmrkNftId,678		) -> DispatchResult {679			let sender = ensure_signed(origin)?;680			let cross_sender = T::CrossAccountId::from_sub(sender.clone());681682			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;683			let nft_id = rmrk_nft_id.into();684685			let collection =686				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;687			collection.check_is_external()?;688689			ensure!(690				<TokenData<T>>::get((collection_id, nft_id)).is_some(),691				<Error<T>>::NoAvailableNftId692			);693694695			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(696				collection_id,697				nft_id,698				RmrkProperty::PendingNftAccept699			)?;700701			match pending_target {702				Some(pending_target) => Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?,703				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),704			}705706			Self::destroy_nft(707				cross_sender,708				collection_id,709				nft_id,710				NESTING_BUDGET,711				<Error<T>>::CannotRejectNonOwnedNft,712			)713			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;714715			Self::deposit_event(Event::NFTRejected {716				sender,717				collection_id: rmrk_collection_id,718				nft_id: rmrk_nft_id,719			});720721			Ok(())722		}723724		/// accept the addition of a new resource to an existing NFT725		#[transactional]726		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]727		pub fn accept_resource(728			origin: OriginFor<T>,729			rmrk_collection_id: RmrkCollectionId,730			rmrk_nft_id: RmrkNftId,731			resource_id: RmrkResourceId,732		) -> DispatchResult {733			let sender = ensure_signed(origin)?;734			let cross_sender = T::CrossAccountId::from_sub(sender);735736			let collection_id = Self::unique_collection_id(rmrk_collection_id)737				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;738			let collection =739				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;740			collection.check_is_external()?;741742			let nft_id = rmrk_nft_id.into();743744			let budget = budget::Value::new(NESTING_BUDGET);745746			let nft_owner =747				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)748					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;749750			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {751				ensure!(res.pending, <Error<T>>::ResourceNotPending);752				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);753754				res.pending = false;755756				Ok(())757			})?;758759			Self::deposit_event(Event::<T>::ResourceAccepted {760				nft_id: rmrk_nft_id,761				resource_id,762			});763764			Ok(())765		}766767		/// accept the removal of a resource of an existing NFT768		#[transactional]769		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]770		pub fn accept_resource_removal(771			origin: OriginFor<T>,772			rmrk_collection_id: RmrkCollectionId,773			rmrk_nft_id: RmrkNftId,774			resource_id: RmrkResourceId,775		) -> DispatchResult {776			let sender = ensure_signed(origin)?;777			let cross_sender = T::CrossAccountId::from_sub(sender);778779			let collection_id = Self::unique_collection_id(rmrk_collection_id)780				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;781			let collection =782				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;783			collection.check_is_external()?;784785			let nft_id = rmrk_nft_id.into();786787			let budget = budget::Value::new(NESTING_BUDGET);788789			let nft_owner =790				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)791					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;792793			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);794795			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;796797			let resource_info = <PalletNft<T>>::token_aux_property((798				collection_id,799				nft_id,800				PropertyScope::Rmrk,801				resource_id_key.clone(),802			))803			.ok_or(<Error<T>>::ResourceDoesntExist)?;804805			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;806807			ensure!(808				resource_info.pending_removal,809				<Error<T>>::ResourceNotPending810			);811812			<PalletNft<T>>::remove_token_aux_property(813				collection_id,814				nft_id,815				PropertyScope::Rmrk,816				resource_id_key,817			);818819			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {820				nft_id: rmrk_nft_id,821				resource_id,822			});823824			Ok(())825		}826827		/// set a custom value on an NFT828		#[transactional]829		#[pallet::weight(<SelfWeightOf<T>>::set_property())]830		pub fn set_property(831			origin: OriginFor<T>,832			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,833			maybe_nft_id: Option<RmrkNftId>,834			key: RmrkKeyString,835			value: RmrkValueString,836		) -> DispatchResult {837			let sender = ensure_signed(origin)?;838			let sender = T::CrossAccountId::from_sub(sender);839840			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;841			let collection =842				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;843			collection.check_is_external()?;844845			let budget = budget::Value::new(NESTING_BUDGET);846847			match maybe_nft_id {848				Some(nft_id) => {849					let token_id: TokenId = nft_id.into();850851					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;852					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;853854					<PalletNft<T>>::set_scoped_token_property(855						collection_id,856						token_id,857						PropertyScope::Rmrk,858						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,859					)?;860				}861				None => {862					let collection = Self::get_typed_nft_collection(863						collection_id,864						misc::CollectionType::Regular,865					)?;866867					Self::check_collection_owner(&collection, &sender)?;868869					<PalletCommon<T>>::set_scoped_collection_property(870						collection_id,871						PropertyScope::Rmrk,872						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,873					)?;874				}875			}876877			Self::deposit_event(Event::PropertySet {878				collection_id: rmrk_collection_id,879				maybe_nft_id,880				key,881				value,882			});883884			Ok(())885		}886887		/// set a different order of resource priority888		#[transactional]889		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]890		pub fn set_priority(891			origin: OriginFor<T>,892			rmrk_collection_id: RmrkCollectionId,893			rmrk_nft_id: RmrkNftId,894			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,895		) -> DispatchResult {896			let sender = ensure_signed(origin)?;897			let sender = T::CrossAccountId::from_sub(sender);898899			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;900			let nft_id = rmrk_nft_id.into();901902			let collection =903				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;904			collection.check_is_external()?;905906			let budget = budget::Value::new(NESTING_BUDGET);907908			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;909			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;910911			<PalletNft<T>>::set_scoped_token_property(912				collection_id,913				nft_id,914				PropertyScope::Rmrk,915				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,916			)?;917918			Self::deposit_event(Event::<T>::PrioritySet {919				collection_id: rmrk_collection_id,920				nft_id: rmrk_nft_id,921			});922923			Ok(())924		}925926		/// Create basic resource927		#[transactional]928		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]929		pub fn add_basic_resource(930			origin: OriginFor<T>,931			rmrk_collection_id: RmrkCollectionId,932			nft_id: RmrkNftId,933			resource: RmrkBasicResource,934		) -> DispatchResult {935			let sender = ensure_signed(origin.clone())?;936937			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;938			let collection =939				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;940			collection.check_is_external()?;941942			let resource_id = Self::resource_add(943				sender,944				collection_id,945				nft_id.into(),946				RmrkResourceTypes::Basic(resource),947			)?;948949			Self::deposit_event(Event::ResourceAdded {950				nft_id,951				resource_id,952			});953			Ok(())954		}955956		/// Create composable resource957		#[transactional]958		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]959		pub fn add_composable_resource(960			origin: OriginFor<T>,961			rmrk_collection_id: RmrkCollectionId,962			nft_id: RmrkNftId,963			resource: RmrkComposableResource,964		) -> DispatchResult {965			let sender = ensure_signed(origin.clone())?;966967			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;968			let collection =969				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;970			collection.check_is_external()?;971972			let resource_id = Self::resource_add(973				sender,974				collection_id,975				nft_id.into(),976				RmrkResourceTypes::Composable(resource),977			)?;978979			Self::deposit_event(Event::ResourceAdded {980				nft_id,981				resource_id,982			});983			Ok(())984		}985986		/// Create slot resource987		#[transactional]988		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]989		pub fn add_slot_resource(990			origin: OriginFor<T>,991			rmrk_collection_id: RmrkCollectionId,992			nft_id: RmrkNftId,993			resource: RmrkSlotResource,994		) -> DispatchResult {995			let sender = ensure_signed(origin.clone())?;996997			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;998			let collection =999				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1000			collection.check_is_external()?;10011002			let resource_id = Self::resource_add(1003				sender,1004				collection_id,1005				nft_id.into(),1006				RmrkResourceTypes::Slot(resource),1007			)?;10081009			Self::deposit_event(Event::ResourceAdded {1010				nft_id,1011				resource_id,1012			});1013			Ok(())1014		}10151016		/// remove resource1017		#[transactional]1018		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1019		pub fn remove_resource(1020			origin: OriginFor<T>,1021			rmrk_collection_id: RmrkCollectionId,1022			nft_id: RmrkNftId,1023			resource_id: RmrkResourceId,1024		) -> DispatchResult {1025			let sender = ensure_signed(origin.clone())?;10261027			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1028			let collection =1029				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1030			collection.check_is_external()?;10311032			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10331034			Self::deposit_event(Event::ResourceRemoval {1035				nft_id,1036				resource_id,1037			});1038			Ok(())1039		}1040	}1041}10421043impl<T: Config> Pallet<T> {1044	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1045		let key = rmrk_key.to_key::<T>()?;10461047		let scoped_key = PropertyScope::Rmrk1048			.apply(key)1049			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10501051		Ok(scoped_key)1052	}10531054	// todo think about renaming these1055	pub fn rmrk_property<E: Encode>(1056		rmrk_key: RmrkProperty,1057		value: &E,1058	) -> Result<Property, DispatchError> {1059		let key = rmrk_key.to_key::<T>()?;10601061		let value = Self::encode_property(value)?;10621063		let property = Property { key, value };10641065		Ok(property)1066	}10671068	pub fn encode_property<E: Encode, S: Get<u32>>(1069		value: &E,1070	) -> Result<BoundedBytes<S>, DispatchError> {1071		let value = value1072			.encode()1073			.try_into()1074			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10751076		Ok(value)1077	}10781079	pub fn decode_property<D: Decode, S: Get<u32>>(1080		vec: &BoundedBytes<S>,1081	) -> Result<D, DispatchError> {1082		vec.decode()1083			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1084	}10851086	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1087	where1088		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1089	{1090		vec.rebind()1091			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1092	}10931094	fn init_collection(1095		sender: T::CrossAccountId,1096		data: CreateCollectionData<T::AccountId>,1097		properties: impl Iterator<Item = Property>,1098	) -> Result<CollectionId, DispatchError> {1099		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11001101		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1102			return Err(<Error<T>>::NoAvailableCollectionId.into());1103		}11041105		<PalletCommon<T>>::set_scoped_collection_properties(1106			collection_id?,1107			PropertyScope::Rmrk,1108			properties,1109		)?;11101111		collection_id1112	}11131114	pub fn create_nft(1115		sender: &T::CrossAccountId,1116		owner: &T::CrossAccountId,1117		collection: &NonfungibleHandle<T>,1118		properties: impl Iterator<Item = Property>,1119	) -> Result<TokenId, DispatchError> {1120		let data = CreateNftExData {1121			properties: BoundedVec::default(),1122			owner: owner.clone(),1123		};11241125		let budget = budget::Value::new(NESTING_BUDGET);11261127		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11281129		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11301131		<PalletNft<T>>::set_scoped_token_properties(1132			collection.id,1133			nft_id,1134			PropertyScope::Rmrk,1135			properties,1136		)?;11371138		Ok(nft_id)1139	}11401141	fn destroy_nft(1142		sender: T::CrossAccountId,1143		collection_id: CollectionId,1144		token_id: TokenId,1145		max_burns: u32,1146		error_if_not_owned: Error<T>,1147	) -> DispatchResultWithPostInfo {1148		let collection =1149			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11501151		let token_data =1152			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11531154		let from = token_data.owner;11551156		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11571158		ensure!(1159			<PalletStructure<T>>::check_indirectly_owned(1160				sender.clone(),1161				collection_id,1162				token_id,1163				None,1164				&owner_check_budget1165			)?,1166			error_if_not_owned,1167		);11681169		let burns_budget = budget::Value::new(max_burns);1170		let breadth_budget = budget::Value::new(max_burns);11711172		<PalletNft<T>>::burn_recursively(1173			&collection,1174			&from,1175			token_id,1176			&burns_budget,1177			&breadth_budget,1178		)1179	}11801181	fn insert_pending_child(1182		target: (CollectionId, TokenId),1183		child: (RmrkCollectionId, RmrkNftId),1184	) -> DispatchResult {1185		Self::mutate_pending_child(target, |pending_children| {1186			pending_children.insert(child);1187		})1188	}11891190	fn remove_pending_child(1191		target: (CollectionId, TokenId),1192		child: (RmrkCollectionId, RmrkNftId),1193	) -> DispatchResult {1194		Self::mutate_pending_child(target, |pending_children| {1195			pending_children.remove(&child);1196		})1197	}11981199	fn mutate_pending_child(1200		(target_collection_id, target_nft_id): (CollectionId, TokenId),1201		f: impl FnOnce(&mut PendingChildrenMap),1202	) -> DispatchResult {1203		<PalletNft<T>>::try_mutate_token_aux_property(1204			target_collection_id,1205			target_nft_id,1206			PropertyScope::Rmrk,1207			Self::rmrk_property_key(PendingChildren)?,1208			|pending_children| -> DispatchResult {1209				let mut map = match pending_children {1210					Some(map) => Self::decode_property(map)?,1211					None => PendingChildrenMap::new(),1212				};12131214				f(&mut map);12151216				*pending_children = Some(Self::encode_property(&map)?);12171218				Ok(())1219			},1220		)1221	}12221223	fn iterate_pending_children(collection_id: CollectionId, nft_id: TokenId) -> Result<impl Iterator<Item=PendingChild>, DispatchError> {1224		let property = <PalletNft<T>>::token_aux_property((1225			collection_id,1226			nft_id,1227			PropertyScope::Rmrk,1228			Self::rmrk_property_key(PendingChildren)?1229		));12301231		let pending_children = match property {1232			Some(map) => Self::decode_property(&map)?,1233			None => PendingChildrenMap::new(),1234		};12351236		Ok(pending_children.into_iter())1237	}12381239	fn acquire_next_resource_id(1240		collection_id: CollectionId,1241		nft_id: TokenId,1242	) -> Result<RmrkResourceId, DispatchError> {1243		let resource_id: RmrkResourceId =1244			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12451246		let next_id = resource_id1247			.checked_add(1)1248			.ok_or(<Error<T>>::NoAvailableResourceId)?;12491250		<PalletNft<T>>::set_scoped_token_property(1251			collection_id,1252			nft_id,1253			PropertyScope::Rmrk,1254			Self::rmrk_property(NextResourceId, &next_id)?,1255		)?;12561257		Ok(resource_id)1258	}12591260	fn resource_add(1261		sender: T::AccountId,1262		collection_id: CollectionId,1263		nft_id: TokenId,1264		resource: RmrkResourceTypes,1265	) -> Result<RmrkResourceId, DispatchError> {1266		let collection =1267			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1268		ensure!(collection.owner == sender, Error::<T>::NoPermission);12691270		let sender = T::CrossAccountId::from_sub(sender);1271		let budget = budget::Value::new(NESTING_BUDGET);12721273		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1274			.map_err(Self::map_unique_err_to_proxy)?;12751276		let pending = sender != nft_owner;12771278		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;12791280		let resource_info = RmrkResourceInfo {1281			id,1282			resource,1283			pending,1284			pending_removal: false,1285		};12861287		<PalletNft<T>>::try_mutate_token_aux_property(1288			collection_id,1289			nft_id,1290			PropertyScope::Rmrk,1291			Self::rmrk_property_key(ResourceId(id))?,1292			|value| -> DispatchResult {1293				*value = Some(Self::encode_property(&resource_info)?);12941295				Ok(())1296			},1297		)?;12981299		Ok(id)1300	}13011302	fn resource_remove(1303		sender: T::AccountId,1304		collection_id: CollectionId,1305		nft_id: TokenId,1306		resource_id: RmrkResourceId,1307	) -> DispatchResult {1308		let collection =1309			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1310		ensure!(collection.owner == sender, Error::<T>::NoPermission);13111312		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1313		let scope = PropertyScope::Rmrk;13141315		ensure!(1316			<PalletNft<T>>::token_aux_property((1317				collection_id,1318				nft_id,1319				scope,1320				resource_id_key.clone()1321			))1322			.is_some(),1323			<Error<T>>::ResourceDoesntExist1324		);13251326		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1327		let topmost_owner =1328			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13291330		let sender = T::CrossAccountId::from_sub(sender);1331		if topmost_owner == sender {1332			<PalletNft<T>>::remove_token_aux_property(1333				collection_id,1334				nft_id,1335				PropertyScope::Rmrk,1336				Self::rmrk_property_key(ResourceId(resource_id))?,1337			);1338		} else {1339			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1340				res.pending_removal = true;13411342				Ok(())1343			})?;1344		}13451346		Ok(())1347	}13481349	fn try_mutate_resource_info(1350		collection_id: CollectionId,1351		nft_id: TokenId,1352		resource_id: RmrkResourceId,1353		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1354	) -> DispatchResult {1355		<PalletNft<T>>::try_mutate_token_aux_property(1356			collection_id,1357			nft_id,1358			PropertyScope::Rmrk,1359			Self::rmrk_property_key(ResourceId(resource_id))?,1360			|value| match value {1361				Some(value) => {1362					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;13631364					f(&mut resource_info)?;13651366					*value = Self::encode_property(&resource_info)?;13671368					Ok(())1369				}1370				None => Err(<Error<T>>::ResourceDoesntExist.into()),1371			},1372		)1373	}13741375	fn change_collection_owner(1376		collection_id: CollectionId,1377		collection_type: misc::CollectionType,1378		sender: T::AccountId,1379		new_owner: T::AccountId,1380	) -> DispatchResult {1381		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1382		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;13831384		let mut collection = collection.into_inner();13851386		collection.owner = new_owner;1387		collection.save()1388	}13891390	pub fn check_collection_owner(1391		collection: &NonfungibleHandle<T>,1392		account: &T::CrossAccountId,1393	) -> DispatchResult {1394		collection1395			.check_is_owner(account)1396			.map_err(Self::map_unique_err_to_proxy)1397	}13981399	pub fn last_collection_idx() -> RmrkCollectionId {1400		<CollectionIndex<T>>::get()1401	}14021403	pub fn unique_collection_id(1404		rmrk_collection_id: RmrkCollectionId,1405	) -> Result<CollectionId, DispatchError> {1406		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1407			.map_err(|_| <Error<T>>::CollectionUnknown.into())1408	}14091410	pub fn rmrk_collection_id(1411		unique_collection_id: CollectionId,1412	) -> Result<RmrkCollectionId, DispatchError> {1413		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1414	}14151416	pub fn get_nft_collection(1417		collection_id: CollectionId,1418	) -> Result<NonfungibleHandle<T>, DispatchError> {1419		let collection = <CollectionHandle<T>>::try_get(collection_id)1420			.map_err(|_| <Error<T>>::CollectionUnknown)?;14211422		match collection.mode {1423			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1424			_ => Err(<Error<T>>::CollectionUnknown.into()),1425		}1426	}14271428	pub fn collection_exists(collection_id: CollectionId) -> bool {1429		<CollectionHandle<T>>::try_get(collection_id).is_ok()1430	}14311432	pub fn get_collection_property(1433		collection_id: CollectionId,1434		key: RmrkProperty,1435	) -> Result<PropertyValue, DispatchError> {1436		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1437			.get(&Self::rmrk_property_key(key)?)1438			.ok_or(<Error<T>>::CollectionUnknown)?1439			.clone();14401441		Ok(collection_property)1442	}14431444	pub fn get_collection_property_decoded<V: Decode>(1445		collection_id: CollectionId,1446		key: RmrkProperty,1447	) -> Result<V, DispatchError> {1448		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1449	}14501451	pub fn get_collection_type(1452		collection_id: CollectionId,1453	) -> Result<misc::CollectionType, DispatchError> {1454		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1455			if err != <Error<T>>::CollectionUnknown.into() {1456				<Error<T>>::CorruptedCollectionType.into()1457			} else {1458				err1459			}1460		})1461	}14621463	pub fn ensure_collection_type(1464		collection_id: CollectionId,1465		collection_type: misc::CollectionType,1466	) -> DispatchResult {1467		let actual_type = Self::get_collection_type(collection_id)?;1468		ensure!(1469			actual_type == collection_type,1470			<CommonError<T>>::NoPermission1471		);14721473		Ok(())1474	}14751476	pub fn get_typed_nft_collection(1477		collection_id: CollectionId,1478		collection_type: misc::CollectionType,1479	) -> Result<NonfungibleHandle<T>, DispatchError> {1480		Self::ensure_collection_type(collection_id, collection_type)?;14811482		Self::get_nft_collection(collection_id)1483	}14841485	pub fn get_typed_nft_collection_mapped(1486		rmrk_collection_id: RmrkCollectionId,1487		collection_type: misc::CollectionType,1488	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1489		let unique_collection_id = match collection_type {1490			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1491			_ => rmrk_collection_id.into(),1492		};14931494		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14951496		Ok((collection, unique_collection_id))1497	}14981499	pub fn get_nft_property(1500		collection_id: CollectionId,1501		nft_id: TokenId,1502		key: RmrkProperty,1503	) -> Result<PropertyValue, DispatchError> {1504		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1505			.get(&Self::rmrk_property_key(key)?)1506			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1507			.clone();15081509		Ok(nft_property)1510	}15111512	pub fn get_nft_property_decoded<V: Decode>(1513		collection_id: CollectionId,1514		nft_id: TokenId,1515		key: RmrkProperty,1516	) -> Result<V, DispatchError> {1517		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1518	}15191520	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1521		<TokenData<T>>::contains_key((collection_id, nft_id))1522	}15231524	pub fn get_nft_type(1525		collection_id: CollectionId,1526		token_id: TokenId,1527	) -> Result<NftType, DispatchError> {1528		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1529			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1530	}15311532	pub fn ensure_nft_type(1533		collection_id: CollectionId,1534		token_id: TokenId,1535		nft_type: NftType,1536	) -> DispatchResult {1537		let actual_type = Self::get_nft_type(collection_id, token_id)?;1538		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);15391540		Ok(())1541	}15421543	pub fn ensure_nft_owner(1544		collection_id: CollectionId,1545		token_id: TokenId,1546		possible_owner: &T::CrossAccountId,1547		nesting_budget: &dyn budget::Budget,1548	) -> DispatchResult {1549		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1550			possible_owner.clone(),1551			collection_id,1552			token_id,1553			None,1554			nesting_budget,1555		)1556		.map_err(Self::map_unique_err_to_proxy)?;15571558		ensure!(is_owned, <Error<T>>::NoPermission);15591560		Ok(())1561	}15621563	pub fn filter_user_properties<Key, Value, R, Mapper>(1564		collection_id: CollectionId,1565		token_id: Option<TokenId>,1566		filter_keys: Option<Vec<RmrkPropertyKey>>,1567		mapper: Mapper,1568	) -> Result<Vec<R>, DispatchError>1569	where1570		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1571		Value: Decode + Default,1572		Mapper: Fn(Key, Value) -> R,1573	{1574		filter_keys1575			.map(|keys| {1576				let properties = keys1577					.into_iter()1578					.filter_map(|key| {1579						let key: Key = key.try_into().ok()?;15801581						let value = match token_id {1582							Some(token_id) => Self::get_nft_property_decoded(1583								collection_id,1584								token_id,1585								UserProperty(key.as_ref()),1586							),1587							None => Self::get_collection_property_decoded(1588								collection_id,1589								UserProperty(key.as_ref()),1590							),1591						}1592						.ok()?;15931594						Some(mapper(key, value))1595					})1596					.collect();15971598				Ok(properties)1599			})1600			.unwrap_or_else(|| {1601				let properties =1602					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16031604				Ok(properties)1605			})1606	}16071608	pub fn iterate_user_properties<Key, Value, R, Mapper>(1609		collection_id: CollectionId,1610		token_id: Option<TokenId>,1611		mapper: Mapper,1612	) -> Result<impl Iterator<Item = R>, DispatchError>1613	where1614		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1615		Value: Decode + Default,1616		Mapper: Fn(Key, Value) -> R,1617	{1618		let properties = match token_id {1619			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1620			None => <PalletCommon<T>>::collection_properties(collection_id),1621		};16221623		let properties = properties.into_iter().filter_map(move |(key, value)| {1624			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16251626			let key: Key = key.to_vec().try_into().ok()?;1627			let value: Value = value.decode().ok()?;16281629			Some(mapper(key, value))1630		});16311632		Ok(properties)1633	}16341635	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1636		map_unique_err_to_proxy! {1637			match err {1638				CommonError::NoPermission => NoPermission,1639				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1640				CommonError::PublicMintingNotAllowed => NoPermission,1641				CommonError::TokenNotFound => NoAvailableNftId,1642				CommonError::ApprovedValueTooLow => NoPermission,1643				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1644				StructureError::TokenNotFound => NoAvailableNftId,1645				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1646			}1647		}1648	}1649}
modifiedpallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -15,9 +15,11 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 use super::*;
+use up_data_structs::PropertyScope;
 use core::convert::AsRef;
 
-const RESOURCE_ID_PREFIX: &str = "rsid-";
+pub const RESOURCE_ID_PREFIX: &str = "rsid-";
+pub const USER_PROPERTY_PREFIX: &str = "userprop-";
 
 pub enum RmrkProperty<'r> {
 	Metadata,
@@ -31,6 +33,7 @@
 	NextResourceId,
 	ResourceId(RmrkResourceId),
 	PendingNftAccept,
+	PendingChildren,
 	Parts,
 	Base,
 	Src,
@@ -73,6 +76,7 @@
 			Self::NextResourceId => key!("next-resource-id"),
 			Self::ResourceId(id) => key!(RESOURCE_ID_PREFIX, id.to_le_bytes()),
 			Self::PendingNftAccept => key!("pending-nft-accept"),
+			Self::PendingChildren => key!("pending-children"),
 			Self::Parts => key!("parts"),
 			Self::Base => key!("base"),
 			Self::Src => key!("src"),
@@ -83,7 +87,19 @@
 			Self::ZIndex => key!("z-index"),
 			Self::ThemeName => key!("theme-name"),
 			Self::ThemeInherit => key!("theme-inherit"),
-			Self::UserProperty(name) => key!("userprop-", name),
+			Self::UserProperty(name) => key!(USER_PROPERTY_PREFIX, name),
 		}
 	}
 }
+
+pub fn strip_key_prefix(key: &PropertyKey, prefix: &str) -> Option<PropertyKey> {
+	let key_prefix = PropertyKey::try_from(prefix.as_bytes().to_vec()).ok()?;
+	let key_prefix = PropertyScope::Rmrk.apply(key_prefix).ok()?;
+
+	key.as_slice().strip_prefix(key_prefix.as_slice())?
+		.to_vec().try_into().ok()
+}
+
+pub fn is_valid_key_prefix(key: &PropertyKey, prefix: &str) -> bool {
+	strip_key_prefix(key, prefix).is_some()
+}
modifiedpallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/rpc.rs
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -132,17 +132,6 @@
 	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()?;
 
@@ -151,6 +140,13 @@
 					nft_id: child_token.0,
 				})
 			})
+			.chain(
+				<Pallet<T>>::iterate_pending_children(collection_id, nft_id)?
+					.map(|(child_collection, child_nft_id)| RmrkNftChild {
+						collection_id: child_collection,
+						nft_id: child_nft_id,
+					})
+			)
 			.collect(),
 	)
 }
@@ -224,7 +220,11 @@
 		nft_id,
 		PropertyScope::Rmrk,
 	)
-	.filter_map(|(_, value)| {
+	.filter_map(|(key, value)| {
+		if !is_valid_key_prefix(&key, RESOURCE_ID_PREFIX) {
+			return None;
+		}
+
 		let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property(&value).ok()?;
 
 		Some(resource_info)