git.delta.rocks / unique-network / refs/commits / 73992bf599ce

difftreelog

doc: rmrk proxies

Farhad Hakimov2022-07-15parent: #12c8c8f.patch.diff
in: master

8 files changed

modifiedpallets/proxy-rmrk-core/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-core/src/benchmarking.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use sp_std::vec;
 
 use frame_benchmarking::{benchmarks, account};
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::{23	vec::Vec,24	collections::{btree_set::BTreeSet, btree_map::BTreeMap},25};26use up_data_structs::{*, mapping::TokenAddressMapping};27use pallet_common::{28	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,29};30use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};31use pallet_structure::{Pallet as PalletStructure, Error as StructureError};32use pallet_evm::account::CrossAccountId;33use core::convert::AsRef;3435pub use pallet::*;3637#[cfg(feature = "runtime-benchmarks")]38pub mod benchmarking;39pub mod misc;40pub mod property;41pub mod rpc;42pub mod weights;4344pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4546use weights::WeightInfo;47use misc::*;48pub use property::*;4950use RmrkProperty::*;5152pub const NESTING_BUDGET: u32 = 5;5354type PendingTarget = (CollectionId, TokenId);55type PendingChild = (RmrkCollectionId, RmrkNftId);56type PendingChildrenSet = BTreeSet<PendingChild>;5758type BasesMap = BTreeMap<RmrkBaseId, u32>;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, &PendingChildrenSet::new())?,402					Self::rmrk_property(AssociatedBases, &BasesMap::new())?,403				]404				.into_iter(),405			)406			.map_err(|err| match err {407				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),408				err => Self::map_unique_err_to_proxy(err),409			})?;410411			if let Some(resources) = resources {412				for resource in resources {413					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;414				}415			}416417			Self::deposit_event(Event::NftMinted {418				owner,419				collection_id,420				nft_id: nft_id.0,421			});422423			Ok(())424		}425426		/// burn nft427		#[transactional]428		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]429		pub fn burn_nft(430			origin: OriginFor<T>,431			collection_id: RmrkCollectionId,432			nft_id: RmrkNftId,433			max_burns: u32,434		) -> DispatchResult {435			let sender = ensure_signed(origin)?;436			let cross_sender = T::CrossAccountId::from_sub(sender.clone());437438			let collection = Self::get_typed_nft_collection(439				Self::unique_collection_id(collection_id)?,440				misc::CollectionType::Regular,441			)?;442			collection.check_is_external()?;443444			Self::destroy_nft(445				cross_sender,446				Self::unique_collection_id(collection_id)?,447				nft_id.into(),448				max_burns,449				<Error<T>>::NoPermission,450			)451			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;452453			Self::deposit_event(Event::NFTBurned {454				owner: sender,455				nft_id,456			});457458			Ok(())459		}460461		/// Transfers a NFT from an Account or NFT A to another Account or NFT B462		///463		/// Parameters:464		/// - `origin`: sender of the transaction465		/// - `rmrk_collection_id`: collection id of the nft to be transferred466		/// - `rmrk_nft_id`: nft id of the nft to be transferred467		/// - `new_owner`: new owner of the nft which can be either an account or a NFT468		#[transactional]469		#[pallet::weight(<SelfWeightOf<T>>::send())]470		pub fn send(471			origin: OriginFor<T>,472			rmrk_collection_id: RmrkCollectionId,473			rmrk_nft_id: RmrkNftId,474			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,475		) -> DispatchResult {476			let sender = ensure_signed(origin.clone())?;477			let cross_sender = T::CrossAccountId::from_sub(sender.clone());478479			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;480			let nft_id = rmrk_nft_id.into();481482			let collection =483				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;484			collection.check_is_external()?;485486			let token_data =487				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;488489			let from = token_data.owner;490491			ensure!(492				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,493				<Error<T>>::NonTransferable494			);495496			ensure!(497				Self::get_nft_property_decoded::<Option<PendingTarget>>(498					collection_id,499					nft_id,500					RmrkProperty::PendingNftAccept501				)?502				.is_none(),503				<Error<T>>::NoPermission504			);505506			let target_owner;507			let approval_required;508509			match new_owner {510				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {511					target_owner = T::CrossAccountId::from_sub(account_id.clone());512					approval_required = false;513				}514				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(515					target_collection_id,516					target_nft_id,517				) => {518					let target_collection_id = Self::unique_collection_id(target_collection_id)?;519520					let target_nft_budget = budget::Value::new(NESTING_BUDGET);521522					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(523						target_collection_id,524						target_nft_id.into(),525						Some((collection_id, nft_id)),526						&target_nft_budget,527					)528					.map_err(Self::map_unique_err_to_proxy)?;529530					approval_required = cross_sender != target_nft_owner;531532					if approval_required {533						target_owner = target_nft_owner;534535						<PalletNft<T>>::set_scoped_token_property(536							collection.id,537							nft_id,538							PropertyScope::Rmrk,539							Self::rmrk_property::<Option<PendingTarget>>(540								PendingNftAccept,541								&Some((target_collection_id, target_nft_id.into())),542							)?,543						)?;544545						Self::insert_pending_child(546							(target_collection_id, target_nft_id.into()),547							(rmrk_collection_id, rmrk_nft_id),548						)?;549					} else {550						target_owner = T::CrossTokenAddressMapping::token_to_address(551							target_collection_id,552							target_nft_id.into(),553						);554					}555				}556			}557558			let src_nft_budget = budget::Value::new(NESTING_BUDGET);559560			<PalletNft<T>>::transfer_from(561				&collection,562				&cross_sender,563				&from,564				&target_owner,565				nft_id,566				&src_nft_budget,567			)568			.map_err(Self::map_unique_err_to_proxy)?;569570			Self::deposit_event(Event::NFTSent {571				sender,572				recipient: new_owner,573				collection_id: rmrk_collection_id,574				nft_id: rmrk_nft_id,575				approval_required,576			});577578			Ok(())579		}580581		/// Accepts an NFT sent from another account to self or owned NFT582		///583		/// Parameters:584		/// - `origin`: sender of the transaction585		/// - `rmrk_collection_id`: collection id of the nft to be accepted586		/// - `rmrk_nft_id`: nft id of the nft to be accepted587		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was588		///   sent to589		#[transactional]590		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]591		pub fn accept_nft(592			origin: OriginFor<T>,593			rmrk_collection_id: RmrkCollectionId,594			rmrk_nft_id: RmrkNftId,595			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,596		) -> DispatchResult {597			let sender = ensure_signed(origin.clone())?;598			let cross_sender = T::CrossAccountId::from_sub(sender.clone());599600			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;601			let nft_id = rmrk_nft_id.into();602603			let collection =604				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;605			collection.check_is_external()?;606607			let new_cross_owner = match new_owner {608				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {609					T::CrossAccountId::from_sub(account_id.clone())610				}611				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(612					target_collection_id,613					target_nft_id,614				) => {615					let target_collection_id = Self::unique_collection_id(target_collection_id)?;616617					T::CrossTokenAddressMapping::token_to_address(618						target_collection_id,619						TokenId(target_nft_id),620					)621				}622			};623624			let budget = budget::Value::new(NESTING_BUDGET);625626			<PalletNft<T>>::transfer(627				&collection,628				&cross_sender,629				&new_cross_owner,630				nft_id,631				&budget,632			)633			.map_err(|err| {634				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {635					<Error<T>>::CannotAcceptNonOwnedNft.into()636				} else {637					Self::map_unique_err_to_proxy(err)638				}639			})?;640641			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(642				collection_id,643				nft_id,644				RmrkProperty::PendingNftAccept,645			)?;646647			if let Some(pending_target) = pending_target {648				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;649650				<PalletNft<T>>::set_scoped_token_property(651					collection.id,652					nft_id,653					PropertyScope::Rmrk,654					Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,655				)?;656			}657658			Self::deposit_event(Event::NFTAccepted {659				sender,660				recipient: new_owner,661				collection_id: rmrk_collection_id,662				nft_id: rmrk_nft_id,663			});664665			Ok(())666		}667668		/// Rejects an NFT sent from another account to self or owned NFT669		///670		/// Parameters:671		/// - `origin`: sender of the transaction672		/// - `rmrk_collection_id`: collection id of the nft to be accepted673		/// - `rmrk_nft_id`: nft id of the nft to be accepted674		#[transactional]675		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]676		pub fn reject_nft(677			origin: OriginFor<T>,678			rmrk_collection_id: RmrkCollectionId,679			rmrk_nft_id: RmrkNftId,680		) -> DispatchResult {681			let sender = ensure_signed(origin)?;682			let cross_sender = T::CrossAccountId::from_sub(sender.clone());683684			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;685			let nft_id = rmrk_nft_id.into();686687			let collection =688				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;689			collection.check_is_external()?;690691			ensure!(692				<TokenData<T>>::get((collection_id, nft_id)).is_some(),693				<Error<T>>::NoAvailableNftId694			);695696			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(697				collection_id,698				nft_id,699				RmrkProperty::PendingNftAccept,700			)?;701702			match pending_target {703				Some(pending_target) => {704					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?705				}706				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),707			}708709			Self::destroy_nft(710				cross_sender,711				collection_id,712				nft_id,713				NESTING_BUDGET,714				<Error<T>>::CannotRejectNonOwnedNft,715			)716			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;717718			Self::deposit_event(Event::NFTRejected {719				sender,720				collection_id: rmrk_collection_id,721				nft_id: rmrk_nft_id,722			});723724			Ok(())725		}726727		/// accept the addition of a new resource to an existing NFT728		#[transactional]729		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]730		pub fn accept_resource(731			origin: OriginFor<T>,732			rmrk_collection_id: RmrkCollectionId,733			rmrk_nft_id: RmrkNftId,734			resource_id: RmrkResourceId,735		) -> DispatchResult {736			let sender = ensure_signed(origin)?;737			let cross_sender = T::CrossAccountId::from_sub(sender);738739			let collection_id = Self::unique_collection_id(rmrk_collection_id)740				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;741			let collection =742				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;743			collection.check_is_external()?;744745			let nft_id = rmrk_nft_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {754				ensure!(res.pending, <Error<T>>::ResourceNotPending);755				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);756757				res.pending = false;758759				Ok(())760			})?;761762			Self::deposit_event(Event::<T>::ResourceAccepted {763				nft_id: rmrk_nft_id,764				resource_id,765			});766767			Ok(())768		}769770		/// accept the removal of a resource of an existing NFT771		#[transactional]772		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]773		pub fn accept_resource_removal(774			origin: OriginFor<T>,775			rmrk_collection_id: RmrkCollectionId,776			rmrk_nft_id: RmrkNftId,777			resource_id: RmrkResourceId,778		) -> DispatchResult {779			let sender = ensure_signed(origin)?;780			let cross_sender = T::CrossAccountId::from_sub(sender);781782			let collection_id = Self::unique_collection_id(rmrk_collection_id)783				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;784			let collection =785				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;786			collection.check_is_external()?;787788			let nft_id = rmrk_nft_id.into();789790			let budget = budget::Value::new(NESTING_BUDGET);791792			let nft_owner =793				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)794					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;795796			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);797798			let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;799800			let resource_info = <PalletNft<T>>::token_aux_property((801				collection_id,802				nft_id,803				PropertyScope::Rmrk,804				resource_id_key.clone(),805			))806			.ok_or(<Error<T>>::ResourceDoesntExist)?;807808			let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;809810			ensure!(811				resource_info.pending_removal,812				<Error<T>>::ResourceNotPending813			);814815			<PalletNft<T>>::remove_token_aux_property(816				collection_id,817				nft_id,818				PropertyScope::Rmrk,819				resource_id_key,820			);821822			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {823				let base_id = resource.base;824825				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;826			}827828			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {829				nft_id: rmrk_nft_id,830				resource_id,831			});832833			Ok(())834		}835836		/// set a custom value on an NFT837		#[transactional]838		#[pallet::weight(<SelfWeightOf<T>>::set_property())]839		pub fn set_property(840			origin: OriginFor<T>,841			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,842			maybe_nft_id: Option<RmrkNftId>,843			key: RmrkKeyString,844			value: RmrkValueString,845		) -> DispatchResult {846			let sender = ensure_signed(origin)?;847			let sender = T::CrossAccountId::from_sub(sender);848849			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;850			let collection =851				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;852			collection.check_is_external()?;853854			let budget = budget::Value::new(NESTING_BUDGET);855856			match maybe_nft_id {857				Some(nft_id) => {858					let token_id: TokenId = nft_id.into();859860					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;861					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;862863					<PalletNft<T>>::set_scoped_token_property(864						collection_id,865						token_id,866						PropertyScope::Rmrk,867						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,868					)?;869				}870				None => {871					let collection = Self::get_typed_nft_collection(872						collection_id,873						misc::CollectionType::Regular,874					)?;875876					Self::check_collection_owner(&collection, &sender)?;877878					<PalletCommon<T>>::set_scoped_collection_property(879						collection_id,880						PropertyScope::Rmrk,881						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,882					)?;883				}884			}885886			Self::deposit_event(Event::PropertySet {887				collection_id: rmrk_collection_id,888				maybe_nft_id,889				key,890				value,891			});892893			Ok(())894		}895896		/// set a different order of resource priority897		#[transactional]898		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]899		pub fn set_priority(900			origin: OriginFor<T>,901			rmrk_collection_id: RmrkCollectionId,902			rmrk_nft_id: RmrkNftId,903			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,904		) -> DispatchResult {905			let sender = ensure_signed(origin)?;906			let sender = T::CrossAccountId::from_sub(sender);907908			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;909			let nft_id = rmrk_nft_id.into();910911			let collection =912				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;913			collection.check_is_external()?;914915			let budget = budget::Value::new(NESTING_BUDGET);916917			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;918			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;919920			<PalletNft<T>>::set_scoped_token_property(921				collection_id,922				nft_id,923				PropertyScope::Rmrk,924				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,925			)?;926927			Self::deposit_event(Event::<T>::PrioritySet {928				collection_id: rmrk_collection_id,929				nft_id: rmrk_nft_id,930			});931932			Ok(())933		}934935		/// Create basic resource936		#[transactional]937		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]938		pub fn add_basic_resource(939			origin: OriginFor<T>,940			rmrk_collection_id: RmrkCollectionId,941			nft_id: RmrkNftId,942			resource: RmrkBasicResource,943		) -> DispatchResult {944			let sender = ensure_signed(origin.clone())?;945946			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;947			let collection =948				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;949			collection.check_is_external()?;950951			let resource_id = Self::resource_add(952				sender,953				collection_id,954				nft_id.into(),955				RmrkResourceTypes::Basic(resource),956			)?;957958			Self::deposit_event(Event::ResourceAdded {959				nft_id,960				resource_id,961			});962			Ok(())963		}964965		/// Create composable resource966		#[transactional]967		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]968		pub fn add_composable_resource(969			origin: OriginFor<T>,970			rmrk_collection_id: RmrkCollectionId,971			nft_id: RmrkNftId,972			resource: RmrkComposableResource,973		) -> DispatchResult {974			let sender = ensure_signed(origin.clone())?;975976			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;977			let collection =978				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;979			collection.check_is_external()?;980981			let base_id = resource.base;982983			let resource_id = Self::resource_add(984				sender,985				collection_id,986				nft_id.into(),987				RmrkResourceTypes::Composable(resource),988			)?;989990			<PalletNft<T>>::try_mutate_token_aux_property(991				collection_id,992				nft_id.into(),993				PropertyScope::Rmrk,994				Self::rmrk_property_key(AssociatedBases)?,995				|value| -> DispatchResult {996					let mut bases: BasesMap = match value {997						Some(value) => Self::decode_property(value)?,998						None => BasesMap::new(),999					};10001001					*bases.entry(base_id).or_insert(0) += 1;10021003					*value = Some(Self::encode_property(&bases)?);1004					Ok(())1005				},1006			)?;10071008			Self::deposit_event(Event::ResourceAdded {1009				nft_id,1010				resource_id,1011			});1012			Ok(())1013		}10141015		/// Create slot resource1016		#[transactional]1017		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1018		pub fn add_slot_resource(1019			origin: OriginFor<T>,1020			rmrk_collection_id: RmrkCollectionId,1021			nft_id: RmrkNftId,1022			resource: RmrkSlotResource,1023		) -> DispatchResult {1024			let sender = ensure_signed(origin.clone())?;10251026			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1027			let collection =1028				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1029			collection.check_is_external()?;10301031			let resource_id = Self::resource_add(1032				sender,1033				collection_id,1034				nft_id.into(),1035				RmrkResourceTypes::Slot(resource),1036			)?;10371038			Self::deposit_event(Event::ResourceAdded {1039				nft_id,1040				resource_id,1041			});1042			Ok(())1043		}10441045		/// remove resource1046		#[transactional]1047		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1048		pub fn remove_resource(1049			origin: OriginFor<T>,1050			rmrk_collection_id: RmrkCollectionId,1051			nft_id: RmrkNftId,1052			resource_id: RmrkResourceId,1053		) -> DispatchResult {1054			let sender = ensure_signed(origin.clone())?;10551056			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1057			let collection =1058				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1059			collection.check_is_external()?;10601061			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10621063			Self::deposit_event(Event::ResourceRemoval {1064				nft_id,1065				resource_id,1066			});1067			Ok(())1068		}1069	}1070}10711072impl<T: Config> Pallet<T> {1073	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1074		let key = rmrk_key.to_key::<T>()?;10751076		let scoped_key = PropertyScope::Rmrk1077			.apply(key)1078			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10791080		Ok(scoped_key)1081	}10821083	// todo think about renaming these1084	pub fn rmrk_property<E: Encode>(1085		rmrk_key: RmrkProperty,1086		value: &E,1087	) -> Result<Property, DispatchError> {1088		let key = rmrk_key.to_key::<T>()?;10891090		let value = Self::encode_property(value)?;10911092		let property = Property { key, value };10931094		Ok(property)1095	}10961097	pub fn encode_property<E: Encode, S: Get<u32>>(1098		value: &E,1099	) -> Result<BoundedBytes<S>, DispatchError> {1100		let value = value1101			.encode()1102			.try_into()1103			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;11041105		Ok(value)1106	}11071108	pub fn decode_property<D: Decode, S: Get<u32>>(1109		vec: &BoundedBytes<S>,1110	) -> Result<D, DispatchError> {1111		vec.decode()1112			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1113	}11141115	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1116	where1117		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1118	{1119		vec.rebind()1120			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1121	}11221123	fn init_collection(1124		sender: T::CrossAccountId,1125		data: CreateCollectionData<T::AccountId>,1126		properties: impl Iterator<Item = Property>,1127	) -> Result<CollectionId, DispatchError> {1128		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11291130		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1131			return Err(<Error<T>>::NoAvailableCollectionId.into());1132		}11331134		<PalletCommon<T>>::set_scoped_collection_properties(1135			collection_id?,1136			PropertyScope::Rmrk,1137			properties,1138		)?;11391140		collection_id1141	}11421143	pub fn create_nft(1144		sender: &T::CrossAccountId,1145		owner: &T::CrossAccountId,1146		collection: &NonfungibleHandle<T>,1147		properties: impl Iterator<Item = Property>,1148	) -> Result<TokenId, DispatchError> {1149		let data = CreateNftExData {1150			properties: BoundedVec::default(),1151			owner: owner.clone(),1152		};11531154		let budget = budget::Value::new(NESTING_BUDGET);11551156		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11571158		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11591160		<PalletNft<T>>::set_scoped_token_properties(1161			collection.id,1162			nft_id,1163			PropertyScope::Rmrk,1164			properties,1165		)?;11661167		Ok(nft_id)1168	}11691170	fn destroy_nft(1171		sender: T::CrossAccountId,1172		collection_id: CollectionId,1173		token_id: TokenId,1174		max_burns: u32,1175		error_if_not_owned: Error<T>,1176	) -> DispatchResultWithPostInfo {1177		let collection =1178			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11791180		let token_data =1181			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11821183		let from = token_data.owner;11841185		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11861187		ensure!(1188			<PalletStructure<T>>::check_indirectly_owned(1189				sender.clone(),1190				collection_id,1191				token_id,1192				None,1193				&owner_check_budget1194			)?,1195			error_if_not_owned,1196		);11971198		let burns_budget = budget::Value::new(max_burns);1199		let breadth_budget = budget::Value::new(max_burns);12001201		<PalletNft<T>>::burn_recursively(1202			&collection,1203			&from,1204			token_id,1205			&burns_budget,1206			&breadth_budget,1207		)1208	}12091210	fn insert_pending_child(1211		target: (CollectionId, TokenId),1212		child: (RmrkCollectionId, RmrkNftId),1213	) -> DispatchResult {1214		Self::mutate_pending_child(target, |pending_children| {1215			pending_children.insert(child);1216		})1217	}12181219	fn remove_pending_child(1220		target: (CollectionId, TokenId),1221		child: (RmrkCollectionId, RmrkNftId),1222	) -> DispatchResult {1223		Self::mutate_pending_child(target, |pending_children| {1224			pending_children.remove(&child);1225		})1226	}12271228	fn mutate_pending_child(1229		(target_collection_id, target_nft_id): (CollectionId, TokenId),1230		f: impl FnOnce(&mut PendingChildrenSet),1231	) -> DispatchResult {1232		<PalletNft<T>>::try_mutate_token_aux_property(1233			target_collection_id,1234			target_nft_id,1235			PropertyScope::Rmrk,1236			Self::rmrk_property_key(PendingChildren)?,1237			|pending_children| -> DispatchResult {1238				let mut map = match pending_children {1239					Some(map) => Self::decode_property(map)?,1240					None => PendingChildrenSet::new(),1241				};12421243				f(&mut map);12441245				*pending_children = Some(Self::encode_property(&map)?);12461247				Ok(())1248			},1249		)1250	}12511252	fn iterate_pending_children(1253		collection_id: CollectionId,1254		nft_id: TokenId,1255	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1256		let property = <PalletNft<T>>::token_aux_property((1257			collection_id,1258			nft_id,1259			PropertyScope::Rmrk,1260			Self::rmrk_property_key(PendingChildren)?,1261		));12621263		let pending_children = match property {1264			Some(map) => Self::decode_property(&map)?,1265			None => PendingChildrenSet::new(),1266		};12671268		Ok(pending_children.into_iter())1269	}12701271	fn acquire_next_resource_id(1272		collection_id: CollectionId,1273		nft_id: TokenId,1274	) -> Result<RmrkResourceId, DispatchError> {1275		let resource_id: RmrkResourceId =1276			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12771278		let next_id = resource_id1279			.checked_add(1)1280			.ok_or(<Error<T>>::NoAvailableResourceId)?;12811282		<PalletNft<T>>::set_scoped_token_property(1283			collection_id,1284			nft_id,1285			PropertyScope::Rmrk,1286			Self::rmrk_property(NextResourceId, &next_id)?,1287		)?;12881289		Ok(resource_id)1290	}12911292	fn resource_add(1293		sender: T::AccountId,1294		collection_id: CollectionId,1295		nft_id: TokenId,1296		resource: RmrkResourceTypes,1297	) -> Result<RmrkResourceId, DispatchError> {1298		let collection =1299			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1300		ensure!(collection.owner == sender, Error::<T>::NoPermission);13011302		let sender = T::CrossAccountId::from_sub(sender);1303		let budget = budget::Value::new(NESTING_BUDGET);13041305		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1306			.map_err(Self::map_unique_err_to_proxy)?;13071308		let pending = sender != nft_owner;13091310		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;13111312		let resource_info = RmrkResourceInfo {1313			id,1314			resource,1315			pending,1316			pending_removal: false,1317		};13181319		<PalletNft<T>>::try_mutate_token_aux_property(1320			collection_id,1321			nft_id,1322			PropertyScope::Rmrk,1323			Self::rmrk_property_key(ResourceId(id))?,1324			|value| -> DispatchResult {1325				*value = Some(Self::encode_property(&resource_info)?);13261327				Ok(())1328			},1329		)?;13301331		Ok(id)1332	}13331334	fn resource_remove(1335		sender: T::AccountId,1336		collection_id: CollectionId,1337		nft_id: TokenId,1338		resource_id: RmrkResourceId,1339	) -> DispatchResult {1340		let collection =1341			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1342		ensure!(collection.owner == sender, Error::<T>::NoPermission);13431344		let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1345		let scope = PropertyScope::Rmrk;13461347		let resource = <PalletNft<T>>::token_aux_property((1348			collection_id,1349			nft_id,1350			scope,1351			resource_id_key.clone(),1352		))1353		.ok_or(<Error<T>>::ResourceDoesntExist)?;13541355		let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;13561357		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1358		let topmost_owner =1359			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13601361		let sender = T::CrossAccountId::from_sub(sender);1362		if topmost_owner == sender {1363			<PalletNft<T>>::remove_token_aux_property(1364				collection_id,1365				nft_id,1366				PropertyScope::Rmrk,1367				Self::rmrk_property_key(ResourceId(resource_id))?,1368			);13691370			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1371				let base_id = resource.base;13721373				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1374			}1375		} else {1376			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1377				res.pending_removal = true;13781379				Ok(())1380			})?;1381		}13821383		Ok(())1384	}13851386	fn remove_associated_base_id(1387		collection_id: CollectionId,1388		nft_id: TokenId,1389		base_id: RmrkBaseId,1390	) -> DispatchResult {1391		<PalletNft<T>>::try_mutate_token_aux_property(1392			collection_id,1393			nft_id,1394			PropertyScope::Rmrk,1395			Self::rmrk_property_key(AssociatedBases)?,1396			|value| -> DispatchResult {1397				let mut bases: BasesMap = match value {1398					Some(value) => Self::decode_property(value)?,1399					None => BasesMap::new(),1400				};14011402				let remaining = bases.get(&base_id);14031404				if let Some(remaining) = remaining {1405					if let Some(0) | None = remaining.checked_sub(1) {1406						bases.remove(&base_id);1407					}1408				}14091410				*value = Some(Self::encode_property(&bases)?);1411				Ok(())1412			},1413		)1414	}14151416	fn try_mutate_resource_info(1417		collection_id: CollectionId,1418		nft_id: TokenId,1419		resource_id: RmrkResourceId,1420		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1421	) -> DispatchResult {1422		<PalletNft<T>>::try_mutate_token_aux_property(1423			collection_id,1424			nft_id,1425			PropertyScope::Rmrk,1426			Self::rmrk_property_key(ResourceId(resource_id))?,1427			|value| match value {1428				Some(value) => {1429					let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;14301431					f(&mut resource_info)?;14321433					*value = Self::encode_property(&resource_info)?;14341435					Ok(())1436				}1437				None => Err(<Error<T>>::ResourceDoesntExist.into()),1438			},1439		)1440	}14411442	fn change_collection_owner(1443		collection_id: CollectionId,1444		collection_type: misc::CollectionType,1445		sender: T::AccountId,1446		new_owner: T::AccountId,1447	) -> DispatchResult {1448		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1449		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;14501451		let mut collection = collection.into_inner();14521453		collection.owner = new_owner;1454		collection.save()1455	}14561457	pub fn check_collection_owner(1458		collection: &NonfungibleHandle<T>,1459		account: &T::CrossAccountId,1460	) -> DispatchResult {1461		collection1462			.check_is_owner(account)1463			.map_err(Self::map_unique_err_to_proxy)1464	}14651466	pub fn last_collection_idx() -> RmrkCollectionId {1467		<CollectionIndex<T>>::get()1468	}14691470	pub fn unique_collection_id(1471		rmrk_collection_id: RmrkCollectionId,1472	) -> Result<CollectionId, DispatchError> {1473		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1474			.map_err(|_| <Error<T>>::CollectionUnknown.into())1475	}14761477	pub fn rmrk_collection_id(1478		unique_collection_id: CollectionId,1479	) -> Result<RmrkCollectionId, DispatchError> {1480		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1481	}14821483	pub fn get_nft_collection(1484		collection_id: CollectionId,1485	) -> Result<NonfungibleHandle<T>, DispatchError> {1486		let collection = <CollectionHandle<T>>::try_get(collection_id)1487			.map_err(|_| <Error<T>>::CollectionUnknown)?;14881489		match collection.mode {1490			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1491			_ => Err(<Error<T>>::CollectionUnknown.into()),1492		}1493	}14941495	pub fn collection_exists(collection_id: CollectionId) -> bool {1496		<CollectionHandle<T>>::try_get(collection_id).is_ok()1497	}14981499	pub fn get_collection_property(1500		collection_id: CollectionId,1501		key: RmrkProperty,1502	) -> Result<PropertyValue, DispatchError> {1503		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1504			.get(&Self::rmrk_property_key(key)?)1505			.ok_or(<Error<T>>::CollectionUnknown)?1506			.clone();15071508		Ok(collection_property)1509	}15101511	pub fn get_collection_property_decoded<V: Decode>(1512		collection_id: CollectionId,1513		key: RmrkProperty,1514	) -> Result<V, DispatchError> {1515		Self::decode_property(&Self::get_collection_property(collection_id, key)?)1516	}15171518	pub fn get_collection_type(1519		collection_id: CollectionId,1520	) -> Result<misc::CollectionType, DispatchError> {1521		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1522			if err != <Error<T>>::CollectionUnknown.into() {1523				<Error<T>>::CorruptedCollectionType.into()1524			} else {1525				err1526			}1527		})1528	}15291530	pub fn ensure_collection_type(1531		collection_id: CollectionId,1532		collection_type: misc::CollectionType,1533	) -> DispatchResult {1534		let actual_type = Self::get_collection_type(collection_id)?;1535		ensure!(1536			actual_type == collection_type,1537			<CommonError<T>>::NoPermission1538		);15391540		Ok(())1541	}15421543	pub fn get_typed_nft_collection(1544		collection_id: CollectionId,1545		collection_type: misc::CollectionType,1546	) -> Result<NonfungibleHandle<T>, DispatchError> {1547		Self::ensure_collection_type(collection_id, collection_type)?;15481549		Self::get_nft_collection(collection_id)1550	}15511552	pub fn get_typed_nft_collection_mapped(1553		rmrk_collection_id: RmrkCollectionId,1554		collection_type: misc::CollectionType,1555	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1556		let unique_collection_id = match collection_type {1557			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1558			_ => rmrk_collection_id.into(),1559		};15601561		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;15621563		Ok((collection, unique_collection_id))1564	}15651566	pub fn get_nft_property(1567		collection_id: CollectionId,1568		nft_id: TokenId,1569		key: RmrkProperty,1570	) -> Result<PropertyValue, DispatchError> {1571		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1572			.get(&Self::rmrk_property_key(key)?)1573			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1574			.clone();15751576		Ok(nft_property)1577	}15781579	pub fn get_nft_property_decoded<V: Decode>(1580		collection_id: CollectionId,1581		nft_id: TokenId,1582		key: RmrkProperty,1583	) -> Result<V, DispatchError> {1584		Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1585	}15861587	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1588		<TokenData<T>>::contains_key((collection_id, nft_id))1589	}15901591	pub fn get_nft_type(1592		collection_id: CollectionId,1593		token_id: TokenId,1594	) -> Result<NftType, DispatchError> {1595		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1596			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1597	}15981599	pub fn ensure_nft_type(1600		collection_id: CollectionId,1601		token_id: TokenId,1602		nft_type: NftType,1603	) -> DispatchResult {1604		let actual_type = Self::get_nft_type(collection_id, token_id)?;1605		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);16061607		Ok(())1608	}16091610	pub fn ensure_nft_owner(1611		collection_id: CollectionId,1612		token_id: TokenId,1613		possible_owner: &T::CrossAccountId,1614		nesting_budget: &dyn budget::Budget,1615	) -> DispatchResult {1616		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1617			possible_owner.clone(),1618			collection_id,1619			token_id,1620			None,1621			nesting_budget,1622		)1623		.map_err(Self::map_unique_err_to_proxy)?;16241625		ensure!(is_owned, <Error<T>>::NoPermission);16261627		Ok(())1628	}16291630	pub fn filter_user_properties<Key, Value, R, Mapper>(1631		collection_id: CollectionId,1632		token_id: Option<TokenId>,1633		filter_keys: Option<Vec<RmrkPropertyKey>>,1634		mapper: Mapper,1635	) -> Result<Vec<R>, DispatchError>1636	where1637		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1638		Value: Decode + Default,1639		Mapper: Fn(Key, Value) -> R,1640	{1641		filter_keys1642			.map(|keys| {1643				let properties = keys1644					.into_iter()1645					.filter_map(|key| {1646						let key: Key = key.try_into().ok()?;16471648						let value = match token_id {1649							Some(token_id) => Self::get_nft_property_decoded(1650								collection_id,1651								token_id,1652								UserProperty(key.as_ref()),1653							),1654							None => Self::get_collection_property_decoded(1655								collection_id,1656								UserProperty(key.as_ref()),1657							),1658						}1659						.ok()?;16601661						Some(mapper(key, value))1662					})1663					.collect();16641665				Ok(properties)1666			})1667			.unwrap_or_else(|| {1668				let properties =1669					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16701671				Ok(properties)1672			})1673	}16741675	pub fn iterate_user_properties<Key, Value, R, Mapper>(1676		collection_id: CollectionId,1677		token_id: Option<TokenId>,1678		mapper: Mapper,1679	) -> Result<impl Iterator<Item = R>, DispatchError>1680	where1681		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1682		Value: Decode + Default,1683		Mapper: Fn(Key, Value) -> R,1684	{1685		let properties = match token_id {1686			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1687			None => <PalletCommon<T>>::collection_properties(collection_id),1688		};16891690		let properties = properties.into_iter().filter_map(move |(key, value)| {1691			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16921693			let key: Key = key.to_vec().try_into().ok()?;1694			let value: Value = value.decode().ok()?;16951696			Some(mapper(key, value))1697		});16981699		Ok(properties)1700	}17011702	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1703		map_unique_err_to_proxy! {1704			match err {1705				CommonError::NoPermission => NoPermission,1706				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1707				CommonError::PublicMintingNotAllowed => NoPermission,1708				CommonError::TokenNotFound => NoAvailableNftId,1709				CommonError::ApprovedValueTooLow => NoPermission,1710				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1711				StructureError::TokenNotFound => NoAvailableNftId,1712				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1713			}1714		}1715	}1716}
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//! # RMRK Core Proxy Pallet18//! 19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//! 21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//! 25//! ## Overview26//! 27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core, 28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//! 32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//! 35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//! 38//! ### What is RMRK?39//! 40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives. 41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//! 43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources, 45//! and more.46//! 47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK spec repository: <https://github.com/rmrk-team/rmrk-spec>52//! 53//! ## Proxy Implementation54//! 55//! An external user is supposed to be able to utilize this proxy as they would56//! utilize RMRK, and get exactly the same results. Normally, Unique transactions57//! are off-limits to RMRK collections and tokens, and vice versa. However,58//! the information stored on chain can be freely interpreted by storage reads and RPCs.59//! 60//! ### ID Mapping61//! 62//! RMRK's collections' IDs are counted independently of Unique's and start at 0.63//! Note that tokens' IDs still start at 1.64//! The collections themselves, as well as tokens, are stored as Unique collections,65//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).66//! 67//! ### External/Internal Collection Insulation68//! 69//! A Unique transaction cannot target collections purposed for RMRK,70//! and they are flagged as `external` to specify that. On the other hand, 71//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.72//! 73//! ### Native Properties74//! 75//! Many of RMRK's native parameters are stored as scoped properties of a collection 76//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`77//! is an unacceptable symbol in user-defined proeprties, which, along with other safeguards,78//! makes them impossible to tamper with.79//! 80//! ### Collection and NFT Types81//! 82//! RMRK introduces the concept of a Base, which is a catalgoue of Parts, 83//! possible components of an NFT. Due to its similarity with the functionality84//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes85//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and 86//! [`NftType`](pallet_rmrk_core::misc::NftType).87//! 88//! ## Interface89//! 90//! ### Dispatchables91//! 92//! - `create_collection` - Create a new collection of NFTs.93//! - `destroy_collection` - Destroy a collection.94//! - `change_collection_issuer` - Change the issuer of a collection. 95//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).96//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**97//! - `mint_nft` - Mint an NFT in a specified collection.98//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.99//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.100//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.101//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.102//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.103//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.104//! - `set_property` - Add or edit a custom user property of a token or a collection.105//! - `set_priority` - Set a different order of resource priorities for an NFT.106//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.107//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.108//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.109//! - `remove_resource` - Remove and erase a resource from an NFT.110111#![cfg_attr(not(feature = "std"), no_std)]112113use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};114use frame_system::{pallet_prelude::*, ensure_signed};115use sp_runtime::{DispatchError, Permill, traits::StaticLookup};116use sp_std::{117	vec::Vec,118	collections::{btree_set::BTreeSet, btree_map::BTreeMap},119};120use up_data_structs::{*, mapping::TokenAddressMapping};121use pallet_common::{122	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,123};124use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};125use pallet_structure::{Pallet as PalletStructure, Error as StructureError};126use pallet_evm::account::CrossAccountId;127use core::convert::AsRef;128129pub use pallet::*;130131#[cfg(feature = "runtime-benchmarks")]132pub mod benchmarking;133pub mod misc;134pub mod property;135pub mod rpc;136pub mod weights;137138pub type SelfWeightOf<T> = <T as Config>::WeightInfo;139140use weights::WeightInfo;141use misc::*;142pub use property::*;143144use RmrkProperty::*;145146/// Maximum number of levels of depth in the token nesting tree.147pub const NESTING_BUDGET: u32 = 5;148149type PendingTarget = (CollectionId, TokenId);150type PendingChild = (RmrkCollectionId, RmrkNftId);151type PendingChildrenSet = BTreeSet<PendingChild>;152153type BasesMap = BTreeMap<RmrkBaseId, u32>;154155#[frame_support::pallet]156pub mod pallet {157	use super::*;158	use pallet_evm::account;159160	#[pallet::config]161	pub trait Config:162		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config163	{164		/// Overarching event type.165		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;166		167		/// The weight information of this pallet.168		type WeightInfo: WeightInfo;169	}170171	/// Latest yet-unused collection ID.172	#[pallet::storage]173	#[pallet::getter(fn collection_index)]174	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;175176	/// Mapping from RMRK collection ID to Unique's.177	#[pallet::storage]178	pub type UniqueCollectionId<T: Config> =179		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;180181	#[pallet::pallet]182	#[pallet::generate_store(pub(super) trait Store)]183	pub struct Pallet<T>(_);184185	#[pallet::event]186	#[pallet::generate_deposit(pub(super) fn deposit_event)]187	pub enum Event<T: Config> {188		CollectionCreated {189			issuer: T::AccountId,190			collection_id: RmrkCollectionId,191		},192		CollectionDestroyed {193			issuer: T::AccountId,194			collection_id: RmrkCollectionId,195		},196		IssuerChanged {197			old_issuer: T::AccountId,198			new_issuer: T::AccountId,199			collection_id: RmrkCollectionId,200		},201		CollectionLocked {202			issuer: T::AccountId,203			collection_id: RmrkCollectionId,204		},205		NftMinted {206			owner: T::AccountId,207			collection_id: RmrkCollectionId,208			nft_id: RmrkNftId,209		},210		NFTBurned {211			owner: T::AccountId,212			nft_id: RmrkNftId,213		},214		NFTSent {215			sender: T::AccountId,216			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,217			collection_id: RmrkCollectionId,218			nft_id: RmrkNftId,219			approval_required: bool,220		},221		NFTAccepted {222			sender: T::AccountId,223			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,224			collection_id: RmrkCollectionId,225			nft_id: RmrkNftId,226		},227		NFTRejected {228			sender: T::AccountId,229			collection_id: RmrkCollectionId,230			nft_id: RmrkNftId,231		},232		PropertySet {233			collection_id: RmrkCollectionId,234			maybe_nft_id: Option<RmrkNftId>,235			key: RmrkKeyString,236			value: RmrkValueString,237		},238		ResourceAdded {239			nft_id: RmrkNftId,240			resource_id: RmrkResourceId,241		},242		ResourceRemoval {243			nft_id: RmrkNftId,244			resource_id: RmrkResourceId,245		},246		ResourceAccepted {247			nft_id: RmrkNftId,248			resource_id: RmrkResourceId,249		},250		ResourceRemovalAccepted {251			nft_id: RmrkNftId,252			resource_id: RmrkResourceId,253		},254		PrioritySet {255			collection_id: RmrkCollectionId,256			nft_id: RmrkNftId,257		},258	}259260	#[pallet::error]261	pub enum Error<T> {262		/* Unique proxy-specific events */263		/// Property of the type of RMRK collection could not be read successfully.264		CorruptedCollectionType,265		// NftTypeEncodeError,266		/// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).267		RmrkPropertyKeyIsTooLong,268		/// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).269		RmrkPropertyValueIsTooLong,270		/// Could not find a property by the supplied key.271		RmrkPropertyIsNotFound,272		/// Something went wrong when decoding encoded data from the storage. 273		/// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.274		UnableToDecodeRmrkData,275276		/* RMRK compatible events */277		/// Only destroying collections without tokens is allowed.278		CollectionNotEmpty,279		/// Could not find an ID for a collection. It is likely there were too many collections created on the chain.280		NoAvailableCollectionId,281		/// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection.282		NoAvailableNftId,283		/// Collection does not exist, has a wrong type, or does not map to a Unique ID.284		CollectionUnknown,285		/// No permission to perform action.286		NoPermission,287		/// Token is marked as non-transferable, and thus cannot be transferred.288		NonTransferable,289		/// Too many tokens created in the collection, no new ones are allowed.290		CollectionFullOrLocked,291		/// No such resource found.292		ResourceDoesntExist,293		/// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros. 294		/// Sending to self is redundant.295		CannotSendToDescendentOrSelf,296		/// Not the target owner of the sent NFT.297		CannotAcceptNonOwnedNft,298		/// Not the target owner of the sent NFT.299		CannotRejectNonOwnedNft,300		/// NFT was not sent and is not pending.301		CannotRejectNonPendingNft,302		/// Resource is not pending for the operation.303		ResourceNotPending,304		/// Could not find an ID for the resource. Is is likely there were too many resources created on an NFT.305		NoAvailableResourceId,306	}307308	#[pallet::call]309	impl<T: Config> Pallet<T> {310		// todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?311312		/// Create a new collection of NFTs.313		///314		/// # Permissions:315		/// * Anyone - will be assigned as the issuer of the collection.316		///317		/// # Arguments:318		/// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.319		/// - `max`: Optional maximum number of tokens.320		/// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs. 321		/// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.322		#[transactional]323		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]324		pub fn create_collection(325			origin: OriginFor<T>,326			metadata: RmrkString,327			max: Option<u32>,328			symbol: RmrkCollectionSymbol,329		) -> DispatchResult {330			let sender = ensure_signed(origin)?;331332			let limits = CollectionLimits {333				owner_can_transfer: Some(false),334				token_limit: max,335				..Default::default()336			};337338			let data = CreateCollectionData {339				limits: Some(limits),340				token_prefix: symbol341					.into_inner()342					.try_into()343					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,344				permissions: Some(CollectionPermissions {345					nesting: Some(NestingPermissions {346						token_owner: true,347						collection_admin: false,348						restricted: None,349						#[cfg(feature = "runtime-benchmarks")]350						permissive: false,351					}),352					..Default::default()353				}),354				..Default::default()355			};356357			let unique_collection_id = Self::init_collection(358				T::CrossAccountId::from_sub(sender.clone()),359				data,360				[361					Self::encode_rmrk_property(Metadata, &metadata)?,362					Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,363				]364				.into_iter(),365			)?;366			let rmrk_collection_id = <CollectionIndex<T>>::get();367368			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);369370			<PalletCommon<T>>::set_scoped_collection_property(371				unique_collection_id,372				RMRK_SCOPE,373				Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,374			)?;375376			<CollectionIndex<T>>::mutate(|n| *n += 1);377378			Self::deposit_event(Event::CollectionCreated {379				issuer: sender,380				collection_id: rmrk_collection_id,381			});382383			Ok(())384		}385386		/// Destroy a collection. 387		/// 388		/// Only empty collections can be destroyed. If it has any tokens, they must be burned first.389		///390		/// # Permissions:391		/// * Collection issuer392		///393		/// # Arguments:394		/// - `collection_id`: RMRK ID of the collection to destroy.395		#[transactional]396		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]397		pub fn destroy_collection(398			origin: OriginFor<T>,399			collection_id: RmrkCollectionId,400		) -> DispatchResult {401			let sender = ensure_signed(origin)?;402			let cross_sender = T::CrossAccountId::from_sub(sender.clone());403404			let collection = Self::get_typed_nft_collection(405				Self::unique_collection_id(collection_id)?,406				misc::CollectionType::Regular,407			)?;408			collection.check_is_external()?;409410			<PalletNft<T>>::destroy_collection(collection, &cross_sender)411				.map_err(Self::map_unique_err_to_proxy)?;412413			Self::deposit_event(Event::CollectionDestroyed {414				issuer: sender,415				collection_id,416			});417418			Ok(())419		}420421		/// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).422		/// 423		/// # Permissions:424		/// * Collection issuer425		///426		/// # Arguments:427		/// - `collection_id`: RMRK collection ID to change the issuer of.428		/// - `new_issuer`: Collection's new issuer.429		#[transactional]430		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]431		pub fn change_collection_issuer(432			origin: OriginFor<T>,433			collection_id: RmrkCollectionId,434			new_issuer: <T::Lookup as StaticLookup>::Source,435		) -> DispatchResult {436			let sender = ensure_signed(origin)?;437438			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;439			collection.check_is_external()?;440441			let new_issuer = T::Lookup::lookup(new_issuer)?;442443			Self::change_collection_owner(444				Self::unique_collection_id(collection_id)?,445				misc::CollectionType::Regular,446				sender.clone(),447				new_issuer.clone(),448			)?;449450			Self::deposit_event(Event::IssuerChanged {451				old_issuer: sender,452				new_issuer,453				collection_id,454			});455456			Ok(())457		}458459		/// "Lock" the collection and prevent new token creation. Cannot be undone.460		/// 461		/// # Permissions:462		/// * Collection issuer463		///464		/// # Arguments:465		/// - `collection_id`: RMRK ID of the collection to lock.466		#[transactional]467		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]468		pub fn lock_collection(469			origin: OriginFor<T>,470			collection_id: RmrkCollectionId,471		) -> DispatchResult {472			let sender = ensure_signed(origin)?;473			let cross_sender = T::CrossAccountId::from_sub(sender.clone());474475			let collection = Self::get_typed_nft_collection(476				Self::unique_collection_id(collection_id)?,477				misc::CollectionType::Regular,478			)?;479			collection.check_is_external()?;480481			Self::check_collection_owner(&collection, &cross_sender)?;482483			let token_count = collection.total_supply();484485			let mut collection = collection.into_inner();486			collection.limits.token_limit = Some(token_count);487			collection.save()?;488489			Self::deposit_event(Event::CollectionLocked {490				issuer: sender,491				collection_id,492			});493494			Ok(())495		}496497		/// Mint an NFT in a specified collection.498		///499		/// # Permissions:500		/// * Collection issuer501		/// 502		/// # Arguments:503		/// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).504		/// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.505		/// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.506		/// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.507		/// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.508		/// - `transferable`: Can this NFT be transferred? Cannot be changed.509		/// - `resources`: Resource data to be added to the NFT immediately after minting.510		#[transactional]511		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]512		pub fn mint_nft(513			origin: OriginFor<T>,514			owner: Option<T::AccountId>,515			collection_id: RmrkCollectionId,516			recipient: Option<T::AccountId>,517			royalty_amount: Option<Permill>,518			metadata: RmrkString,519			transferable: bool,520			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,521		) -> DispatchResult {522			let sender = ensure_signed(origin)?;523			let cross_sender = T::CrossAccountId::from_sub(sender.clone());524525			let owner = owner.unwrap_or(sender.clone());526			let cross_owner = T::CrossAccountId::from_sub(owner.clone());527528			let collection = Self::get_typed_nft_collection(529				Self::unique_collection_id(collection_id)?,530				misc::CollectionType::Regular,531			)?;532			collection.check_is_external()?;533534			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {535				recipient: recipient.unwrap_or_else(|| owner.clone()),536				amount,537			});538539			let nft_id = Self::create_nft(540				&cross_sender,541				&cross_owner,542				&collection,543				[544					Self::encode_rmrk_property(TokenType, &NftType::Regular)?,545					Self::encode_rmrk_property(Transferable, &transferable)?,546					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,547					Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,548					Self::encode_rmrk_property(Metadata, &metadata)?,549					Self::encode_rmrk_property(Equipped, &false)?,550					Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,551					Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,552					Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,553					Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,554				]555				.into_iter(),556			)557			.map_err(|err| match err {558				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),559				err => Self::map_unique_err_to_proxy(err),560			})?;561562			if let Some(resources) = resources {563				for resource in resources {564					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;565				}566			}567568			Self::deposit_event(Event::NftMinted {569				owner,570				collection_id,571				nft_id: nft_id.0,572			});573574			Ok(())575		}576577		/// Burn an NFT, destroying it and its nested tokens up to the specified limit. 578		/// If the burning budget is exceeded, the transaction is reverted.579		/// 580		/// This is the way to burn a nested token as well.581		/// 582		/// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).583		/// 584		/// # Permissions:585		/// * Token owner586		/// 587		/// # Arguments:588		/// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.589		/// - `nft_id`: ID of the NFT to be destroyed.590		/// - `max_burns`: Maximum number of tokens to burn, used for nesting. The transaction 591		/// is reverted if there are more tokens to burn in the nesting tree than this number.592		#[transactional]593		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]594		pub fn burn_nft(595			origin: OriginFor<T>,596			collection_id: RmrkCollectionId,597			nft_id: RmrkNftId,598			max_burns: u32,599		) -> DispatchResult {600			let sender = ensure_signed(origin)?;601			let cross_sender = T::CrossAccountId::from_sub(sender.clone());602603			let collection = Self::get_typed_nft_collection(604				Self::unique_collection_id(collection_id)?,605				misc::CollectionType::Regular,606			)?;607			collection.check_is_external()?;608609			Self::destroy_nft(610				cross_sender,611				Self::unique_collection_id(collection_id)?,612				nft_id.into(),613				max_burns,614				<Error<T>>::NoPermission,615			)616			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;617618			Self::deposit_event(Event::NFTBurned {619				owner: sender,620				nft_id,621			});622623			Ok(())624		}625626		/// Transfer an NFT from an account/NFT A to another account/NFT B.627		/// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].628		/// 629		/// If the target owner is an NFT owned by another account, then the NFT will enter630		/// the pending state and will have to be accepted by the other account.631		///632		/// # Permissions:633		/// - Token owner634		/// 635		/// # Arguments:636		/// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.637		/// - `nft_id`: ID of the NFT to be transferred.638		/// - `new_owner`: New owner of the nft which can be either an account or a NFT.639		#[transactional]640		#[pallet::weight(<SelfWeightOf<T>>::send())]641		pub fn send(642			origin: OriginFor<T>,643			rmrk_collection_id: RmrkCollectionId,644			rmrk_nft_id: RmrkNftId,645			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,646		) -> DispatchResult {647			let sender = ensure_signed(origin.clone())?;648			let cross_sender = T::CrossAccountId::from_sub(sender.clone());649650			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;651			let nft_id = rmrk_nft_id.into();652653			let collection =654				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;655			collection.check_is_external()?;656657			let token_data =658				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;659660			let from = token_data.owner;661662			ensure!(663				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,664				<Error<T>>::NonTransferable665			);666667			ensure!(668				Self::get_nft_property_decoded::<Option<PendingTarget>>(669					collection_id,670					nft_id,671					RmrkProperty::PendingNftAccept672				)?673				.is_none(),674				<Error<T>>::NoPermission675			);676677			let target_owner;678			let approval_required;679680			match new_owner {681				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {682					target_owner = T::CrossAccountId::from_sub(account_id.clone());683					approval_required = false;684				}685				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(686					target_collection_id,687					target_nft_id,688				) => {689					let target_collection_id = Self::unique_collection_id(target_collection_id)?;690691					let target_nft_budget = budget::Value::new(NESTING_BUDGET);692693					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(694						target_collection_id,695						target_nft_id.into(),696						Some((collection_id, nft_id)),697						&target_nft_budget,698					)699					.map_err(Self::map_unique_err_to_proxy)?;700701					approval_required = cross_sender != target_nft_owner;702703					if approval_required {704						target_owner = target_nft_owner;705706						<PalletNft<T>>::set_scoped_token_property(707							collection.id,708							nft_id,709							RMRK_SCOPE,710							Self::encode_rmrk_property::<Option<PendingTarget>>(711								PendingNftAccept,712								&Some((target_collection_id, target_nft_id.into())),713							)?,714						)?;715716						Self::insert_pending_child(717							(target_collection_id, target_nft_id.into()),718							(rmrk_collection_id, rmrk_nft_id),719						)?;720					} else {721						target_owner = T::CrossTokenAddressMapping::token_to_address(722							target_collection_id,723							target_nft_id.into(),724						);725					}726				}727			}728729			let src_nft_budget = budget::Value::new(NESTING_BUDGET);730731			<PalletNft<T>>::transfer_from(732				&collection,733				&cross_sender,734				&from,735				&target_owner,736				nft_id,737				&src_nft_budget,738			)739			.map_err(Self::map_unique_err_to_proxy)?;740741			Self::deposit_event(Event::NFTSent {742				sender,743				recipient: new_owner,744				collection_id: rmrk_collection_id,745				nft_id: rmrk_nft_id,746				approval_required,747			});748749			Ok(())750		}751752		/// Accept an NFT sent from another account to self or an owned NFT.753		/// 754		/// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.755		/// 756		/// # Permissions:757		/// - Token-owner-to-be758		///759		/// # Arguments:760		/// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.761		/// - `rmrk_nft_id`: ID of the NFT to be accepted.762		/// - `new_owner`: Either the sender's account ID or a sender-owned NFT, 763		/// whichever the accepted NFT was sent to.764		#[transactional]765		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]766		pub fn accept_nft(767			origin: OriginFor<T>,768			rmrk_collection_id: RmrkCollectionId,769			rmrk_nft_id: RmrkNftId,770			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,771		) -> DispatchResult {772			let sender = ensure_signed(origin.clone())?;773			let cross_sender = T::CrossAccountId::from_sub(sender.clone());774775			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;776			let nft_id = rmrk_nft_id.into();777778			let collection =779				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;780			collection.check_is_external()?;781782			let new_cross_owner = match new_owner {783				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {784					T::CrossAccountId::from_sub(account_id.clone())785				}786				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(787					target_collection_id,788					target_nft_id,789				) => {790					let target_collection_id = Self::unique_collection_id(target_collection_id)?;791792					T::CrossTokenAddressMapping::token_to_address(793						target_collection_id,794						TokenId(target_nft_id),795					)796				}797			};798799			let budget = budget::Value::new(NESTING_BUDGET);800801			<PalletNft<T>>::transfer(802				&collection,803				&cross_sender,804				&new_cross_owner,805				nft_id,806				&budget,807			)808			.map_err(|err| {809				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {810					<Error<T>>::CannotAcceptNonOwnedNft.into()811				} else {812					Self::map_unique_err_to_proxy(err)813				}814			})?;815816			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(817				collection_id,818				nft_id,819				RmrkProperty::PendingNftAccept,820			)?;821822			if let Some(pending_target) = pending_target {823				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;824825				<PalletNft<T>>::set_scoped_token_property(826					collection.id,827					nft_id,828					RMRK_SCOPE,829					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,830				)?;831			}832833			Self::deposit_event(Event::NFTAccepted {834				sender,835				recipient: new_owner,836				collection_id: rmrk_collection_id,837				nft_id: rmrk_nft_id,838			});839840			Ok(())841		}842843		/// Reject an NFT sent from another account to self or owned NFT.844		/// The NFT in question will not be sent back and burnt instead.845		/// 846		/// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.847		/// 848		/// # Permissions:849		/// - Token-owner-to-be-not850		/// 851		/// # Arguments:852		/// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.853		/// - `rmrk_nft_id`: ID of the NFT to be rejected.854		#[transactional]855		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]856		pub fn reject_nft(857			origin: OriginFor<T>,858			rmrk_collection_id: RmrkCollectionId,859			rmrk_nft_id: RmrkNftId,860		) -> DispatchResult {861			let sender = ensure_signed(origin)?;862			let cross_sender = T::CrossAccountId::from_sub(sender.clone());863864			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865			let nft_id = rmrk_nft_id.into();866867			let collection =868				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869			collection.check_is_external()?;870871			ensure!(872				<TokenData<T>>::get((collection_id, nft_id)).is_some(),873				<Error<T>>::NoAvailableNftId874			);875876			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(877				collection_id,878				nft_id,879				RmrkProperty::PendingNftAccept,880			)?;881882			match pending_target {883				Some(pending_target) => {884					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?885				}886				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),887			}888889			Self::destroy_nft(890				cross_sender,891				collection_id,892				nft_id,893				NESTING_BUDGET,894				<Error<T>>::CannotRejectNonOwnedNft,895			)896			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;897898			Self::deposit_event(Event::NFTRejected {899				sender,900				collection_id: rmrk_collection_id,901				nft_id: rmrk_nft_id,902			});903904			Ok(())905		}906907		/// Accept the addition of a newly created pending resource to an existing NFT.908		/// 909		/// This transaction is needed when a resource is created and assigned to an NFT910		/// by a non-owner, i.e. the collection issuer, with one of the 911		/// [`add_...` transactions](crate::pallet::Call::add_basic_resource).912		/// 913		/// # Permissions:914		/// - Token owner915		///916		/// # Arguments:917		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.918		/// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.919		/// - `resource_id`: ID of the newly created pending resource.920		#[transactional]921		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]922		pub fn accept_resource(923			origin: OriginFor<T>,924			rmrk_collection_id: RmrkCollectionId,925			rmrk_nft_id: RmrkNftId,926			resource_id: RmrkResourceId,927		) -> DispatchResult {928			let sender = ensure_signed(origin)?;929			let cross_sender = T::CrossAccountId::from_sub(sender);930931			let collection_id = Self::unique_collection_id(rmrk_collection_id)932				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;933			let collection =934				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935			collection.check_is_external()?;936937			let nft_id = rmrk_nft_id.into();938939			let budget = budget::Value::new(NESTING_BUDGET);940941			let nft_owner =942				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)943					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;944945			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {946				ensure!(res.pending, <Error<T>>::ResourceNotPending);947				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);948949				res.pending = false;950951				Ok(())952			})?;953954			Self::deposit_event(Event::<T>::ResourceAccepted {955				nft_id: rmrk_nft_id,956				resource_id,957			});958959			Ok(())960		}961962		/// Accept the removal of a removal-pending resource from an NFT.963		/// 964		/// This transaction is needed when a non-owner, i.e. the collection issuer, 965		/// requests a [removal](`crate::pallet::Call::remove_resource`) of a resource from an NFT.966		/// 967		/// # Permissions:968		/// - Token owner969		///970		/// # Arguments:971		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.972		/// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.973		/// - `resource_id`: ID of the removal-pending resource.974		#[transactional]975		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]976		pub fn accept_resource_removal(977			origin: OriginFor<T>,978			rmrk_collection_id: RmrkCollectionId,979			rmrk_nft_id: RmrkNftId,980			resource_id: RmrkResourceId,981		) -> DispatchResult {982			let sender = ensure_signed(origin)?;983			let cross_sender = T::CrossAccountId::from_sub(sender);984985			let collection_id = Self::unique_collection_id(rmrk_collection_id)986				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;987			let collection =988				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;989			collection.check_is_external()?;990991			let nft_id = rmrk_nft_id.into();992993			let budget = budget::Value::new(NESTING_BUDGET);994995			let nft_owner =996				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)997					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;998999			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10001001			let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10021003			let resource_info = <PalletNft<T>>::token_aux_property((1004				collection_id,1005				nft_id,1006				RMRK_SCOPE,1007				resource_id_key.clone(),1008			))1009			.ok_or(<Error<T>>::ResourceDoesntExist)?;10101011			let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10121013			ensure!(1014				resource_info.pending_removal,1015				<Error<T>>::ResourceNotPending1016			);10171018			<PalletNft<T>>::remove_token_aux_property(1019				collection_id,1020				nft_id,1021				RMRK_SCOPE,1022				resource_id_key,1023			);10241025			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1026				let base_id = resource.base;10271028				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1029			}10301031			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1032				nft_id: rmrk_nft_id,1033				resource_id,1034			});10351036			Ok(())1037		}10381039		/// Add or edit a custom user property, a key-value pair, describing the metadata 1040		/// of a token or a collection, on either one of these.1041		/// 1042		/// Note that in this proxy implementation many details regarding RMRK are stored 1043		/// as scoped properties prefixed with "rmrk:", normally inaccessible 1044		/// to external transactions and RPCs.1045		/// 1046		/// # Permissions:1047		/// - Collection issuer - in case of collection property1048		/// - Token owner - in case of NFT property1049		///1050		/// # Arguments:1051		/// - `rmrk_collection_id`: RMRK collection ID.1052		/// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1053		/// - `key`: Key of the custom property to be referenced by.1054		/// - `value`: Value of the custom property to be stored.1055		#[transactional]1056		#[pallet::weight(<SelfWeightOf<T>>::set_property())]1057		pub fn set_property(1058			origin: OriginFor<T>,1059			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,1060			maybe_nft_id: Option<RmrkNftId>,1061			key: RmrkKeyString,1062			value: RmrkValueString,1063		) -> DispatchResult {1064			let sender = ensure_signed(origin)?;1065			let sender = T::CrossAccountId::from_sub(sender);10661067			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1068			let collection =1069				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1070			collection.check_is_external()?;10711072			let budget = budget::Value::new(NESTING_BUDGET);10731074			match maybe_nft_id {1075				Some(nft_id) => {1076					let token_id: TokenId = nft_id.into();10771078					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1079					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;10801081					<PalletNft<T>>::set_scoped_token_property(1082						collection_id,1083						token_id,1084						RMRK_SCOPE,1085						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1086					)?;1087				}1088				None => {1089					let collection = Self::get_typed_nft_collection(1090						collection_id,1091						misc::CollectionType::Regular,1092					)?;10931094					Self::check_collection_owner(&collection, &sender)?;10951096					<PalletCommon<T>>::set_scoped_collection_property(1097						collection_id,1098						RMRK_SCOPE,1099						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1100					)?;1101				}1102			}11031104			Self::deposit_event(Event::PropertySet {1105				collection_id: rmrk_collection_id,1106				maybe_nft_id,1107				key,1108				value,1109			});11101111			Ok(())1112		}11131114		/// Set a different order of resource priorities for an NFT. Priorities can be used,1115		/// for example, for order of rendering.1116		/// 1117		/// Note that the priorities are not updated automatically, and are an empty vector1118		/// by default. There is no pre-set definition for the order to be particular,1119		/// it can be interpreted arbitrarily use-case by use-case.1120		/// 1121		/// # Permissions:1122		/// - Token owner1123		///1124		/// # Arguments:1125		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1126		/// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1127		/// - `priorities`: Ordered vector of resource IDs.1128		#[transactional]1129		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]1130		pub fn set_priority(1131			origin: OriginFor<T>,1132			rmrk_collection_id: RmrkCollectionId,1133			rmrk_nft_id: RmrkNftId,1134			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1135		) -> DispatchResult {1136			let sender = ensure_signed(origin)?;1137			let sender = T::CrossAccountId::from_sub(sender);11381139			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1140			let nft_id = rmrk_nft_id.into();11411142			let collection =1143				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1144			collection.check_is_external()?;11451146			let budget = budget::Value::new(NESTING_BUDGET);11471148			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1149			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11501151			<PalletNft<T>>::set_scoped_token_property(1152				collection_id,1153				nft_id,1154				RMRK_SCOPE,1155				Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1156			)?;11571158			Self::deposit_event(Event::<T>::PrioritySet {1159				collection_id: rmrk_collection_id,1160				nft_id: rmrk_nft_id,1161			});11621163			Ok(())1164		}11651166		/// Create and set/propose a basic resource for an NFT.1167		/// 1168		/// A resource is considered a part of an NFT, an additional piece of metadata1169		/// usually serving to add a piece of media on top of the root metadata, be it1170		/// a different wing on the root template bird or something entirely unrelated.1171		/// A basic resource is the simplest, lacking a base or composables.1172		/// 1173		/// See RMRK docs for more information and examples.1174		/// 1175		/// # Permissions:1176		/// - Collection issuer - if not the token owner, adding the resource will warrant 1177		/// the owner's [acceptance](crate::pallet::Call::accept_resource).1178		///1179		/// # Arguments:1180		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1181		/// - `nft_id`: ID of the NFT to assign a resource to.1182		/// - `resource`: Data of the resource to be created.1183		#[transactional]1184		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1185		pub fn add_basic_resource(1186			origin: OriginFor<T>,1187			rmrk_collection_id: RmrkCollectionId,1188			nft_id: RmrkNftId,1189			resource: RmrkBasicResource,1190		) -> DispatchResult {1191			let sender = ensure_signed(origin.clone())?;11921193			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1194			let collection =1195				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1196			collection.check_is_external()?;11971198			let resource_id = Self::resource_add(1199				sender,1200				collection_id,1201				nft_id.into(),1202				RmrkResourceTypes::Basic(resource),1203			)?;12041205			Self::deposit_event(Event::ResourceAdded {1206				nft_id,1207				resource_id,1208			});1209			Ok(())1210		}12111212		/// Create and set/propose a composable resource for an NFT.1213		/// 1214		/// A resource is considered a part of an NFT, an additional piece of metadata1215		/// usually serving to add a piece of media on top of the root metadata, be it1216		/// a different wing on the root template bird or something entirely unrelated.1217		/// A composable resource links to a base and has a subset of its parts it is composed of.1218		/// 1219		/// See RMRK docs for more information and examples.1220		/// 1221		/// # Permissions:1222		/// - Collection issuer - if not the token owner, adding the resource will warrant 1223		/// the owner's [acceptance](crate::pallet::Call::accept_resource).1224		///1225		/// # Arguments:1226		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1227		/// - `nft_id`: ID of the NFT to assign a resource to.1228		/// - `resource`: Data of the resource to be created.1229		#[transactional]1230		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1231		pub fn add_composable_resource(1232			origin: OriginFor<T>,1233			rmrk_collection_id: RmrkCollectionId,1234			nft_id: RmrkNftId,1235			resource: RmrkComposableResource,1236		) -> DispatchResult {1237			let sender = ensure_signed(origin.clone())?;12381239			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1240			let collection =1241				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1242			collection.check_is_external()?;12431244			let base_id = resource.base;12451246			let resource_id = Self::resource_add(1247				sender,1248				collection_id,1249				nft_id.into(),1250				RmrkResourceTypes::Composable(resource),1251			)?;12521253			<PalletNft<T>>::try_mutate_token_aux_property(1254				collection_id,1255				nft_id.into(),1256				RMRK_SCOPE,1257				Self::get_scoped_property_key(AssociatedBases)?,1258				|value| -> DispatchResult {1259					let mut bases: BasesMap = match value {1260						Some(value) => Self::decode_property_value(value)?,1261						None => BasesMap::new(),1262					};12631264					*bases.entry(base_id).or_insert(0) += 1;12651266					*value = Some(Self::encode_property_value(&bases)?);1267					Ok(())1268				},1269			)?;12701271			Self::deposit_event(Event::ResourceAdded {1272				nft_id,1273				resource_id,1274			});1275			Ok(())1276		}12771278		/// Create and set/propose a slot resource for an NFT.1279		/// 1280		/// A resource is considered a part of an NFT, an additional piece of metadata1281		/// usually serving to add a piece of media on top of the root metadata, be it1282		/// a different wing on the root template bird or something entirely unrelated.1283		/// A slot resource links to a base and a slot in it which it now occupies.1284		/// 1285		/// See RMRK docs for more information and examples.1286		/// 1287		/// # Permissions:1288		/// - Collection issuer - if not the token owner, adding the resource will warrant 1289		/// the owner's [acceptance](crate::pallet::Call::accept_resource).1290		///1291		/// # Arguments:1292		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1293		/// - `nft_id`: ID of the NFT to assign a resource to.1294		/// - `resource`: Data of the resource to be created.1295		#[transactional]1296		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1297		pub fn add_slot_resource(1298			origin: OriginFor<T>,1299			rmrk_collection_id: RmrkCollectionId,1300			nft_id: RmrkNftId,1301			resource: RmrkSlotResource,1302		) -> DispatchResult {1303			let sender = ensure_signed(origin.clone())?;13041305			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1306			let collection =1307				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1308			collection.check_is_external()?;13091310			let resource_id = Self::resource_add(1311				sender,1312				collection_id,1313				nft_id.into(),1314				RmrkResourceTypes::Slot(resource),1315			)?;13161317			Self::deposit_event(Event::ResourceAdded {1318				nft_id,1319				resource_id,1320			});1321			Ok(())1322		}13231324		/// Remove and erase a resource from an NFT.1325		/// 1326		/// If the sender does not own the NFT, then it will be pending confirmation,1327		/// and will have to be [accepted](crate::pallet::Call::accept_resource_removal) by the token owner.1328		/// 1329		/// # Permissions1330		/// - Collection issuer1331		/// 1332		/// # Arguments1333		/// - `collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1334		/// - `nft_id`: ID of the NFT with a resource to be removed.1335		/// - `resource_id`: ID of the resource to be removed.1336		#[transactional]1337		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1338		pub fn remove_resource(1339			origin: OriginFor<T>,1340			rmrk_collection_id: RmrkCollectionId,1341			nft_id: RmrkNftId,1342			resource_id: RmrkResourceId,1343		) -> DispatchResult {1344			let sender = ensure_signed(origin.clone())?;13451346			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1347			let collection =1348				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1349			collection.check_is_external()?;13501351			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13521353			Self::deposit_event(Event::ResourceRemoval {1354				nft_id,1355				resource_id,1356			});1357			Ok(())1358		}1359	}1360}13611362impl<T: Config> Pallet<T> {1363	/// Transform one of possible RMRK keys into a byte key with a RMRK scope.1364	pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1365		let key = rmrk_key.to_key::<T>()?;13661367		let scoped_key = RMRK_SCOPE1368			.apply(key)1369			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13701371		Ok(scoped_key)1372	}13731374	/// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet) 1375	/// and encoding the value from an arbitrary type into bytes.1376	pub fn encode_rmrk_property<E: Encode>(1377		rmrk_key: RmrkProperty,1378		value: &E,1379	) -> Result<Property, DispatchError> {1380		let key = rmrk_key.to_key::<T>()?;13811382		let value = Self::encode_property_value(value)?;13831384		let property = Property { key, value };13851386		Ok(property)1387	}13881389	/// Encode property value from an arbitrary type into bytes for storage.1390	pub fn encode_property_value<E: Encode, S: Get<u32>>(1391		value: &E,1392	) -> Result<BoundedBytes<S>, DispatchError> {1393		let value = value1394			.encode()1395			.try_into()1396			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;13971398		Ok(value)1399	}14001401	/// Decode property value from bytes into an arbitrary type.1402	pub fn decode_property_value<D: Decode, S: Get<u32>>(1403		vec: &BoundedBytes<S>,1404	) -> Result<D, DispatchError> {1405		vec.decode()1406			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1407	}14081409	/// Change the limit of a property value byte vector.1410	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1411	where1412		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1413	{1414		vec.rebind()1415			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1416	}14171418	/// Initialize a new NFT collection with certain RMRK-scoped properties.1419	/// 1420	/// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1421	fn init_collection(1422		sender: T::CrossAccountId,1423		data: CreateCollectionData<T::AccountId>,1424		properties: impl Iterator<Item = Property>,1425	) -> Result<CollectionId, DispatchError> {1426		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14271428		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1429			return Err(<Error<T>>::NoAvailableCollectionId.into());1430		}14311432		<PalletCommon<T>>::set_scoped_collection_properties(1433			collection_id?,1434			RMRK_SCOPE,1435			properties,1436		)?;14371438		collection_id1439	}14401441	/// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1442	/// 1443	/// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1444	pub fn create_nft(1445		sender: &T::CrossAccountId,1446		owner: &T::CrossAccountId,1447		collection: &NonfungibleHandle<T>,1448		properties: impl Iterator<Item = Property>,1449	) -> Result<TokenId, DispatchError> {1450		let data = CreateNftExData {1451			properties: BoundedVec::default(),1452			owner: owner.clone(),1453		};14541455		let budget = budget::Value::new(NESTING_BUDGET);14561457		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;14581459		let nft_id = <PalletNft<T>>::current_token_id(collection.id);14601461		<PalletNft<T>>::set_scoped_token_properties(1462			collection.id,1463			nft_id,1464			RMRK_SCOPE,1465			properties,1466		)?;14671468		Ok(nft_id)1469	}14701471	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1472	/// 1473	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1474	fn destroy_nft(1475		sender: T::CrossAccountId,1476		collection_id: CollectionId,1477		token_id: TokenId,1478		max_burns: u32,1479		error_if_not_owned: Error<T>,1480	) -> DispatchResultWithPostInfo {1481		let collection =1482			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;14831484		let token_data =1485			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;14861487		let from = token_data.owner;14881489		let owner_check_budget = budget::Value::new(NESTING_BUDGET);14901491		ensure!(1492			<PalletStructure<T>>::check_indirectly_owned(1493				sender.clone(),1494				collection_id,1495				token_id,1496				None,1497				&owner_check_budget1498			)?,1499			error_if_not_owned,1500		);15011502		let burns_budget = budget::Value::new(max_burns);1503		let breadth_budget = budget::Value::new(max_burns);15041505		<PalletNft<T>>::burn_recursively(1506			&collection,1507			&from,1508			token_id,1509			&burns_budget,1510			&breadth_budget,1511		)1512	}15131514	/// Add a sent token pending acceptance to the target owning token as a property.1515	fn insert_pending_child(1516		target: (CollectionId, TokenId),1517		child: (RmrkCollectionId, RmrkNftId),1518	) -> DispatchResult {1519		Self::mutate_pending_children(target, |pending_children| {1520			pending_children.insert(child);1521		})1522	}15231524	/// Remove a sent token pending acceptance from the target token's properties.1525	fn remove_pending_child(1526		target: (CollectionId, TokenId),1527		child: (RmrkCollectionId, RmrkNftId),1528	) -> DispatchResult {1529		Self::mutate_pending_children(target, |pending_children| {1530			pending_children.remove(&child);1531		})1532	}15331534	/// Apply a mutation to the property of a token containing sent tokens 1535	/// that are currently pending acceptance.1536	fn mutate_pending_children(1537		(target_collection_id, target_nft_id): (CollectionId, TokenId),1538		f: impl FnOnce(&mut PendingChildrenSet),1539	) -> DispatchResult {1540		<PalletNft<T>>::try_mutate_token_aux_property(1541			target_collection_id,1542			target_nft_id,1543			RMRK_SCOPE,1544			Self::get_scoped_property_key(PendingChildren)?,1545			|pending_children| -> DispatchResult {1546				let mut map = match pending_children {1547					Some(map) => Self::decode_property_value(map)?,1548					None => PendingChildrenSet::new(),1549				};15501551				f(&mut map);15521553				*pending_children = Some(Self::encode_property_value(&map)?);15541555				Ok(())1556			},1557		)1558	}15591560	/// Get an iterator from a token's property containing tokens sent to it 1561	/// that are currently pending acceptance.1562	fn iterate_pending_children(1563		collection_id: CollectionId,1564		nft_id: TokenId,1565	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1566		let property = <PalletNft<T>>::token_aux_property((1567			collection_id,1568			nft_id,1569			RMRK_SCOPE,1570			Self::get_scoped_property_key(PendingChildren)?,1571		));15721573		let pending_children = match property {1574			Some(map) => Self::decode_property_value(&map)?,1575			None => PendingChildrenSet::new(),1576		};15771578		Ok(pending_children.into_iter())1579	}15801581	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1582	/// Thus, the returned resource ID should be used.1583	fn acquire_next_resource_id(1584		collection_id: CollectionId,1585		nft_id: TokenId,1586	) -> Result<RmrkResourceId, DispatchError> {1587		let resource_id: RmrkResourceId =1588			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;15891590		let next_id = resource_id1591			.checked_add(1)1592			.ok_or(<Error<T>>::NoAvailableResourceId)?;15931594		<PalletNft<T>>::set_scoped_token_property(1595			collection_id,1596			nft_id,1597			RMRK_SCOPE,1598			Self::encode_rmrk_property(NextResourceId, &next_id)?,1599		)?;16001601		Ok(resource_id)1602	}16031604	/// Create and add a resource for a regular NFT, mark it as pending if the sender 1605	/// is not the token owner. The sender must be the collection owner.1606	fn resource_add(1607		sender: T::AccountId,1608		collection_id: CollectionId,1609		nft_id: TokenId,1610		resource: RmrkResourceTypes,1611	) -> Result<RmrkResourceId, DispatchError> {1612		let collection =1613			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1614		ensure!(collection.owner == sender, Error::<T>::NoPermission);16151616		let sender = T::CrossAccountId::from_sub(sender);1617		let budget = budget::Value::new(NESTING_BUDGET);16181619		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1620			.map_err(Self::map_unique_err_to_proxy)?;16211622		let pending = sender != nft_owner;16231624		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16251626		let resource_info = RmrkResourceInfo {1627			id,1628			resource,1629			pending,1630			pending_removal: false,1631		};16321633		<PalletNft<T>>::try_mutate_token_aux_property(1634			collection_id,1635			nft_id,1636			RMRK_SCOPE,1637			Self::get_scoped_property_key(ResourceId(id))?,1638			|value| -> DispatchResult {1639				*value = Some(Self::encode_property_value(&resource_info)?);16401641				Ok(())1642			},1643		)?;16441645		Ok(id)1646	}16471648	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1649	/// The sender must be the collection owner.1650	fn resource_remove(1651		sender: T::AccountId,1652		collection_id: CollectionId,1653		nft_id: TokenId,1654		resource_id: RmrkResourceId,1655	) -> DispatchResult {1656		let collection =1657			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1658		ensure!(collection.owner == sender, Error::<T>::NoPermission);16591660		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16611662		let resource = <PalletNft<T>>::token_aux_property((1663			collection_id,1664			nft_id,1665			RMRK_SCOPE,1666			resource_id_key.clone(),1667		))1668		.ok_or(<Error<T>>::ResourceDoesntExist)?;16691670		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16711672		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1673		let topmost_owner =1674			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16751676		let sender = T::CrossAccountId::from_sub(sender);1677		if topmost_owner == sender {1678			<PalletNft<T>>::remove_token_aux_property(1679				collection_id,1680				nft_id,1681				RMRK_SCOPE,1682				Self::get_scoped_property_key(ResourceId(resource_id))?,1683			);16841685			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1686				let base_id = resource.base;16871688				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1689			}1690		} else {1691			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1692				res.pending_removal = true;16931694				Ok(())1695			})?;1696		}16971698		Ok(())1699	}17001701	/// Remove one usage of a base from an NFT's property of associated bases. The base will stay, however, 1702	/// if the count of resources using the base is still non-zero.1703	fn remove_associated_base_id(1704		collection_id: CollectionId,1705		nft_id: TokenId,1706		base_id: RmrkBaseId,1707	) -> DispatchResult {1708		<PalletNft<T>>::try_mutate_token_aux_property(1709			collection_id,1710			nft_id,1711			RMRK_SCOPE,1712			Self::get_scoped_property_key(AssociatedBases)?,1713			|value| -> DispatchResult {1714				let mut bases: BasesMap = match value {1715					Some(value) => Self::decode_property_value(value)?,1716					None => BasesMap::new(),1717				};17181719				let remaining = bases.get(&base_id);17201721				if let Some(remaining) = remaining {1722					if let Some(0) | None = remaining.checked_sub(1) {1723						bases.remove(&base_id);1724					}1725				}17261727				*value = Some(Self::encode_property_value(&bases)?);1728				Ok(())1729			},1730		)1731	}17321733	/// Apply a mutation to a resource stored in the token properties of an NFT.1734	fn try_mutate_resource_info(1735		collection_id: CollectionId,1736		nft_id: TokenId,1737		resource_id: RmrkResourceId,1738		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1739	) -> DispatchResult {1740		<PalletNft<T>>::try_mutate_token_aux_property(1741			collection_id,1742			nft_id,1743			RMRK_SCOPE,1744			Self::get_scoped_property_key(ResourceId(resource_id))?,1745			|value| match value {1746				Some(value) => {1747					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17481749					f(&mut resource_info)?;17501751					*value = Self::encode_property_value(&resource_info)?;17521753					Ok(())1754				}1755				None => Err(<Error<T>>::ResourceDoesntExist.into()),1756			},1757		)1758	}17591760	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1761	fn change_collection_owner(1762		collection_id: CollectionId,1763		collection_type: misc::CollectionType,1764		sender: T::AccountId,1765		new_owner: T::AccountId,1766	) -> DispatchResult {1767		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1768		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17691770		let mut collection = collection.into_inner();17711772		collection.owner = new_owner;1773		collection.save()1774	}17751776	/// Ensure that an account is the collection owner/issuer, return an error if not.1777	pub fn check_collection_owner(1778		collection: &NonfungibleHandle<T>,1779		account: &T::CrossAccountId,1780	) -> DispatchResult {1781		collection1782			.check_is_owner(account)1783			.map_err(Self::map_unique_err_to_proxy)1784	}17851786	/// Get the latest yet-unused RMRK collection index from the storage.1787	pub fn last_collection_idx() -> RmrkCollectionId {1788		<CollectionIndex<T>>::get()1789	}17901791	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1792	pub fn unique_collection_id(1793		rmrk_collection_id: RmrkCollectionId,1794	) -> Result<CollectionId, DispatchError> {1795		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1796			.map_err(|_| <Error<T>>::CollectionUnknown.into())1797	}17981799	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1800	pub fn rmrk_collection_id(1801		unique_collection_id: CollectionId,1802	) -> Result<RmrkCollectionId, DispatchError> {1803		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1804	}18051806	/// Fetch a Unique NFT collection.1807	pub fn get_nft_collection(1808		collection_id: CollectionId,1809	) -> Result<NonfungibleHandle<T>, DispatchError> {1810		let collection = <CollectionHandle<T>>::try_get(collection_id)1811			.map_err(|_| <Error<T>>::CollectionUnknown)?;18121813		match collection.mode {1814			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1815			_ => Err(<Error<T>>::CollectionUnknown.into()),1816		}1817	}18181819	/// Check if an NFT collection with such an ID exists.1820	pub fn collection_exists(collection_id: CollectionId) -> bool {1821		<CollectionHandle<T>>::try_get(collection_id).is_ok()1822	}18231824	/// Fetch and decode a RMRK-scoped collection property value in bytes.1825	pub fn get_collection_property(1826		collection_id: CollectionId,1827		key: RmrkProperty,1828	) -> Result<PropertyValue, DispatchError> {1829		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1830			.get(&Self::get_scoped_property_key(key)?)1831			.ok_or(<Error<T>>::CollectionUnknown)?1832			.clone();18331834		Ok(collection_property)1835	}18361837	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1838	pub fn get_collection_property_decoded<V: Decode>(1839		collection_id: CollectionId,1840		key: RmrkProperty,1841	) -> Result<V, DispatchError> {1842		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1843	}18441845	/// Get the type of a collection stored in it as a scoped property.1846	/// 1847	/// RMRK Core proxy differentiates between regular collections as well as RMRK bases as collections.1848	pub fn get_collection_type(1849		collection_id: CollectionId,1850	) -> Result<misc::CollectionType, DispatchError> {1851		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1852			if err != <Error<T>>::CollectionUnknown.into() {1853				<Error<T>>::CorruptedCollectionType.into()1854			} else {1855				err1856			}1857		})1858	}18591860	/// Ensure that the type of the collection equals the provided type,1861	/// otherwise return an error.1862	pub fn ensure_collection_type(1863		collection_id: CollectionId,1864		collection_type: misc::CollectionType,1865	) -> DispatchResult {1866		let actual_type = Self::get_collection_type(collection_id)?;1867		ensure!(1868			actual_type == collection_type,1869			<CommonError<T>>::NoPermission1870		);18711872		Ok(())1873	}18741875	/// Fetch an NFT collection, but make sure it has the appropriate type.1876	pub fn get_typed_nft_collection(1877		collection_id: CollectionId,1878		collection_type: misc::CollectionType,1879	) -> Result<NonfungibleHandle<T>, DispatchError> {1880		Self::ensure_collection_type(collection_id, collection_type)?;18811882		Self::get_nft_collection(collection_id)1883	}18841885	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection), 1886	/// but also return the Unique collection ID.1887	pub fn get_typed_nft_collection_mapped(1888		rmrk_collection_id: RmrkCollectionId,1889		collection_type: misc::CollectionType,1890	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1891		let unique_collection_id = match collection_type {1892			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1893			_ => rmrk_collection_id.into(),1894		};18951896		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;18971898		Ok((collection, unique_collection_id))1899	}19001901	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1902	pub fn get_nft_property(1903		collection_id: CollectionId,1904		nft_id: TokenId,1905		key: RmrkProperty,1906	) -> Result<PropertyValue, DispatchError> {1907		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1908			.get(&Self::get_scoped_property_key(key)?)1909			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1910			.clone();19111912		Ok(nft_property)1913	}19141915	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1916	pub fn get_nft_property_decoded<V: Decode>(1917		collection_id: CollectionId,1918		nft_id: TokenId,1919		key: RmrkProperty,1920	) -> Result<V, DispatchError> {1921		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1922	}19231924	/// Check that an NFT exists.1925	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1926		<TokenData<T>>::contains_key((collection_id, nft_id))1927	}19281929	/// Get the type of an NFT stored in it as a scoped property.1930	/// 1931	/// RMRK Core proxy differentiates between regular NFTs, and RMRK parts and themes.1932	pub fn get_nft_type(1933		collection_id: CollectionId,1934		token_id: TokenId,1935	) -> Result<NftType, DispatchError> {1936		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1937			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1938	}19391940	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1941	pub fn ensure_nft_type(1942		collection_id: CollectionId,1943		token_id: TokenId,1944		nft_type: NftType,1945	) -> DispatchResult {1946		let actual_type = Self::get_nft_type(collection_id, token_id)?;1947		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19481949		Ok(())1950	}19511952	/// Ensure that an account is the owner of the token, either directly 1953	/// or at the top of the nesting hierarchy; return an error if it is not.1954	pub fn ensure_nft_owner(1955		collection_id: CollectionId,1956		token_id: TokenId,1957		possible_owner: &T::CrossAccountId,1958		nesting_budget: &dyn budget::Budget,1959	) -> DispatchResult {1960		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1961			possible_owner.clone(),1962			collection_id,1963			token_id,1964			None,1965			nesting_budget,1966		)1967		.map_err(Self::map_unique_err_to_proxy)?;19681969		ensure!(is_owned, <Error<T>>::NoPermission);19701971		Ok(())1972	}19731974	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied, 1975	/// or, if None are provided, return all non-scoped properties.1976	pub fn filter_user_properties<Key, Value, R, Mapper>(1977		collection_id: CollectionId,1978		token_id: Option<TokenId>,1979		filter_keys: Option<Vec<RmrkPropertyKey>>,1980		mapper: Mapper,1981	) -> Result<Vec<R>, DispatchError>1982	where1983		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1984		Value: Decode + Default,1985		Mapper: Fn(Key, Value) -> R,1986	{1987		filter_keys1988			.map(|keys| {1989				let properties = keys1990					.into_iter()1991					.filter_map(|key| {1992						let key: Key = key.try_into().ok()?;19931994						let value = match token_id {1995							Some(token_id) => Self::get_nft_property_decoded(1996								collection_id,1997								token_id,1998								UserProperty(key.as_ref()),1999							),2000							None => Self::get_collection_property_decoded(2001								collection_id,2002								UserProperty(key.as_ref()),2003							),2004						}2005						.ok()?;20062007						Some(mapper(key, value))2008					})2009					.collect();20102011				Ok(properties)2012			})2013			.unwrap_or_else(|| {2014				let properties =2015					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20162017				Ok(properties)2018			})2019	}20202021	/// Get all non-scoped properties from a collection or a token, and apply some transformation 2022	/// to each key-value pair.2023	pub fn iterate_user_properties<Key, Value, R, Mapper>(2024		collection_id: CollectionId,2025		token_id: Option<TokenId>,2026		mapper: Mapper,2027	) -> Result<impl Iterator<Item = R>, DispatchError>2028	where2029		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2030		Value: Decode + Default,2031		Mapper: Fn(Key, Value) -> R,2032	{2033		let properties = match token_id {2034			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2035			None => <PalletCommon<T>>::collection_properties(collection_id),2036		};20372038		let properties = properties.into_iter().filter_map(move |(key, value)| {2039			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20402041			let key: Key = key.to_vec().try_into().ok()?;2042			let value: Value = value.decode().ok()?;20432044			Some(mapper(key, value))2045		});20462047		Ok(properties)2048	}20492050	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2051	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2052		map_unique_err_to_proxy! {2053			match err {2054				CommonError::NoPermission => NoPermission,2055				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2056				CommonError::PublicMintingNotAllowed => NoPermission,2057				CommonError::TokenNotFound => NoAvailableNftId,2058				CommonError::ApprovedValueTooLow => NoPermission,2059				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2060				StructureError::TokenNotFound => NoAvailableNftId,2061				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2062			}2063		}2064	}2065}
modifiedpallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -14,9 +14,13 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
+//! Miscellaneous helpers and utilities used by the proxy pallet.
+
 use super::*;
 use codec::{Encode, Decode, Error};
 
+/// Match errors from one type to another and return an error 
+/// if a match is successful.
 #[macro_export]
 macro_rules! map_unique_err_to_proxy {
     (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ $(,)? }) => {
@@ -30,8 +34,9 @@
     };
 }
 
-// Utilize the RmrkCore pallet for access to Runtime errors.
+/// Interface to decode bytes from a bounded vector into an arbitrary type.
 pub trait RmrkDecode<T: Decode, S> {
+	/// Try to decode bytes from a bounded vector into an arbitrary type.
 	fn decode(&self) -> Result<T, Error>;
 }
 
@@ -43,8 +48,9 @@
 	}
 }
 
-// Utilize the RmrkCore pallet for access to Runtime errors.
+/// Interface to "rebind", change the limit of a bounded byte vector.
 pub trait RmrkRebind<T, S> {
+	/// Try to change the limit of a bounded byte vector.
 	fn rebind(&self) -> Result<BoundedVec<u8, S>, Error>;
 }
 
@@ -58,12 +64,16 @@
 	}
 }
 
+/// RMRK Base shares functionality with a regular collection, and is thus
+/// stored as one, but they are used for different purposes and need to be differentiated.
 #[derive(Encode, Decode, PartialEq, Eq)]
 pub enum CollectionType {
 	Regular,
 	Base,
 }
 
+/// RMRK Base, being stored as a collection, can have different kinds of tokens,
+/// all except the `Regular` type, which is attributed to `Regular` collection.
 #[derive(Encode, Decode, PartialEq, Eq)]
 pub enum NftType {
 	Regular,
modifiedpallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -14,13 +14,21 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
+//! Details of storing and handling RMRK properties.
+
 use super::*;
 use up_data_structs::PropertyScope;
 use core::convert::AsRef;
 
+/// Property prefix for storing resources.
 pub const RESOURCE_ID_PREFIX: &str = "rsid-";
+/// Property prefix for storing custom user-defined properties.
 pub const USER_PROPERTY_PREFIX: &str = "userprop-";
+/// Property scope for RMRK, used to signify that this property 
+/// was created and is used by RMRK.
+pub const RMRK_SCOPE: PropertyScope = PropertyScope::Rmrk;
 
+/// Predefined RMRK property keys for storage of RMRK data format on the Unique chain.
 pub enum RmrkProperty<'r> {
 	Metadata,
 	CollectionType,
@@ -49,6 +57,7 @@
 }
 
 impl<'r> RmrkProperty<'r> {
+	/// Convert a predefined RMRK property key enum into string bytes.
 	pub fn to_key<T: Config>(self) -> Result<PropertyKey, Error<T>> {
 		fn get_bytes<T: AsRef<[u8]>>(container: &T) -> &[u8] {
 			container.as_ref()
@@ -94,9 +103,10 @@
 	}
 }
 
+/// Strip a property key of its prefix and RMRK scope.
 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()?;
+	let key_prefix = RMRK_SCOPE.apply(key_prefix).ok()?;
 
 	key.as_slice()
 		.strip_prefix(key_prefix.as_slice())?
@@ -105,6 +115,7 @@
 		.ok()
 }
 
+/// Check that the key has the prefix.
 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
@@ -1,9 +1,29 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! Realizations of RMRK RPCs (remote procedure calls) related to the Core pallet.
+
 use super::*;
 
+/// Get the latest created collection ID.
 pub fn last_collection_idx<T: Config>() -> Result<RmrkCollectionId, DispatchError> {
 	Ok(<Pallet<T>>::last_collection_idx())
 }
 
+/// Get collection info by ID.
 pub fn collection_by_id<T: Config>(
 	collection_id: RmrkCollectionId,
 ) -> Result<Option<RmrkCollectionInfo<T::AccountId>>, DispatchError> {
@@ -29,6 +49,7 @@
 	}))
 }
 
+/// Get NFT info by collection and NFT IDs.
 pub fn nft_by_id<T: Config>(
 	collection_id: RmrkCollectionId,
 	nft_by_id: RmrkNftId,
@@ -83,6 +104,8 @@
 	}))
 }
 
+
+/// Get tokens owned by an account in a collection.
 pub fn account_tokens<T: Config>(
 	account_id: T::AccountId,
 	collection_id: RmrkCollectionId,
@@ -116,6 +139,7 @@
 	Ok(tokens)
 }
 
+/// Get tokens nested in an NFT - its direct children (not the children's children).
 pub fn nft_children<T: Config>(
 	collection_id: RmrkCollectionId,
 	nft_id: RmrkNftId,
@@ -152,6 +176,7 @@
 	)
 }
 
+/// Get collection properties, created by the user - not the proxy-specific properties.
 pub fn collection_properties<T: Config>(
 	collection_id: RmrkCollectionId,
 	filter_keys: Option<Vec<RmrkPropertyKey>>,
@@ -174,6 +199,7 @@
 	Ok(properties)
 }
 
+/// Get NFT properties, created by the user - not the proxy-specific properties.
 pub fn nft_properties<T: Config>(
 	collection_id: RmrkCollectionId,
 	nft_id: RmrkNftId,
@@ -199,6 +225,7 @@
 	Ok(properties)
 }
 
+/// Get data of resources of an NFT.
 pub fn nft_resources<T: Config>(
 	collection_id: RmrkCollectionId,
 	nft_id: RmrkNftId,
@@ -226,7 +253,7 @@
 			return None;
 		}
 
-		let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property(&value).ok()?;
+		let resource_info: RmrkResourceInfo = <Pallet<T>>::decode_property_value(&value).ok()?;
 
 		Some(resource_info)
 	})
@@ -235,6 +262,7 @@
 	Ok(resources)
 }
 
+/// Get the priority of a resource in an NFT.
 pub fn nft_resource_priority<T: Config>(
 	collection_id: RmrkCollectionId,
 	nft_id: RmrkNftId,
modifiedpallets/proxy-rmrk-equip/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-equip/src/benchmarking.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 use sp_std::vec;
 
 use frame_benchmarking::{benchmarks, account};
modifiedpallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -14,6 +14,88 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
+//! # RMRK Core Proxy Pallet
+//! 
+//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).
+//! 
+//! - [`Config`]
+//! - [`Call`]
+//! - [`Pallet`]
+//! 
+//! ## Overview
+//! 
+//! The RMRK Equip Proxy pallet mirrors the functionality of RMRK Equip, 
+//! binding its externalities to Unique's own underlying structure.
+//! It is purposed to mimic RMRK Equip exactly, allowing seamless integrations
+//! of solutions based on RMRK.
+//! 
+//! RMRK Equip itself contains functionality to equip NFTs, and work with Bases,
+//! Parts, and Themes.
+//! 
+//! Equip Proxy is responsible for a more specific area of RMRK, and heavily relies on the Core. 
+//! For a more foundational description of proxy implementation, please refer to [`pallet_rmrk_core`].
+//! 
+//! *Note*, that while RMRK itself is subject to active development and restructuring,
+//! the proxy may be caught temporarily out of date.
+//! 
+//! ### What is RMRK?
+//! 
+//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives. 
+//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.
+//! 
+//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,
+//! make use of specific changeable and partially shared metadata in the form of resources, 
+//! and more.
+//! 
+//! Visit RMRK documentation and repositories to learn more:
+//! - Docs: <https://docs.rmrk.app/getting-started/>
+//! - FAQ: <https://coda.io/@rmrk/faq>
+//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>
+//! - RMRK spec repository: <https://github.com/rmrk-team/rmrk-spec>
+//! 
+//! ## Proxy Implementation
+//! 
+//! An external user is supposed to be able to utilize this proxy as they would
+//! utilize RMRK, and get exactly the same results. Normally, Unique transactions
+//! are off-limits to RMRK collections and tokens, and vice versa. However,
+//! the information stored on chain can be freely interpreted by storage reads and RPCs.
+//! 
+//! ### ID Mapping
+//! 
+//! RMRK's collections' IDs are counted independently of Unique's and start at 0.
+//! Note that tokens' IDs still start at 1.
+//! The collections themselves, as well as tokens, are stored as Unique collections,
+//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).
+//! 
+//! ### External/Internal Collection Insulation
+//! 
+//! A Unique transaction cannot target collections purposed for RMRK,
+//! and they are flagged as `external` to specify that. On the other hand, 
+//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.
+//! 
+//! ### Native Properties
+//! 
+//! Many of RMRK's native parameters are stored as scoped properties of a collection 
+//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`
+//! is an unacceptable symbol in user-defined proeprties, which, along with other safeguards,
+//! makes them impossible to tamper with.
+//! 
+//! ### Collection and NFT Types
+//! 
+//! RMRK introduces the concept of a Base, which is a catalgoue of Parts, 
+//! possible components of an NFT. Due to its similarity with the functionality
+//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes
+//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and 
+//! [`NftType`](pallet_rmrk_core::misc::NftType).
+//! 
+//! ## Interface
+//! 
+//! ### Dispatchables
+//! 
+//! - `create_base` - Create a new Base.
+//! - `theme_add` - Add a Theme to a Base.
+//! - `equippable` - Update the array of Collections allowed to be equipped to a Base's specified Slot Part.
+
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};
@@ -45,15 +127,20 @@
 
 	#[pallet::config]
 	pub trait Config: frame_system::Config + pallet_rmrk_core::Config {
+		/// Overarching event type.
 		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+
+		/// The weight information of this pallet.
 		type WeightInfo: WeightInfo;
 	}
 
+	/// Map of a base ID and a part ID to an NFT in the base collection serving as the part.
 	#[pallet::storage]
 	#[pallet::getter(fn internal_part_id)]
 	pub type InernalPartId<T: Config> =
 		StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, RmrkPartId, TokenId>;
 
+	/// Checkmark that a base has a Theme NFT named "default".
 	#[pallet::storage]
 	#[pallet::getter(fn base_has_default_theme)]
 	pub type BaseHasDefaultTheme<T: Config> =
@@ -78,26 +165,36 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
+		/// No permission to perform action.
 		PermissionError,
+		/// Could not find an ID for a base collection. It is likely there were too many collections created on the chain.
 		NoAvailableBaseId,
+		/// Could not find a suitable ID for a part, likely too many part tokens were created in the base.
 		NoAvailablePartId,
+		/// Base collection linked to this ID does not exist.
 		BaseDoesntExist,
+		/// No theme named "default" is associated with the Base.
 		NeedsDefaultThemeFirst,
+		/// Part linked to this ID does not exist.
 		PartDoesntExist,
+		/// Cannot assign equippables to a fixed part.
 		NoEquippableOnFixedPart,
 	}
 
 	#[pallet::call]
 	impl<T: Config> Pallet<T> {
-		/// Creates a new Base.
-		/// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
+		/// Create a new Base.
+		/// 
+		/// Modeled after the [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
+		/// 
+		/// # Permissions
+		/// - Anyone - will be assigned as the issuer of the base.
 		///
-		/// Parameters:
-		/// - origin: Caller, will be assigned as the issuer of the Base
-		/// - base_type: media type, e.g. "svg"
-		/// - symbol: arbitrary client-chosen symbol
-		/// - parts: array of Fixed and Slot parts composing the base, confined in length by
-		///   RmrkPartsLimit
+		/// # Arguments:
+		/// - `base_type`: Arbitrary media type, e.g. "svg".
+		/// - `symbol`: Arbitrary client-chosen symbol.
+		/// - `parts`: Array of Fixed and Slot parts composing the base, 
+		/// confined in length by [`RmrkPartsLimit`](up_data_structs::RmrkPartsLimit).
 		#[transactional]
 		#[pallet::weight(<SelfWeightOf<T>>::create_base(parts.len() as u32))]
 		pub fn create_base(
@@ -131,8 +228,8 @@
 				collection_id,
 				PropertyScope::Rmrk,
 				[
-					<PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,
-					<PalletCore<T>>::rmrk_property(BaseType, &base_type)?,
+					<PalletCore<T>>::encode_rmrk_property(CollectionType, &misc::CollectionType::Base)?,
+					<PalletCore<T>>::encode_rmrk_property(BaseType, &base_type)?,
 				]
 				.into_iter(),
 			)?;
@@ -151,19 +248,21 @@
 			Ok(())
 		}
 
-		/// Adds a Theme to a Base.
-		/// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)
-		/// Themes are stored in the Themes storage
+		/// Add a Theme to a Base.
 		/// A Theme named "default" is required prior to adding other Themes.
+		/// 
+		/// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md).
 		///
-		/// Parameters:
-		/// - origin: The caller of the function, must be issuer of the base
-		/// - base_id: The Base containing the Theme to be updated
-		/// - theme: The Theme to add to the Base.  A Theme has a name and properties, which are an
+		/// # Permissions:
+		/// - Base issuer
+		/// 
+		/// # Arguments:
+		/// - `base_id`: Base ID containing the Theme to be updated.
+		/// - `theme`: Theme to add to the Base.  A Theme has a name and properties, which are an
 		///   array of [key, value, inherit].
-		///   - key: arbitrary BoundedString, defined by client
-		///   - value: arbitrary BoundedString, defined by client
-		///   - inherit: optional bool
+		///   - `key`: Arbitrary BoundedString, defined by client.
+		///   - `value`: Arbitrary BoundedString, defined by client.
+		///   - `inherit`: Optional bool.
 		#[transactional]
 		#[pallet::weight(<SelfWeightOf<T>>::theme_add(theme.properties.len() as u32))]
 		pub fn theme_add(
@@ -191,9 +290,9 @@
 				owner,
 				&collection,
 				[
-					<PalletCore<T>>::rmrk_property(TokenType, &NftType::Theme)?,
-					<PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,
-					<PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?,
+					<PalletCore<T>>::encode_rmrk_property(TokenType, &NftType::Theme)?,
+					<PalletCore<T>>::encode_rmrk_property(ThemeName, &theme.name)?,
+					<PalletCore<T>>::encode_rmrk_property(ThemeInherit, &theme.inherit)?,
 				]
 				.into_iter(),
 			)
@@ -204,7 +303,7 @@
 					collection_id,
 					token_id,
 					PropertyScope::Rmrk,
-					<PalletCore<T>>::rmrk_property(
+					<PalletCore<T>>::encode_rmrk_property(
 						UserProperty(property.key.as_slice()),
 						&property.value,
 					)?,
@@ -214,6 +313,17 @@
 			Ok(())
 		}
 
+		/// Update the array of Collections allowed to be equipped to a Base's specified Slot Part.
+		/// 
+		/// Modeled after [equippable interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/equippable.md).
+		///
+		/// # Permissions:
+		/// - Base issuer
+		/// 
+		/// # Arguments:
+		/// - `base_id`: Base containing the Slot Part to be updated.
+		/// - `part_id`: Slot Part whose Equippable List is being updated.
+		/// - `equippables`: List of equippables that will override the current Equippables list.
 		#[transactional]
 		#[pallet::weight(<SelfWeightOf<T>>::equippable())]
 		pub fn equippable(
@@ -253,7 +363,7 @@
 						base_collection_id,
 						part_id,
 						PropertyScope::Rmrk,
-						<PalletCore<T>>::rmrk_property(EquippableList, &equippables)?,
+						<PalletCore<T>>::encode_rmrk_property(EquippableList, &equippables)?,
 					)?;
 				}
 			}
@@ -266,6 +376,8 @@
 }
 
 impl<T: Config> Pallet<T> {
+	/// Create or renew an NFT serving as a part, setting its properties
+	/// to those of the part.
 	fn create_part(
 		sender: &T::CrossAccountId,
 		collection: &NonfungibleHandle<T>,
@@ -298,7 +410,7 @@
 					collection.id,
 					token_id,
 					PropertyScope::Rmrk,
-					<PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?,
+					<PalletCore<T>>::encode_rmrk_property(ExternalPartId, &part_id)?,
 				)?;
 
 				token_id
@@ -310,9 +422,9 @@
 			token_id,
 			PropertyScope::Rmrk,
 			[
-				<PalletCore<T>>::rmrk_property(TokenType, &nft_type)?,
-				<PalletCore<T>>::rmrk_property(Src, &src)?,
-				<PalletCore<T>>::rmrk_property(ZIndex, &z_index)?,
+				<PalletCore<T>>::encode_rmrk_property(TokenType, &nft_type)?,
+				<PalletCore<T>>::encode_rmrk_property(Src, &src)?,
+				<PalletCore<T>>::encode_rmrk_property(ZIndex, &z_index)?,
 			]
 			.into_iter(),
 		)?;
@@ -322,13 +434,15 @@
 				collection.id,
 				token_id,
 				PropertyScope::Rmrk,
-				<PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?,
+				<PalletCore<T>>::encode_rmrk_property(EquippableList, &part.equippable)?,
 			)?;
 		}
 
 		Ok(())
 	}
 
+	/// Ensure that the collection under the base ID is a base collection,
+	/// and fetch it.
 	fn get_base(base_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {
 		let collection =
 			<PalletCore<T>>::get_typed_nft_collection(base_id, misc::CollectionType::Base)
modifiedpallets/proxy-rmrk-equip/src/rpc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/rpc.rs
+++ b/pallets/proxy-rmrk-equip/src/rpc.rs
@@ -1,7 +1,26 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! Realizations of RMRK RPCs (remote procedure calls) related to the Equip pallet.
+
 use super::*;
 use pallet_rmrk_core::{misc, property::*};
 use sp_std::vec::Vec;
 
+/// Get base info by its ID.
 pub fn base<T: Config>(
 	base_id: RmrkBaseId,
 ) -> Result<Option<RmrkBaseInfo<T::AccountId>>, DispatchError> {
@@ -22,6 +41,7 @@
 	}))
 }
 
+/// Get all parts of a base.
 pub fn base_parts<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
 	use pallet_common::CommonCollectionOperations;
 
@@ -93,6 +113,7 @@
 	Ok(parts)
 }
 
+/// Get the theme names belonging to a base.
 pub fn theme_names<T: Config>(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
 	use pallet_common::CommonCollectionOperations;
 
@@ -124,6 +145,7 @@
 	Ok(theme_names)
 }
 
+/// Get theme info, including properties, optionally limited to the provided keys.
 pub fn theme<T: Config>(
 	base_id: RmrkBaseId,
 	theme_name: RmrkThemeName,