git.delta.rocks / unique-network / refs/commits / 0e74039449d0

difftreelog

doc(rmrk): adjusted for clarity

Farhad Hakimov2022-07-22parent: #287a010.patch.diff
in: master

4 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # 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 collection76//! 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) and86//! [`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>;166167		/// 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 transaction591		/// 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 the911		/// [`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 metadata1040		/// of a token or a collection, on either one of these.1041		///1042		/// Note that in this proxy implementation many details regarding RMRK are stored1043		/// as scoped properties prefixed with "rmrk:", normally inaccessible1044		/// 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 warrant1177		/// 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 warrant1223		/// 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 warrant1289		/// 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(collection.id, nft_id, RMRK_SCOPE, properties)?;14621463		Ok(nft_id)1464	}14651466	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1467	///1468	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1469	fn destroy_nft(1470		sender: T::CrossAccountId,1471		collection_id: CollectionId,1472		token_id: TokenId,1473		max_burns: u32,1474		error_if_not_owned: Error<T>,1475	) -> DispatchResultWithPostInfo {1476		let collection =1477			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;14781479		let token_data =1480			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;14811482		let from = token_data.owner;14831484		let owner_check_budget = budget::Value::new(NESTING_BUDGET);14851486		ensure!(1487			<PalletStructure<T>>::check_indirectly_owned(1488				sender.clone(),1489				collection_id,1490				token_id,1491				None,1492				&owner_check_budget1493			)?,1494			error_if_not_owned,1495		);14961497		let burns_budget = budget::Value::new(max_burns);1498		let breadth_budget = budget::Value::new(max_burns);14991500		<PalletNft<T>>::burn_recursively(1501			&collection,1502			&from,1503			token_id,1504			&burns_budget,1505			&breadth_budget,1506		)1507	}15081509	/// Add a sent token pending acceptance to the target owning token as a property.1510	fn insert_pending_child(1511		target: (CollectionId, TokenId),1512		child: (RmrkCollectionId, RmrkNftId),1513	) -> DispatchResult {1514		Self::mutate_pending_children(target, |pending_children| {1515			pending_children.insert(child);1516		})1517	}15181519	/// Remove a sent token pending acceptance from the target token's properties.1520	fn remove_pending_child(1521		target: (CollectionId, TokenId),1522		child: (RmrkCollectionId, RmrkNftId),1523	) -> DispatchResult {1524		Self::mutate_pending_children(target, |pending_children| {1525			pending_children.remove(&child);1526		})1527	}15281529	/// Apply a mutation to the property of a token containing sent tokens1530	/// that are currently pending acceptance.1531	fn mutate_pending_children(1532		(target_collection_id, target_nft_id): (CollectionId, TokenId),1533		f: impl FnOnce(&mut PendingChildrenSet),1534	) -> DispatchResult {1535		<PalletNft<T>>::try_mutate_token_aux_property(1536			target_collection_id,1537			target_nft_id,1538			RMRK_SCOPE,1539			Self::get_scoped_property_key(PendingChildren)?,1540			|pending_children| -> DispatchResult {1541				let mut map = match pending_children {1542					Some(map) => Self::decode_property_value(map)?,1543					None => PendingChildrenSet::new(),1544				};15451546				f(&mut map);15471548				*pending_children = Some(Self::encode_property_value(&map)?);15491550				Ok(())1551			},1552		)1553	}15541555	/// Get an iterator from a token's property containing tokens sent to it1556	/// that are currently pending acceptance.1557	fn iterate_pending_children(1558		collection_id: CollectionId,1559		nft_id: TokenId,1560	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1561		let property = <PalletNft<T>>::token_aux_property((1562			collection_id,1563			nft_id,1564			RMRK_SCOPE,1565			Self::get_scoped_property_key(PendingChildren)?,1566		));15671568		let pending_children = match property {1569			Some(map) => Self::decode_property_value(&map)?,1570			None => PendingChildrenSet::new(),1571		};15721573		Ok(pending_children.into_iter())1574	}15751576	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1577	/// Thus, the returned resource ID should be used.1578	fn acquire_next_resource_id(1579		collection_id: CollectionId,1580		nft_id: TokenId,1581	) -> Result<RmrkResourceId, DispatchError> {1582		let resource_id: RmrkResourceId =1583			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;15841585		let next_id = resource_id1586			.checked_add(1)1587			.ok_or(<Error<T>>::NoAvailableResourceId)?;15881589		<PalletNft<T>>::set_scoped_token_property(1590			collection_id,1591			nft_id,1592			RMRK_SCOPE,1593			Self::encode_rmrk_property(NextResourceId, &next_id)?,1594		)?;15951596		Ok(resource_id)1597	}15981599	/// Create and add a resource for a regular NFT, mark it as pending if the sender1600	/// is not the token owner. The sender must be the collection owner.1601	fn resource_add(1602		sender: T::AccountId,1603		collection_id: CollectionId,1604		nft_id: TokenId,1605		resource: RmrkResourceTypes,1606	) -> Result<RmrkResourceId, DispatchError> {1607		let collection =1608			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1609		ensure!(collection.owner == sender, Error::<T>::NoPermission);16101611		let sender = T::CrossAccountId::from_sub(sender);1612		let budget = budget::Value::new(NESTING_BUDGET);16131614		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1615			.map_err(Self::map_unique_err_to_proxy)?;16161617		let pending = sender != nft_owner;16181619		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16201621		let resource_info = RmrkResourceInfo {1622			id,1623			resource,1624			pending,1625			pending_removal: false,1626		};16271628		<PalletNft<T>>::try_mutate_token_aux_property(1629			collection_id,1630			nft_id,1631			RMRK_SCOPE,1632			Self::get_scoped_property_key(ResourceId(id))?,1633			|value| -> DispatchResult {1634				*value = Some(Self::encode_property_value(&resource_info)?);16351636				Ok(())1637			},1638		)?;16391640		Ok(id)1641	}16421643	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1644	/// The sender must be the collection owner.1645	fn resource_remove(1646		sender: T::AccountId,1647		collection_id: CollectionId,1648		nft_id: TokenId,1649		resource_id: RmrkResourceId,1650	) -> DispatchResult {1651		let collection =1652			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1653		ensure!(collection.owner == sender, Error::<T>::NoPermission);16541655		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16561657		let resource = <PalletNft<T>>::token_aux_property((1658			collection_id,1659			nft_id,1660			RMRK_SCOPE,1661			resource_id_key.clone(),1662		))1663		.ok_or(<Error<T>>::ResourceDoesntExist)?;16641665		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16661667		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1668		let topmost_owner =1669			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16701671		let sender = T::CrossAccountId::from_sub(sender);1672		if topmost_owner == sender {1673			<PalletNft<T>>::remove_token_aux_property(1674				collection_id,1675				nft_id,1676				RMRK_SCOPE,1677				Self::get_scoped_property_key(ResourceId(resource_id))?,1678			);16791680			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1681				let base_id = resource.base;16821683				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1684			}1685		} else {1686			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1687				res.pending_removal = true;16881689				Ok(())1690			})?;1691		}16921693		Ok(())1694	}16951696	/// Remove one usage of a base from an NFT's property of associated bases. The base will stay, however,1697	/// if the count of resources using the base is still non-zero.1698	fn remove_associated_base_id(1699		collection_id: CollectionId,1700		nft_id: TokenId,1701		base_id: RmrkBaseId,1702	) -> DispatchResult {1703		<PalletNft<T>>::try_mutate_token_aux_property(1704			collection_id,1705			nft_id,1706			RMRK_SCOPE,1707			Self::get_scoped_property_key(AssociatedBases)?,1708			|value| -> DispatchResult {1709				let mut bases: BasesMap = match value {1710					Some(value) => Self::decode_property_value(value)?,1711					None => BasesMap::new(),1712				};17131714				let remaining = bases.get(&base_id);17151716				if let Some(remaining) = remaining {1717					if let Some(0) | None = remaining.checked_sub(1) {1718						bases.remove(&base_id);1719					}1720				}17211722				*value = Some(Self::encode_property_value(&bases)?);1723				Ok(())1724			},1725		)1726	}17271728	/// Apply a mutation to a resource stored in the token properties of an NFT.1729	fn try_mutate_resource_info(1730		collection_id: CollectionId,1731		nft_id: TokenId,1732		resource_id: RmrkResourceId,1733		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1734	) -> DispatchResult {1735		<PalletNft<T>>::try_mutate_token_aux_property(1736			collection_id,1737			nft_id,1738			RMRK_SCOPE,1739			Self::get_scoped_property_key(ResourceId(resource_id))?,1740			|value| match value {1741				Some(value) => {1742					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17431744					f(&mut resource_info)?;17451746					*value = Self::encode_property_value(&resource_info)?;17471748					Ok(())1749				}1750				None => Err(<Error<T>>::ResourceDoesntExist.into()),1751			},1752		)1753	}17541755	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1756	fn change_collection_owner(1757		collection_id: CollectionId,1758		collection_type: misc::CollectionType,1759		sender: T::AccountId,1760		new_owner: T::AccountId,1761	) -> DispatchResult {1762		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1763		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17641765		let mut collection = collection.into_inner();17661767		collection.owner = new_owner;1768		collection.save()1769	}17701771	/// Ensure that an account is the collection owner/issuer, return an error if not.1772	pub fn check_collection_owner(1773		collection: &NonfungibleHandle<T>,1774		account: &T::CrossAccountId,1775	) -> DispatchResult {1776		collection1777			.check_is_owner(account)1778			.map_err(Self::map_unique_err_to_proxy)1779	}17801781	/// Get the latest yet-unused RMRK collection index from the storage.1782	pub fn last_collection_idx() -> RmrkCollectionId {1783		<CollectionIndex<T>>::get()1784	}17851786	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1787	pub fn unique_collection_id(1788		rmrk_collection_id: RmrkCollectionId,1789	) -> Result<CollectionId, DispatchError> {1790		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1791			.map_err(|_| <Error<T>>::CollectionUnknown.into())1792	}17931794	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1795	pub fn rmrk_collection_id(1796		unique_collection_id: CollectionId,1797	) -> Result<RmrkCollectionId, DispatchError> {1798		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1799	}18001801	/// Fetch a Unique NFT collection.1802	pub fn get_nft_collection(1803		collection_id: CollectionId,1804	) -> Result<NonfungibleHandle<T>, DispatchError> {1805		let collection = <CollectionHandle<T>>::try_get(collection_id)1806			.map_err(|_| <Error<T>>::CollectionUnknown)?;18071808		match collection.mode {1809			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1810			_ => Err(<Error<T>>::CollectionUnknown.into()),1811		}1812	}18131814	/// Check if an NFT collection with such an ID exists.1815	pub fn collection_exists(collection_id: CollectionId) -> bool {1816		<CollectionHandle<T>>::try_get(collection_id).is_ok()1817	}18181819	/// Fetch and decode a RMRK-scoped collection property value in bytes.1820	pub fn get_collection_property(1821		collection_id: CollectionId,1822		key: RmrkProperty,1823	) -> Result<PropertyValue, DispatchError> {1824		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1825			.get(&Self::get_scoped_property_key(key)?)1826			.ok_or(<Error<T>>::CollectionUnknown)?1827			.clone();18281829		Ok(collection_property)1830	}18311832	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1833	pub fn get_collection_property_decoded<V: Decode>(1834		collection_id: CollectionId,1835		key: RmrkProperty,1836	) -> Result<V, DispatchError> {1837		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1838	}18391840	/// Get the type of a collection stored in it as a scoped property.1841	///1842	/// RMRK Core proxy differentiates between regular collections as well as RMRK bases as collections.1843	pub fn get_collection_type(1844		collection_id: CollectionId,1845	) -> Result<misc::CollectionType, DispatchError> {1846		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1847			if err != <Error<T>>::CollectionUnknown.into() {1848				<Error<T>>::CorruptedCollectionType.into()1849			} else {1850				err1851			}1852		})1853	}18541855	/// Ensure that the type of the collection equals the provided type,1856	/// otherwise return an error.1857	pub fn ensure_collection_type(1858		collection_id: CollectionId,1859		collection_type: misc::CollectionType,1860	) -> DispatchResult {1861		let actual_type = Self::get_collection_type(collection_id)?;1862		ensure!(1863			actual_type == collection_type,1864			<CommonError<T>>::NoPermission1865		);18661867		Ok(())1868	}18691870	/// Fetch an NFT collection, but make sure it has the appropriate type.1871	pub fn get_typed_nft_collection(1872		collection_id: CollectionId,1873		collection_type: misc::CollectionType,1874	) -> Result<NonfungibleHandle<T>, DispatchError> {1875		Self::ensure_collection_type(collection_id, collection_type)?;18761877		Self::get_nft_collection(collection_id)1878	}18791880	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1881	/// but also return the Unique collection ID.1882	pub fn get_typed_nft_collection_mapped(1883		rmrk_collection_id: RmrkCollectionId,1884		collection_type: misc::CollectionType,1885	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1886		let unique_collection_id = match collection_type {1887			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1888			_ => rmrk_collection_id.into(),1889		};18901891		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;18921893		Ok((collection, unique_collection_id))1894	}18951896	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1897	pub fn get_nft_property(1898		collection_id: CollectionId,1899		nft_id: TokenId,1900		key: RmrkProperty,1901	) -> Result<PropertyValue, DispatchError> {1902		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1903			.get(&Self::get_scoped_property_key(key)?)1904			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1905			.clone();19061907		Ok(nft_property)1908	}19091910	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1911	pub fn get_nft_property_decoded<V: Decode>(1912		collection_id: CollectionId,1913		nft_id: TokenId,1914		key: RmrkProperty,1915	) -> Result<V, DispatchError> {1916		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1917	}19181919	/// Check that an NFT exists.1920	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1921		<TokenData<T>>::contains_key((collection_id, nft_id))1922	}19231924	/// Get the type of an NFT stored in it as a scoped property.1925	///1926	/// RMRK Core proxy differentiates between regular NFTs, and RMRK parts and themes.1927	pub fn get_nft_type(1928		collection_id: CollectionId,1929		token_id: TokenId,1930	) -> Result<NftType, DispatchError> {1931		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1932			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1933	}19341935	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1936	pub fn ensure_nft_type(1937		collection_id: CollectionId,1938		token_id: TokenId,1939		nft_type: NftType,1940	) -> DispatchResult {1941		let actual_type = Self::get_nft_type(collection_id, token_id)?;1942		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19431944		Ok(())1945	}19461947	/// Ensure that an account is the owner of the token, either directly1948	/// or at the top of the nesting hierarchy; return an error if it is not.1949	pub fn ensure_nft_owner(1950		collection_id: CollectionId,1951		token_id: TokenId,1952		possible_owner: &T::CrossAccountId,1953		nesting_budget: &dyn budget::Budget,1954	) -> DispatchResult {1955		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1956			possible_owner.clone(),1957			collection_id,1958			token_id,1959			None,1960			nesting_budget,1961		)1962		.map_err(Self::map_unique_err_to_proxy)?;19631964		ensure!(is_owned, <Error<T>>::NoPermission);19651966		Ok(())1967	}19681969	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,1970	/// or, if None are provided, return all non-scoped properties.1971	pub fn filter_user_properties<Key, Value, R, Mapper>(1972		collection_id: CollectionId,1973		token_id: Option<TokenId>,1974		filter_keys: Option<Vec<RmrkPropertyKey>>,1975		mapper: Mapper,1976	) -> Result<Vec<R>, DispatchError>1977	where1978		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1979		Value: Decode + Default,1980		Mapper: Fn(Key, Value) -> R,1981	{1982		filter_keys1983			.map(|keys| {1984				let properties = keys1985					.into_iter()1986					.filter_map(|key| {1987						let key: Key = key.try_into().ok()?;19881989						let value = match token_id {1990							Some(token_id) => Self::get_nft_property_decoded(1991								collection_id,1992								token_id,1993								UserProperty(key.as_ref()),1994							),1995							None => Self::get_collection_property_decoded(1996								collection_id,1997								UserProperty(key.as_ref()),1998							),1999						}2000						.ok()?;20012002						Some(mapper(key, value))2003					})2004					.collect();20052006				Ok(properties)2007			})2008			.unwrap_or_else(|| {2009				let properties =2010					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20112012				Ok(properties)2013			})2014	}20152016	/// Get all non-scoped properties from a collection or a token, and apply some transformation2017	/// to each key-value pair.2018	pub fn iterate_user_properties<Key, Value, R, Mapper>(2019		collection_id: CollectionId,2020		token_id: Option<TokenId>,2021		mapper: Mapper,2022	) -> Result<impl Iterator<Item = R>, DispatchError>2023	where2024		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2025		Value: Decode + Default,2026		Mapper: Fn(Key, Value) -> R,2027	{2028		let properties = match token_id {2029			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2030			None => <PalletCommon<T>>::collection_properties(collection_id),2031		};20322033		let properties = properties.into_iter().filter_map(move |(key, value)| {2034			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20352036			let key: Key = key.to_vec().try_into().ok()?;2037			let value: Value = value.decode().ok()?;20382039			Some(mapper(key, value))2040		});20412042		Ok(properties)2043	}20442045	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2046	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2047		map_unique_err_to_proxy! {2048			match err {2049				CommonError::NoPermission => NoPermission,2050				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2051				CommonError::PublicMintingNotAllowed => NoPermission,2052				CommonError::TokenNotFound => NoAvailableNftId,2053				CommonError::ApprovedValueTooLow => NoPermission,2054				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2055				StructureError::TokenNotFound => NoAvailableNftId,2056				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2057			}2058		}2059	}2060}
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 specification repository: <https://github.com/rmrk-team/rmrk-spec>52//! 53//! ## Terminology54//! 55//! For more information on RMRK, see RMRK's own documentation.56//! 57//! ### Intro to RMRK58//! 59//! - **Resource:** Additional piece of metadata of an NFT usually serving to add 60//! a piece of media on top of the root metadata (NFT's own), be it a different wing 61//! on the root template bird or something entirely unrelated.62//! 63//! - **Base:** A list of possible "components" - Parts, a combination of which can 64//! be appended/equipped to/on an NFT.65//! 66//! - **Part:** Something that, together with other Parts, can constitute an NFT. 67//! Parts are defined in the Base to which they belong. Parts can be either 68//! of the `slot` type or `fixed` type. Slots are intended for equippables.69//! Note that "part of something" and "Part of a Base" can be easily confused, 70//! and in this documentation these words are distinguished by the capital letter.71//! 72//! - **Theme:** Named objects of variable => value pairs which get interpolated into 73//! the Base's `themable` Parts. Themes can hold any value, but are often represented 74//! in RMRK's examples as colors applied to visible Parts.75//! 76//! ### Peculiarities in Unique77//! 78//! - **Scoped properties:** Properties that are normally obscured from users. 79//! Their purpose is to contain structured metadata that was not included in the Unique standard 80//! for collections and tokens, meant to be operated on by proxies and other outliers. 81//! Scoped properties are prefixed with `some-scope:`, where `some-scope` is 82//! an arbitrary keyword, like "rmrk", and `:` is an unacceptable symbol in user-defined 83//! properties, which, along with other safeguards, makes them impossible to tamper with.84//! 85//! - **Auxiliary properties:** A slightly different structure of properties, 86//! trading universality of use for more convenient storage, writes and access. 87//! Meant to be inaccessible to end users.88//!89//! ## Proxy Implementation90//!91//! An external user is supposed to be able to utilize this proxy as they would92//! utilize RMRK, and get exactly the same results. Normally, Unique transactions93//! are off-limits to RMRK collections and tokens, and vice versa. However,94//! the information stored on chain can be freely interpreted by storage reads and RPCs.95//!96//! ### ID Mapping97//!98//! RMRK's collections' IDs are counted independently of Unique's and start at 0.99//! Note that tokens' IDs still start at 1.100//! The collections themselves, as well as tokens, are stored as Unique collections,101//! and thus RMRK IDs are mapped to Unique IDs (but not vice versa).102//!103//! ### External/Internal Collection Insulation104//!105//! A Unique transaction cannot target collections purposed for RMRK,106//! and they are flagged as `external` to specify that. On the other hand,107//! due to the mapping, RMRK transactions and RPCs simply cannot reach Unique collections.108//!109//! ### Native Properties110//!111//! Many of RMRK's native parameters are stored as scoped properties of a collection112//! or an NFT on the chain. Scoped properties are prefixed with `rmrk:`, where `:`113//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,114//! makes them impossible to tamper with.115//!116//! ### Collection and NFT Types, and Base, Parts and Themes Handling117//!118//! RMRK introduces the concept of a Base, which is a catalgoue of Parts,119//! possible components of an NFT. Due to its similarity with the functionality120//! of a token collection, a Base is stored and handled as one, and the Base's Parts and Themes121//! are the collection's NFTs. See [`CollectionType`](pallet_rmrk_core::misc::CollectionType) and122//! [`NftType`](pallet_rmrk_core::misc::NftType).123//! 124//! ## Interface125//!126//! ### Dispatchables127//!128//! - `create_collection` - Create a new collection of NFTs.129//! - `destroy_collection` - Destroy a collection.130//! - `change_collection_issuer` - Change the issuer of a collection.131//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).132//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**133//! - `mint_nft` - Mint an NFT in a specified collection.134//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.135//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.136//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.137//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.138//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.139//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.140//! - `set_property` - Add or edit a custom user property of a token or a collection.141//! - `set_priority` - Set a different order of resource priorities for an NFT.142//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.143//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.144//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.145//! - `remove_resource` - Remove and erase a resource from an NFT.146147#![cfg_attr(not(feature = "std"), no_std)]148149use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};150use frame_system::{pallet_prelude::*, ensure_signed};151use sp_runtime::{DispatchError, Permill, traits::StaticLookup};152use sp_std::{153	vec::Vec,154	collections::{btree_set::BTreeSet, btree_map::BTreeMap},155};156use up_data_structs::{*, mapping::TokenAddressMapping};157use pallet_common::{158	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,159};160use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};161use pallet_structure::{Pallet as PalletStructure, Error as StructureError};162use pallet_evm::account::CrossAccountId;163use core::convert::AsRef;164165pub use pallet::*;166167#[cfg(feature = "runtime-benchmarks")]168pub mod benchmarking;169pub mod misc;170pub mod property;171pub mod rpc;172pub mod weights;173174pub type SelfWeightOf<T> = <T as Config>::WeightInfo;175176use weights::WeightInfo;177use misc::*;178pub use property::*;179180use RmrkProperty::*;181182/// Maximum number of levels of depth in the token nesting tree.183pub const NESTING_BUDGET: u32 = 5;184185type PendingTarget = (CollectionId, TokenId);186type PendingChild = (RmrkCollectionId, RmrkNftId);187type PendingChildrenSet = BTreeSet<PendingChild>;188189type BasesMap = BTreeMap<RmrkBaseId, u32>;190191#[frame_support::pallet]192pub mod pallet {193	use super::*;194	use pallet_evm::account;195196	#[pallet::config]197	pub trait Config:198		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config199	{200		/// Overarching event type.201		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;202203		/// The weight information of this pallet.204		type WeightInfo: WeightInfo;205	}206207	/// Latest yet-unused collection ID.208	#[pallet::storage]209	#[pallet::getter(fn collection_index)]210	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;211212	/// Mapping from RMRK collection ID to Unique's.213	#[pallet::storage]214	pub type UniqueCollectionId<T: Config> =215		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;216217	#[pallet::pallet]218	#[pallet::generate_store(pub(super) trait Store)]219	pub struct Pallet<T>(_);220221	#[pallet::event]222	#[pallet::generate_deposit(pub(super) fn deposit_event)]223	pub enum Event<T: Config> {224		CollectionCreated {225			issuer: T::AccountId,226			collection_id: RmrkCollectionId,227		},228		CollectionDestroyed {229			issuer: T::AccountId,230			collection_id: RmrkCollectionId,231		},232		IssuerChanged {233			old_issuer: T::AccountId,234			new_issuer: T::AccountId,235			collection_id: RmrkCollectionId,236		},237		CollectionLocked {238			issuer: T::AccountId,239			collection_id: RmrkCollectionId,240		},241		NftMinted {242			owner: T::AccountId,243			collection_id: RmrkCollectionId,244			nft_id: RmrkNftId,245		},246		NFTBurned {247			owner: T::AccountId,248			nft_id: RmrkNftId,249		},250		NFTSent {251			sender: T::AccountId,252			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,253			collection_id: RmrkCollectionId,254			nft_id: RmrkNftId,255			approval_required: bool,256		},257		NFTAccepted {258			sender: T::AccountId,259			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,260			collection_id: RmrkCollectionId,261			nft_id: RmrkNftId,262		},263		NFTRejected {264			sender: T::AccountId,265			collection_id: RmrkCollectionId,266			nft_id: RmrkNftId,267		},268		PropertySet {269			collection_id: RmrkCollectionId,270			maybe_nft_id: Option<RmrkNftId>,271			key: RmrkKeyString,272			value: RmrkValueString,273		},274		ResourceAdded {275			nft_id: RmrkNftId,276			resource_id: RmrkResourceId,277		},278		ResourceRemoval {279			nft_id: RmrkNftId,280			resource_id: RmrkResourceId,281		},282		ResourceAccepted {283			nft_id: RmrkNftId,284			resource_id: RmrkResourceId,285		},286		ResourceRemovalAccepted {287			nft_id: RmrkNftId,288			resource_id: RmrkResourceId,289		},290		PrioritySet {291			collection_id: RmrkCollectionId,292			nft_id: RmrkNftId,293		},294	}295296	#[pallet::error]297	pub enum Error<T> {298		/* Unique proxy-specific events */299		/// Property of the type of RMRK collection could not be read successfully.300		CorruptedCollectionType,301		// NftTypeEncodeError,302		/// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).303		RmrkPropertyKeyIsTooLong,304		/// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).305		RmrkPropertyValueIsTooLong,306		/// Could not find a property by the supplied key.307		RmrkPropertyIsNotFound,308		/// Something went wrong when decoding encoded data from the storage.309		/// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.310		UnableToDecodeRmrkData,311312		/* RMRK compatible events */313		/// Only destroying collections without tokens is allowed.314		CollectionNotEmpty,315		/// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.316		NoAvailableCollectionId,317		/// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.318		NoAvailableNftId,319		/// Collection does not exist, has a wrong type, or does not map to a Unique ID.320		CollectionUnknown,321		/// No permission to perform action.322		NoPermission,323		/// Token is marked as non-transferable, and thus cannot be transferred.324		NonTransferable,325		/// Too many tokens created in the collection, no new ones are allowed.326		CollectionFullOrLocked,327		/// No such resource found.328		ResourceDoesntExist,329		/// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.330		/// Sending to self is redundant.331		CannotSendToDescendentOrSelf,332		/// Not the target owner of the sent NFT.333		CannotAcceptNonOwnedNft,334		/// Not the target owner of the sent NFT.335		CannotRejectNonOwnedNft,336		/// NFT was not sent and is not pending.337		CannotRejectNonPendingNft,338		/// Resource is not pending for the operation.339		ResourceNotPending,340		/// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.341		NoAvailableResourceId,342	}343344	#[pallet::call]345	impl<T: Config> Pallet<T> {346		// todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?347348		/// Create a new collection of NFTs.349		///350		/// # Permissions:351		/// * Anyone - will be assigned as the issuer of the collection.352		///353		/// # Arguments:354		/// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.355		/// - `max`: Optional maximum number of tokens.356		/// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.357		/// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.358		#[transactional]359		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]360		pub fn create_collection(361			origin: OriginFor<T>,362			metadata: RmrkString,363			max: Option<u32>,364			symbol: RmrkCollectionSymbol,365		) -> DispatchResult {366			let sender = ensure_signed(origin)?;367368			let limits = CollectionLimits {369				owner_can_transfer: Some(false),370				token_limit: max,371				..Default::default()372			};373374			let data = CreateCollectionData {375				limits: Some(limits),376				token_prefix: symbol377					.into_inner()378					.try_into()379					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,380				permissions: Some(CollectionPermissions {381					nesting: Some(NestingPermissions {382						token_owner: true,383						collection_admin: false,384						restricted: None,385						#[cfg(feature = "runtime-benchmarks")]386						permissive: false,387					}),388					..Default::default()389				}),390				..Default::default()391			};392393			let unique_collection_id = Self::init_collection(394				T::CrossAccountId::from_sub(sender.clone()),395				data,396				[397					Self::encode_rmrk_property(Metadata, &metadata)?,398					Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,399				]400				.into_iter(),401			)?;402			let rmrk_collection_id = <CollectionIndex<T>>::get();403404			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);405406			<PalletCommon<T>>::set_scoped_collection_property(407				unique_collection_id,408				RMRK_SCOPE,409				Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,410			)?;411412			<CollectionIndex<T>>::mutate(|n| *n += 1);413414			Self::deposit_event(Event::CollectionCreated {415				issuer: sender,416				collection_id: rmrk_collection_id,417			});418419			Ok(())420		}421422		/// Destroy a collection.423		///424		/// Only empty collections can be destroyed. If it has any tokens, they must be burned first.425		///426		/// # Permissions:427		/// * Collection issuer428		///429		/// # Arguments:430		/// - `collection_id`: RMRK ID of the collection to destroy.431		#[transactional]432		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]433		pub fn destroy_collection(434			origin: OriginFor<T>,435			collection_id: RmrkCollectionId,436		) -> DispatchResult {437			let sender = ensure_signed(origin)?;438			let cross_sender = T::CrossAccountId::from_sub(sender.clone());439440			let collection = Self::get_typed_nft_collection(441				Self::unique_collection_id(collection_id)?,442				misc::CollectionType::Regular,443			)?;444			collection.check_is_external()?;445446			<PalletNft<T>>::destroy_collection(collection, &cross_sender)447				.map_err(Self::map_unique_err_to_proxy)?;448449			Self::deposit_event(Event::CollectionDestroyed {450				issuer: sender,451				collection_id,452			});453454			Ok(())455		}456457		/// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).458		///459		/// # Permissions:460		/// * Collection issuer461		///462		/// # Arguments:463		/// - `collection_id`: RMRK collection ID to change the issuer of.464		/// - `new_issuer`: Collection's new issuer.465		#[transactional]466		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]467		pub fn change_collection_issuer(468			origin: OriginFor<T>,469			collection_id: RmrkCollectionId,470			new_issuer: <T::Lookup as StaticLookup>::Source,471		) -> DispatchResult {472			let sender = ensure_signed(origin)?;473474			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;475			collection.check_is_external()?;476477			let new_issuer = T::Lookup::lookup(new_issuer)?;478479			Self::change_collection_owner(480				Self::unique_collection_id(collection_id)?,481				misc::CollectionType::Regular,482				sender.clone(),483				new_issuer.clone(),484			)?;485486			Self::deposit_event(Event::IssuerChanged {487				old_issuer: sender,488				new_issuer,489				collection_id,490			});491492			Ok(())493		}494495		/// "Lock" the collection and prevent new token creation. Cannot be undone.496		///497		/// # Permissions:498		/// * Collection issuer499		///500		/// # Arguments:501		/// - `collection_id`: RMRK ID of the collection to lock.502		#[transactional]503		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]504		pub fn lock_collection(505			origin: OriginFor<T>,506			collection_id: RmrkCollectionId,507		) -> DispatchResult {508			let sender = ensure_signed(origin)?;509			let cross_sender = T::CrossAccountId::from_sub(sender.clone());510511			let collection = Self::get_typed_nft_collection(512				Self::unique_collection_id(collection_id)?,513				misc::CollectionType::Regular,514			)?;515			collection.check_is_external()?;516517			Self::check_collection_owner(&collection, &cross_sender)?;518519			let token_count = collection.total_supply();520521			let mut collection = collection.into_inner();522			collection.limits.token_limit = Some(token_count);523			collection.save()?;524525			Self::deposit_event(Event::CollectionLocked {526				issuer: sender,527				collection_id,528			});529530			Ok(())531		}532533		/// Mint an NFT in a specified collection.534		///535		/// # Permissions:536		/// * Collection issuer537		///538		/// # Arguments:539		/// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).540		/// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.541		/// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.542		/// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.543		/// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.544		/// - `transferable`: Can this NFT be transferred? Cannot be changed.545		/// - `resources`: Resource data to be added to the NFT immediately after minting.546		#[transactional]547		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]548		pub fn mint_nft(549			origin: OriginFor<T>,550			owner: Option<T::AccountId>,551			collection_id: RmrkCollectionId,552			recipient: Option<T::AccountId>,553			royalty_amount: Option<Permill>,554			metadata: RmrkString,555			transferable: bool,556			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,557		) -> DispatchResult {558			let sender = ensure_signed(origin)?;559			let cross_sender = T::CrossAccountId::from_sub(sender.clone());560561			let owner = owner.unwrap_or(sender.clone());562			let cross_owner = T::CrossAccountId::from_sub(owner.clone());563564			let collection = Self::get_typed_nft_collection(565				Self::unique_collection_id(collection_id)?,566				misc::CollectionType::Regular,567			)?;568			collection.check_is_external()?;569570			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {571				recipient: recipient.unwrap_or_else(|| owner.clone()),572				amount,573			});574575			let nft_id = Self::create_nft(576				&cross_sender,577				&cross_owner,578				&collection,579				[580					Self::encode_rmrk_property(TokenType, &NftType::Regular)?,581					Self::encode_rmrk_property(Transferable, &transferable)?,582					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,583					Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,584					Self::encode_rmrk_property(Metadata, &metadata)?,585					Self::encode_rmrk_property(Equipped, &false)?,586					Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,587					Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,588					Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,589					Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,590				]591				.into_iter(),592			)593			.map_err(|err| match err {594				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),595				err => Self::map_unique_err_to_proxy(err),596			})?;597598			if let Some(resources) = resources {599				for resource in resources {600					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;601				}602			}603604			Self::deposit_event(Event::NftMinted {605				owner,606				collection_id,607				nft_id: nft_id.0,608			});609610			Ok(())611		}612613		/// Burn an NFT, destroying it and its nested tokens up to the specified limit.614		/// If the burning budget is exceeded, the transaction is reverted.615		///616		/// This is the way to burn a nested token as well.617		///618		/// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).619		///620		/// # Permissions:621		/// * Token owner622		///623		/// # Arguments:624		/// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.625		/// - `nft_id`: ID of the NFT to be destroyed.626		/// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction627		/// is reverted if there are more tokens to burn in the nesting tree than this number.628		/// This is primarily a mechanism of transaction weight control.629		#[transactional]630		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]631		pub fn burn_nft(632			origin: OriginFor<T>,633			collection_id: RmrkCollectionId,634			nft_id: RmrkNftId,635			max_burns: u32,636		) -> DispatchResult {637			let sender = ensure_signed(origin)?;638			let cross_sender = T::CrossAccountId::from_sub(sender.clone());639640			let collection = Self::get_typed_nft_collection(641				Self::unique_collection_id(collection_id)?,642				misc::CollectionType::Regular,643			)?;644			collection.check_is_external()?;645646			Self::destroy_nft(647				cross_sender,648				Self::unique_collection_id(collection_id)?,649				nft_id.into(),650				max_burns,651				<Error<T>>::NoPermission,652			)653			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;654655			Self::deposit_event(Event::NFTBurned {656				owner: sender,657				nft_id,658			});659660			Ok(())661		}662663		/// Transfer an NFT from an account/NFT A to another account/NFT B.664		/// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].665		///666		/// If the target owner is an NFT owned by another account, then the NFT will enter667		/// the pending state and will have to be accepted by the other account.668		///669		/// # Permissions:670		/// - Token owner671		///672		/// # Arguments:673		/// - `collection_id`: RMRK ID of the collection of the NFT to be transferred.674		/// - `nft_id`: ID of the NFT to be transferred.675		/// - `new_owner`: New owner of the nft which can be either an account or a NFT.676		#[transactional]677		#[pallet::weight(<SelfWeightOf<T>>::send())]678		pub fn send(679			origin: OriginFor<T>,680			rmrk_collection_id: RmrkCollectionId,681			rmrk_nft_id: RmrkNftId,682			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,683		) -> DispatchResult {684			let sender = ensure_signed(origin.clone())?;685			let cross_sender = T::CrossAccountId::from_sub(sender.clone());686687			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;688			let nft_id = rmrk_nft_id.into();689690			let collection =691				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;692			collection.check_is_external()?;693694			let token_data =695				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;696697			let from = token_data.owner;698699			ensure!(700				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,701				<Error<T>>::NonTransferable702			);703704			ensure!(705				Self::get_nft_property_decoded::<Option<PendingTarget>>(706					collection_id,707					nft_id,708					RmrkProperty::PendingNftAccept709				)?710				.is_none(),711				<Error<T>>::NoPermission712			);713714			let target_owner;715			let approval_required;716717			match new_owner {718				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {719					target_owner = T::CrossAccountId::from_sub(account_id.clone());720					approval_required = false;721				}722				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(723					target_collection_id,724					target_nft_id,725				) => {726					let target_collection_id = Self::unique_collection_id(target_collection_id)?;727728					let target_nft_budget = budget::Value::new(NESTING_BUDGET);729730					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(731						target_collection_id,732						target_nft_id.into(),733						Some((collection_id, nft_id)),734						&target_nft_budget,735					)736					.map_err(Self::map_unique_err_to_proxy)?;737738					approval_required = cross_sender != target_nft_owner;739740					if approval_required {741						target_owner = target_nft_owner;742743						<PalletNft<T>>::set_scoped_token_property(744							collection.id,745							nft_id,746							RMRK_SCOPE,747							Self::encode_rmrk_property::<Option<PendingTarget>>(748								PendingNftAccept,749								&Some((target_collection_id, target_nft_id.into())),750							)?,751						)?;752753						Self::insert_pending_child(754							(target_collection_id, target_nft_id.into()),755							(rmrk_collection_id, rmrk_nft_id),756						)?;757					} else {758						target_owner = T::CrossTokenAddressMapping::token_to_address(759							target_collection_id,760							target_nft_id.into(),761						);762					}763				}764			}765766			let src_nft_budget = budget::Value::new(NESTING_BUDGET);767768			<PalletNft<T>>::transfer_from(769				&collection,770				&cross_sender,771				&from,772				&target_owner,773				nft_id,774				&src_nft_budget,775			)776			.map_err(Self::map_unique_err_to_proxy)?;777778			Self::deposit_event(Event::NFTSent {779				sender,780				recipient: new_owner,781				collection_id: rmrk_collection_id,782				nft_id: rmrk_nft_id,783				approval_required,784			});785786			Ok(())787		}788789		/// Accept an NFT sent from another account to self or an owned NFT.790		///791		/// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.792		///793		/// # Permissions:794		/// - Token-owner-to-be795		///796		/// # Arguments:797		/// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.798		/// - `rmrk_nft_id`: ID of the NFT to be accepted.799		/// - `new_owner`: Either the sender's account ID or a sender-owned NFT,800		/// whichever the accepted NFT was sent to.801		#[transactional]802		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]803		pub fn accept_nft(804			origin: OriginFor<T>,805			rmrk_collection_id: RmrkCollectionId,806			rmrk_nft_id: RmrkNftId,807			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,808		) -> DispatchResult {809			let sender = ensure_signed(origin.clone())?;810			let cross_sender = T::CrossAccountId::from_sub(sender.clone());811812			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;813			let nft_id = rmrk_nft_id.into();814815			let collection =816				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;817			collection.check_is_external()?;818819			let new_cross_owner = match new_owner {820				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {821					T::CrossAccountId::from_sub(account_id.clone())822				}823				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(824					target_collection_id,825					target_nft_id,826				) => {827					let target_collection_id = Self::unique_collection_id(target_collection_id)?;828829					T::CrossTokenAddressMapping::token_to_address(830						target_collection_id,831						TokenId(target_nft_id),832					)833				}834			};835836			let budget = budget::Value::new(NESTING_BUDGET);837838			<PalletNft<T>>::transfer(839				&collection,840				&cross_sender,841				&new_cross_owner,842				nft_id,843				&budget,844			)845			.map_err(|err| {846				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {847					<Error<T>>::CannotAcceptNonOwnedNft.into()848				} else {849					Self::map_unique_err_to_proxy(err)850				}851			})?;852853			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(854				collection_id,855				nft_id,856				RmrkProperty::PendingNftAccept,857			)?;858859			if let Some(pending_target) = pending_target {860				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;861862				<PalletNft<T>>::set_scoped_token_property(863					collection.id,864					nft_id,865					RMRK_SCOPE,866					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,867				)?;868			}869870			Self::deposit_event(Event::NFTAccepted {871				sender,872				recipient: new_owner,873				collection_id: rmrk_collection_id,874				nft_id: rmrk_nft_id,875			});876877			Ok(())878		}879880		/// Reject an NFT sent from another account to self or owned NFT.881		/// The NFT in question will not be sent back and burnt instead.882		///883		/// The NFT in question must be pending, and, thus, be [sent](`crate::pallet::Call::send`) first.884		///885		/// # Permissions:886		/// - Token-owner-to-be-not887		///888		/// # Arguments:889		/// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.890		/// - `rmrk_nft_id`: ID of the NFT to be rejected.891		#[transactional]892		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]893		pub fn reject_nft(894			origin: OriginFor<T>,895			rmrk_collection_id: RmrkCollectionId,896			rmrk_nft_id: RmrkNftId,897		) -> DispatchResult {898			let sender = ensure_signed(origin)?;899			let cross_sender = T::CrossAccountId::from_sub(sender.clone());900901			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;902			let nft_id = rmrk_nft_id.into();903904			let collection =905				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906			collection.check_is_external()?;907908			ensure!(909				<TokenData<T>>::get((collection_id, nft_id)).is_some(),910				<Error<T>>::NoAvailableNftId911			);912913			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(914				collection_id,915				nft_id,916				RmrkProperty::PendingNftAccept,917			)?;918919			match pending_target {920				Some(pending_target) => {921					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?922				}923				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),924			}925926			Self::destroy_nft(927				cross_sender,928				collection_id,929				nft_id,930				NESTING_BUDGET,931				<Error<T>>::CannotRejectNonOwnedNft,932			)933			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;934935			Self::deposit_event(Event::NFTRejected {936				sender,937				collection_id: rmrk_collection_id,938				nft_id: rmrk_nft_id,939			});940941			Ok(())942		}943944		/// Accept the addition of a newly created pending resource to an existing NFT.945		///946		/// This transaction is needed when a resource is created and assigned to an NFT947		/// by a non-owner, i.e. the collection issuer, with one of the948		/// [`add_...` transactions](crate::pallet::Call::add_basic_resource).949		///950		/// # Permissions:951		/// - Token owner952		///953		/// # Arguments:954		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.955		/// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.956		/// - `resource_id`: ID of the newly created pending resource.957		#[transactional]958		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]959		pub fn accept_resource(960			origin: OriginFor<T>,961			rmrk_collection_id: RmrkCollectionId,962			rmrk_nft_id: RmrkNftId,963			resource_id: RmrkResourceId,964		) -> DispatchResult {965			let sender = ensure_signed(origin)?;966			let cross_sender = T::CrossAccountId::from_sub(sender);967968			let collection_id = Self::unique_collection_id(rmrk_collection_id)969				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;970			let collection =971				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;972			collection.check_is_external()?;973974			let nft_id = rmrk_nft_id.into();975976			let budget = budget::Value::new(NESTING_BUDGET);977978			let nft_owner =979				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)980					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;981982			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {983				ensure!(res.pending, <Error<T>>::ResourceNotPending);984				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);985986				res.pending = false;987988				Ok(())989			})?;990991			Self::deposit_event(Event::<T>::ResourceAccepted {992				nft_id: rmrk_nft_id,993				resource_id,994			});995996			Ok(())997		}998999		/// Accept the removal of a removal-pending resource from an NFT.1000		///1001		/// This transaction is needed when a non-owner, i.e. the collection issuer,1002		/// requests a [removal](`crate::pallet::Call::remove_resource`) of a resource from an NFT.1003		///1004		/// # Permissions:1005		/// - Token owner1006		///1007		/// # Arguments:1008		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1009		/// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1010		/// - `resource_id`: ID of the removal-pending resource.1011		#[transactional]1012		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1013		pub fn accept_resource_removal(1014			origin: OriginFor<T>,1015			rmrk_collection_id: RmrkCollectionId,1016			rmrk_nft_id: RmrkNftId,1017			resource_id: RmrkResourceId,1018		) -> DispatchResult {1019			let sender = ensure_signed(origin)?;1020			let cross_sender = T::CrossAccountId::from_sub(sender);10211022			let collection_id = Self::unique_collection_id(rmrk_collection_id)1023				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;1024			let collection =1025				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1026			collection.check_is_external()?;10271028			let nft_id = rmrk_nft_id.into();10291030			let budget = budget::Value::new(NESTING_BUDGET);10311032			let nft_owner =1033				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1034					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;10351036			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10371038			let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10391040			let resource_info = <PalletNft<T>>::token_aux_property((1041				collection_id,1042				nft_id,1043				RMRK_SCOPE,1044				resource_id_key.clone(),1045			))1046			.ok_or(<Error<T>>::ResourceDoesntExist)?;10471048			let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10491050			ensure!(1051				resource_info.pending_removal,1052				<Error<T>>::ResourceNotPending1053			);10541055			<PalletNft<T>>::remove_token_aux_property(1056				collection_id,1057				nft_id,1058				RMRK_SCOPE,1059				resource_id_key,1060			);10611062			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1063				let base_id = resource.base;10641065				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1066			}10671068			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1069				nft_id: rmrk_nft_id,1070				resource_id,1071			});10721073			Ok(())1074		}10751076		/// Add or edit a custom user property, a key-value pair, describing the metadata1077		/// of a token or a collection, on either one of these.1078		///1079		/// Note that in this proxy implementation many details regarding RMRK are stored1080		/// as scoped properties prefixed with "rmrk:", normally inaccessible1081		/// to external transactions and RPCs.1082		///1083		/// # Permissions:1084		/// - Collection issuer - in case of collection property1085		/// - Token owner - in case of NFT property1086		///1087		/// # Arguments:1088		/// - `rmrk_collection_id`: RMRK collection ID.1089		/// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1090		/// - `key`: Key of the custom property to be referenced by.1091		/// - `value`: Value of the custom property to be stored.1092		#[transactional]1093		#[pallet::weight(<SelfWeightOf<T>>::set_property())]1094		pub fn set_property(1095			origin: OriginFor<T>,1096			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,1097			maybe_nft_id: Option<RmrkNftId>,1098			key: RmrkKeyString,1099			value: RmrkValueString,1100		) -> DispatchResult {1101			let sender = ensure_signed(origin)?;1102			let sender = T::CrossAccountId::from_sub(sender);11031104			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1105			let collection =1106				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1107			collection.check_is_external()?;11081109			let budget = budget::Value::new(NESTING_BUDGET);11101111			match maybe_nft_id {1112				Some(nft_id) => {1113					let token_id: TokenId = nft_id.into();11141115					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1116					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11171118					<PalletNft<T>>::set_scoped_token_property(1119						collection_id,1120						token_id,1121						RMRK_SCOPE,1122						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1123					)?;1124				}1125				None => {1126					let collection = Self::get_typed_nft_collection(1127						collection_id,1128						misc::CollectionType::Regular,1129					)?;11301131					Self::check_collection_owner(&collection, &sender)?;11321133					<PalletCommon<T>>::set_scoped_collection_property(1134						collection_id,1135						RMRK_SCOPE,1136						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137					)?;1138				}1139			}11401141			Self::deposit_event(Event::PropertySet {1142				collection_id: rmrk_collection_id,1143				maybe_nft_id,1144				key,1145				value,1146			});11471148			Ok(())1149		}11501151		/// Set a different order of resource priorities for an NFT. Priorities can be used,1152		/// for example, for order of rendering.1153		///1154		/// Note that the priorities are not updated automatically, and are an empty vector1155		/// by default. There is no pre-set definition for the order to be particular,1156		/// it can be interpreted arbitrarily use-case by use-case.1157		///1158		/// # Permissions:1159		/// - Token owner1160		///1161		/// # Arguments:1162		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1163		/// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1164		/// - `priorities`: Ordered vector of resource IDs.1165		#[transactional]1166		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]1167		pub fn set_priority(1168			origin: OriginFor<T>,1169			rmrk_collection_id: RmrkCollectionId,1170			rmrk_nft_id: RmrkNftId,1171			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1172		) -> DispatchResult {1173			let sender = ensure_signed(origin)?;1174			let sender = T::CrossAccountId::from_sub(sender);11751176			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1177			let nft_id = rmrk_nft_id.into();11781179			let collection =1180				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1181			collection.check_is_external()?;11821183			let budget = budget::Value::new(NESTING_BUDGET);11841185			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1186			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11871188			<PalletNft<T>>::set_scoped_token_property(1189				collection_id,1190				nft_id,1191				RMRK_SCOPE,1192				Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1193			)?;11941195			Self::deposit_event(Event::<T>::PrioritySet {1196				collection_id: rmrk_collection_id,1197				nft_id: rmrk_nft_id,1198			});11991200			Ok(())1201		}12021203		/// Create and set/propose a basic resource for an NFT.1204		///1205		/// A basic resource is the simplest, lacking a Base and anything that comes with it.1206		/// See RMRK docs for more information and examples.1207		///1208		/// # Permissions:1209		/// - Collection issuer - if not the token owner, adding the resource will warrant1210		/// the owner's [acceptance](crate::pallet::Call::accept_resource).1211		///1212		/// # Arguments:1213		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1214		/// - `nft_id`: ID of the NFT to assign a resource to.1215		/// - `resource`: Data of the resource to be created.1216		#[transactional]1217		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1218		pub fn add_basic_resource(1219			origin: OriginFor<T>,1220			rmrk_collection_id: RmrkCollectionId,1221			nft_id: RmrkNftId,1222			resource: RmrkBasicResource,1223		) -> DispatchResult {1224			let sender = ensure_signed(origin.clone())?;12251226			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1227			let collection =1228				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1229			collection.check_is_external()?;12301231			let resource_id = Self::resource_add(1232				sender,1233				collection_id,1234				nft_id.into(),1235				RmrkResourceTypes::Basic(resource),1236			)?;12371238			Self::deposit_event(Event::ResourceAdded {1239				nft_id,1240				resource_id,1241			});1242			Ok(())1243		}12441245		/// Create and set/propose a composable resource for an NFT.1246		///1247		/// A composable resource links to a Base and has a subset of its Parts it is composed of.1248		/// See RMRK docs for more information and examples.1249		///1250		/// # Permissions:1251		/// - Collection issuer - if not the token owner, adding the resource will warrant1252		/// the owner's [acceptance](crate::pallet::Call::accept_resource).1253		///1254		/// # Arguments:1255		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1256		/// - `nft_id`: ID of the NFT to assign a resource to.1257		/// - `resource`: Data of the resource to be created.1258		#[transactional]1259		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1260		pub fn add_composable_resource(1261			origin: OriginFor<T>,1262			rmrk_collection_id: RmrkCollectionId,1263			nft_id: RmrkNftId,1264			resource: RmrkComposableResource,1265		) -> DispatchResult {1266			let sender = ensure_signed(origin.clone())?;12671268			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1269			let collection =1270				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1271			collection.check_is_external()?;12721273			let base_id = resource.base;12741275			let resource_id = Self::resource_add(1276				sender,1277				collection_id,1278				nft_id.into(),1279				RmrkResourceTypes::Composable(resource),1280			)?;12811282			<PalletNft<T>>::try_mutate_token_aux_property(1283				collection_id,1284				nft_id.into(),1285				RMRK_SCOPE,1286				Self::get_scoped_property_key(AssociatedBases)?,1287				|value| -> DispatchResult {1288					let mut bases: BasesMap = match value {1289						Some(value) => Self::decode_property_value(value)?,1290						None => BasesMap::new(),1291					};12921293					*bases.entry(base_id).or_insert(0) += 1;12941295					*value = Some(Self::encode_property_value(&bases)?);1296					Ok(())1297				},1298			)?;12991300			Self::deposit_event(Event::ResourceAdded {1301				nft_id,1302				resource_id,1303			});1304			Ok(())1305		}13061307		/// Create and set/propose a slot resource for an NFT.1308		///1309		/// A slot resource links to a Base and a slot ID in it which it can fit into.1310		/// See RMRK docs for more information and examples.1311		///1312		/// # Permissions:1313		/// - Collection issuer - if not the token owner, adding the resource will warrant1314		/// the owner's [acceptance](crate::pallet::Call::accept_resource).1315		///1316		/// # Arguments:1317		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1318		/// - `nft_id`: ID of the NFT to assign a resource to.1319		/// - `resource`: Data of the resource to be created.1320		#[transactional]1321		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1322		pub fn add_slot_resource(1323			origin: OriginFor<T>,1324			rmrk_collection_id: RmrkCollectionId,1325			nft_id: RmrkNftId,1326			resource: RmrkSlotResource,1327		) -> DispatchResult {1328			let sender = ensure_signed(origin.clone())?;13291330			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1331			let collection =1332				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1333			collection.check_is_external()?;13341335			let resource_id = Self::resource_add(1336				sender,1337				collection_id,1338				nft_id.into(),1339				RmrkResourceTypes::Slot(resource),1340			)?;13411342			Self::deposit_event(Event::ResourceAdded {1343				nft_id,1344				resource_id,1345			});1346			Ok(())1347		}13481349		/// Remove and erase a resource from an NFT.1350		///1351		/// If the sender does not own the NFT, then it will be pending confirmation,1352		/// and will have to be [accepted](crate::pallet::Call::accept_resource_removal) by the token owner.1353		///1354		/// # Permissions1355		/// - Collection issuer1356		///1357		/// # Arguments1358		/// - `collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1359		/// - `nft_id`: ID of the NFT with a resource to be removed.1360		/// - `resource_id`: ID of the resource to be removed.1361		#[transactional]1362		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1363		pub fn remove_resource(1364			origin: OriginFor<T>,1365			rmrk_collection_id: RmrkCollectionId,1366			nft_id: RmrkNftId,1367			resource_id: RmrkResourceId,1368		) -> DispatchResult {1369			let sender = ensure_signed(origin.clone())?;13701371			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1372			let collection =1373				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1374			collection.check_is_external()?;13751376			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13771378			Self::deposit_event(Event::ResourceRemoval {1379				nft_id,1380				resource_id,1381			});1382			Ok(())1383		}1384	}1385}13861387impl<T: Config> Pallet<T> {1388	/// Transform one of possible RMRK keys into a byte key with a RMRK scope.1389	pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1390		let key = rmrk_key.to_key::<T>()?;13911392		let scoped_key = RMRK_SCOPE1393			.apply(key)1394			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13951396		Ok(scoped_key)1397	}13981399	/// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1400	/// and encoding the value from an arbitrary type into bytes.1401	pub fn encode_rmrk_property<E: Encode>(1402		rmrk_key: RmrkProperty,1403		value: &E,1404	) -> Result<Property, DispatchError> {1405		let key = rmrk_key.to_key::<T>()?;14061407		let value = Self::encode_property_value(value)?;14081409		let property = Property { key, value };14101411		Ok(property)1412	}14131414	/// Encode property value from an arbitrary type into bytes for storage.1415	pub fn encode_property_value<E: Encode, S: Get<u32>>(1416		value: &E,1417	) -> Result<BoundedBytes<S>, DispatchError> {1418		let value = value1419			.encode()1420			.try_into()1421			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14221423		Ok(value)1424	}14251426	/// Decode property value from bytes into an arbitrary type.1427	pub fn decode_property_value<D: Decode, S: Get<u32>>(1428		vec: &BoundedBytes<S>,1429	) -> Result<D, DispatchError> {1430		vec.decode()1431			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1432	}14331434	/// Change the limit of a property value byte vector.1435	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1436	where1437		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1438	{1439		vec.rebind()1440			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1441	}14421443	/// Initialize a new NFT collection with certain RMRK-scoped properties.1444	///1445	/// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1446	fn init_collection(1447		sender: T::CrossAccountId,1448		data: CreateCollectionData<T::AccountId>,1449		properties: impl Iterator<Item = Property>,1450	) -> Result<CollectionId, DispatchError> {1451		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14521453		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1454			return Err(<Error<T>>::NoAvailableCollectionId.into());1455		}14561457		<PalletCommon<T>>::set_scoped_collection_properties(1458			collection_id?,1459			RMRK_SCOPE,1460			properties,1461		)?;14621463		collection_id1464	}14651466	/// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1467	///1468	/// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1469	pub fn create_nft(1470		sender: &T::CrossAccountId,1471		owner: &T::CrossAccountId,1472		collection: &NonfungibleHandle<T>,1473		properties: impl Iterator<Item = Property>,1474	) -> Result<TokenId, DispatchError> {1475		let data = CreateNftExData {1476			properties: BoundedVec::default(),1477			owner: owner.clone(),1478		};14791480		let budget = budget::Value::new(NESTING_BUDGET);14811482		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;14831484		let nft_id = <PalletNft<T>>::current_token_id(collection.id);14851486		<PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14871488		Ok(nft_id)1489	}14901491	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1492	///1493	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1494	fn destroy_nft(1495		sender: T::CrossAccountId,1496		collection_id: CollectionId,1497		token_id: TokenId,1498		max_burns: u32,1499		error_if_not_owned: Error<T>,1500	) -> DispatchResultWithPostInfo {1501		let collection =1502			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15031504		let token_data =1505			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15061507		let from = token_data.owner;15081509		let owner_check_budget = budget::Value::new(NESTING_BUDGET);15101511		ensure!(1512			<PalletStructure<T>>::check_indirectly_owned(1513				sender.clone(),1514				collection_id,1515				token_id,1516				None,1517				&owner_check_budget1518			)?,1519			error_if_not_owned,1520		);15211522		let burns_budget = budget::Value::new(max_burns);1523		let breadth_budget = budget::Value::new(max_burns);15241525		<PalletNft<T>>::burn_recursively(1526			&collection,1527			&from,1528			token_id,1529			&burns_budget,1530			&breadth_budget,1531		)1532	}15331534	/// Add a sent token pending acceptance to the target owning token as a property.1535	fn insert_pending_child(1536		target: (CollectionId, TokenId),1537		child: (RmrkCollectionId, RmrkNftId),1538	) -> DispatchResult {1539		Self::mutate_pending_children(target, |pending_children| {1540			pending_children.insert(child);1541		})1542	}15431544	/// Remove a sent token pending acceptance from the target token's properties.1545	fn remove_pending_child(1546		target: (CollectionId, TokenId),1547		child: (RmrkCollectionId, RmrkNftId),1548	) -> DispatchResult {1549		Self::mutate_pending_children(target, |pending_children| {1550			pending_children.remove(&child);1551		})1552	}15531554	/// Apply a mutation to the property of a token containing sent tokens1555	/// that are currently pending acceptance.1556	fn mutate_pending_children(1557		(target_collection_id, target_nft_id): (CollectionId, TokenId),1558		f: impl FnOnce(&mut PendingChildrenSet),1559	) -> DispatchResult {1560		<PalletNft<T>>::try_mutate_token_aux_property(1561			target_collection_id,1562			target_nft_id,1563			RMRK_SCOPE,1564			Self::get_scoped_property_key(PendingChildren)?,1565			|pending_children| -> DispatchResult {1566				let mut map = match pending_children {1567					Some(map) => Self::decode_property_value(map)?,1568					None => PendingChildrenSet::new(),1569				};15701571				f(&mut map);15721573				*pending_children = Some(Self::encode_property_value(&map)?);15741575				Ok(())1576			},1577		)1578	}15791580	/// Get an iterator from a token's property containing tokens sent to it1581	/// that are currently pending acceptance.1582	fn iterate_pending_children(1583		collection_id: CollectionId,1584		nft_id: TokenId,1585	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1586		let property = <PalletNft<T>>::token_aux_property((1587			collection_id,1588			nft_id,1589			RMRK_SCOPE,1590			Self::get_scoped_property_key(PendingChildren)?,1591		));15921593		let pending_children = match property {1594			Some(map) => Self::decode_property_value(&map)?,1595			None => PendingChildrenSet::new(),1596		};15971598		Ok(pending_children.into_iter())1599	}16001601	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1602	/// Thus, the returned resource ID should be used.1603	/// 1604	/// Resource IDs are unique only across an NFT.1605	fn acquire_next_resource_id(1606		collection_id: CollectionId,1607		nft_id: TokenId,1608	) -> Result<RmrkResourceId, DispatchError> {1609		let resource_id: RmrkResourceId =1610			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16111612		let next_id = resource_id1613			.checked_add(1)1614			.ok_or(<Error<T>>::NoAvailableResourceId)?;16151616		<PalletNft<T>>::set_scoped_token_property(1617			collection_id,1618			nft_id,1619			RMRK_SCOPE,1620			Self::encode_rmrk_property(NextResourceId, &next_id)?,1621		)?;16221623		Ok(resource_id)1624	}16251626	/// Create and add a resource for a regular NFT, mark it as pending if the sender1627	/// is not the token owner. The sender must be the collection owner.1628	fn resource_add(1629		sender: T::AccountId,1630		collection_id: CollectionId,1631		nft_id: TokenId,1632		resource: RmrkResourceTypes,1633	) -> Result<RmrkResourceId, DispatchError> {1634		let collection =1635			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1636		ensure!(collection.owner == sender, Error::<T>::NoPermission);16371638		let sender = T::CrossAccountId::from_sub(sender);1639		let budget = budget::Value::new(NESTING_BUDGET);16401641		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1642			.map_err(Self::map_unique_err_to_proxy)?;16431644		let pending = sender != nft_owner;16451646		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16471648		let resource_info = RmrkResourceInfo {1649			id,1650			resource,1651			pending,1652			pending_removal: false,1653		};16541655		<PalletNft<T>>::try_mutate_token_aux_property(1656			collection_id,1657			nft_id,1658			RMRK_SCOPE,1659			Self::get_scoped_property_key(ResourceId(id))?,1660			|value| -> DispatchResult {1661				*value = Some(Self::encode_property_value(&resource_info)?);16621663				Ok(())1664			},1665		)?;16661667		Ok(id)1668	}16691670	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1671	/// The sender must be the collection owner.1672	fn resource_remove(1673		sender: T::AccountId,1674		collection_id: CollectionId,1675		nft_id: TokenId,1676		resource_id: RmrkResourceId,1677	) -> DispatchResult {1678		let collection =1679			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1680		ensure!(collection.owner == sender, Error::<T>::NoPermission);16811682		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16831684		let resource = <PalletNft<T>>::token_aux_property((1685			collection_id,1686			nft_id,1687			RMRK_SCOPE,1688			resource_id_key.clone(),1689		))1690		.ok_or(<Error<T>>::ResourceDoesntExist)?;16911692		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16931694		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1695		let topmost_owner =1696			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16971698		let sender = T::CrossAccountId::from_sub(sender);1699		if topmost_owner == sender {1700			<PalletNft<T>>::remove_token_aux_property(1701				collection_id,1702				nft_id,1703				RMRK_SCOPE,1704				Self::get_scoped_property_key(ResourceId(resource_id))?,1705			);17061707			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1708				let base_id = resource.base;17091710				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1711			}1712		} else {1713			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1714				res.pending_removal = true;17151716				Ok(())1717			})?;1718		}17191720		Ok(())1721	}17221723	/// Remove a Base ID from an NFT if they are associated. 1724	/// The Base itself is deleted if the number of associated NFTs reaches 0.1725	fn remove_associated_base_id(1726		collection_id: CollectionId,1727		nft_id: TokenId,1728		base_id: RmrkBaseId,1729	) -> DispatchResult {1730		<PalletNft<T>>::try_mutate_token_aux_property(1731			collection_id,1732			nft_id,1733			RMRK_SCOPE,1734			Self::get_scoped_property_key(AssociatedBases)?,1735			|value| -> DispatchResult {1736				let mut bases: BasesMap = match value {1737					Some(value) => Self::decode_property_value(value)?,1738					None => BasesMap::new(),1739				};17401741				let remaining = bases.get(&base_id);17421743				if let Some(remaining) = remaining {1744					if let Some(0) | None = remaining.checked_sub(1) {1745						bases.remove(&base_id);1746					}1747				}17481749				*value = Some(Self::encode_property_value(&bases)?);1750				Ok(())1751			},1752		)1753	}17541755	/// Apply a mutation to a resource stored in the token properties of an NFT.1756	fn try_mutate_resource_info(1757		collection_id: CollectionId,1758		nft_id: TokenId,1759		resource_id: RmrkResourceId,1760		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1761	) -> DispatchResult {1762		<PalletNft<T>>::try_mutate_token_aux_property(1763			collection_id,1764			nft_id,1765			RMRK_SCOPE,1766			Self::get_scoped_property_key(ResourceId(resource_id))?,1767			|value| match value {1768				Some(value) => {1769					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17701771					f(&mut resource_info)?;17721773					*value = Self::encode_property_value(&resource_info)?;17741775					Ok(())1776				}1777				None => Err(<Error<T>>::ResourceDoesntExist.into()),1778			},1779		)1780	}17811782	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1783	fn change_collection_owner(1784		collection_id: CollectionId,1785		collection_type: misc::CollectionType,1786		sender: T::AccountId,1787		new_owner: T::AccountId,1788	) -> DispatchResult {1789		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1790		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17911792		let mut collection = collection.into_inner();17931794		collection.owner = new_owner;1795		collection.save()1796	}17971798	/// Ensure that an account is the collection owner/issuer, return an error if not.1799	pub fn check_collection_owner(1800		collection: &NonfungibleHandle<T>,1801		account: &T::CrossAccountId,1802	) -> DispatchResult {1803		collection1804			.check_is_owner(account)1805			.map_err(Self::map_unique_err_to_proxy)1806	}18071808	/// Get the latest yet-unused RMRK collection index from the storage.1809	pub fn last_collection_idx() -> RmrkCollectionId {1810		<CollectionIndex<T>>::get()1811	}18121813	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1814	pub fn unique_collection_id(1815		rmrk_collection_id: RmrkCollectionId,1816	) -> Result<CollectionId, DispatchError> {1817		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1818			.map_err(|_| <Error<T>>::CollectionUnknown.into())1819	}18201821	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1822	pub fn rmrk_collection_id(1823		unique_collection_id: CollectionId,1824	) -> Result<RmrkCollectionId, DispatchError> {1825		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1826	}18271828	/// Fetch a Unique NFT collection.1829	pub fn get_nft_collection(1830		collection_id: CollectionId,1831	) -> Result<NonfungibleHandle<T>, DispatchError> {1832		let collection = <CollectionHandle<T>>::try_get(collection_id)1833			.map_err(|_| <Error<T>>::CollectionUnknown)?;18341835		match collection.mode {1836			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1837			_ => Err(<Error<T>>::CollectionUnknown.into()),1838		}1839	}18401841	/// Check if an NFT collection with such an ID exists.1842	pub fn collection_exists(collection_id: CollectionId) -> bool {1843		<CollectionHandle<T>>::try_get(collection_id).is_ok()1844	}18451846	/// Fetch and decode a RMRK-scoped collection property value in bytes.1847	pub fn get_collection_property(1848		collection_id: CollectionId,1849		key: RmrkProperty,1850	) -> Result<PropertyValue, DispatchError> {1851		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1852			.get(&Self::get_scoped_property_key(key)?)1853			.ok_or(<Error<T>>::CollectionUnknown)?1854			.clone();18551856		Ok(collection_property)1857	}18581859	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1860	pub fn get_collection_property_decoded<V: Decode>(1861		collection_id: CollectionId,1862		key: RmrkProperty,1863	) -> Result<V, DispatchError> {1864		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1865	}18661867	/// Get the type of a collection stored as a scoped property.1868	///1869	/// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1870	pub fn get_collection_type(1871		collection_id: CollectionId,1872	) -> Result<misc::CollectionType, DispatchError> {1873		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1874			if err != <Error<T>>::CollectionUnknown.into() {1875				<Error<T>>::CorruptedCollectionType.into()1876			} else {1877				err1878			}1879		})1880	}18811882	/// Ensure that the type of the collection equals the provided type,1883	/// otherwise return an error.1884	pub fn ensure_collection_type(1885		collection_id: CollectionId,1886		collection_type: misc::CollectionType,1887	) -> DispatchResult {1888		let actual_type = Self::get_collection_type(collection_id)?;1889		ensure!(1890			actual_type == collection_type,1891			<CommonError<T>>::NoPermission1892		);18931894		Ok(())1895	}18961897	/// Fetch an NFT collection, but make sure it has the appropriate type.1898	pub fn get_typed_nft_collection(1899		collection_id: CollectionId,1900		collection_type: misc::CollectionType,1901	) -> Result<NonfungibleHandle<T>, DispatchError> {1902		Self::ensure_collection_type(collection_id, collection_type)?;19031904		Self::get_nft_collection(collection_id)1905	}19061907	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1908	/// but also return the Unique collection ID.1909	pub fn get_typed_nft_collection_mapped(1910		rmrk_collection_id: RmrkCollectionId,1911		collection_type: misc::CollectionType,1912	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1913		let unique_collection_id = match collection_type {1914			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1915			_ => rmrk_collection_id.into(),1916		};19171918		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19191920		Ok((collection, unique_collection_id))1921	}19221923	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1924	pub fn get_nft_property(1925		collection_id: CollectionId,1926		nft_id: TokenId,1927		key: RmrkProperty,1928	) -> Result<PropertyValue, DispatchError> {1929		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1930			.get(&Self::get_scoped_property_key(key)?)1931			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1932			.clone();19331934		Ok(nft_property)1935	}19361937	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1938	pub fn get_nft_property_decoded<V: Decode>(1939		collection_id: CollectionId,1940		nft_id: TokenId,1941		key: RmrkProperty,1942	) -> Result<V, DispatchError> {1943		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1944	}19451946	/// Check that an NFT exists.1947	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1948		<TokenData<T>>::contains_key((collection_id, nft_id))1949	}19501951	/// Get the type of an NFT stored as a scoped property.1952	///1953	/// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1954	pub fn get_nft_type(1955		collection_id: CollectionId,1956		token_id: TokenId,1957	) -> Result<NftType, DispatchError> {1958		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1959			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1960	}19611962	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1963	pub fn ensure_nft_type(1964		collection_id: CollectionId,1965		token_id: TokenId,1966		nft_type: NftType,1967	) -> DispatchResult {1968		let actual_type = Self::get_nft_type(collection_id, token_id)?;1969		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19701971		Ok(())1972	}19731974	/// Ensure that an account is the owner of the token, either directly1975	/// or at the top of the nesting hierarchy; return an error if it is not.1976	pub fn ensure_nft_owner(1977		collection_id: CollectionId,1978		token_id: TokenId,1979		possible_owner: &T::CrossAccountId,1980		nesting_budget: &dyn budget::Budget,1981	) -> DispatchResult {1982		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1983			possible_owner.clone(),1984			collection_id,1985			token_id,1986			None,1987			nesting_budget,1988		)1989		.map_err(Self::map_unique_err_to_proxy)?;19901991		ensure!(is_owned, <Error<T>>::NoPermission);19921993		Ok(())1994	}19951996	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,1997	/// or, if None are provided, return all non-scoped properties.1998	pub fn filter_user_properties<Key, Value, R, Mapper>(1999		collection_id: CollectionId,2000		token_id: Option<TokenId>,2001		filter_keys: Option<Vec<RmrkPropertyKey>>,2002		mapper: Mapper,2003	) -> Result<Vec<R>, DispatchError>2004	where2005		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2006		Value: Decode + Default,2007		Mapper: Fn(Key, Value) -> R,2008	{2009		filter_keys2010			.map(|keys| {2011				let properties = keys2012					.into_iter()2013					.filter_map(|key| {2014						let key: Key = key.try_into().ok()?;20152016						let value = match token_id {2017							Some(token_id) => Self::get_nft_property_decoded(2018								collection_id,2019								token_id,2020								UserProperty(key.as_ref()),2021							),2022							None => Self::get_collection_property_decoded(2023								collection_id,2024								UserProperty(key.as_ref()),2025							),2026						}2027						.ok()?;20282029						Some(mapper(key, value))2030					})2031					.collect();20322033				Ok(properties)2034			})2035			.unwrap_or_else(|| {2036				let properties =2037					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20382039				Ok(properties)2040			})2041	}20422043	/// Get all non-scoped properties from a collection or a token, and apply some transformation,2044	/// supplied by `mapper`, to each key-value pair.2045	pub fn iterate_user_properties<Key, Value, R, Mapper>(2046		collection_id: CollectionId,2047		token_id: Option<TokenId>,2048		mapper: Mapper,2049	) -> Result<impl Iterator<Item = R>, DispatchError>2050	where2051		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2052		Value: Decode + Default,2053		Mapper: Fn(Key, Value) -> R,2054	{2055		let properties = match token_id {2056			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2057			None => <PalletCommon<T>>::collection_properties(collection_id),2058		};20592060		let properties = properties.into_iter().filter_map(move |(key, value)| {2061			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20622063			let key: Key = key.to_vec().try_into().ok()?;2064			let value: Value = value.decode().ok()?;20652066			Some(mapper(key, value))2067		});20682069		Ok(properties)2070	}20712072	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2073	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2074		map_unique_err_to_proxy! {2075			match err {2076				CommonError::NoPermission => NoPermission,2077				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2078				CommonError::PublicMintingNotAllowed => NoPermission,2079				CommonError::TokenNotFound => NoAvailableNftId,2080				CommonError::ApprovedValueTooLow => NoPermission,2081				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2082				StructureError::TokenNotFound => NoAvailableNftId,2083				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2084			}2085		}2086	}2087}
modifiedpallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/rpc.rs
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -224,7 +224,7 @@
 	Ok(properties)
 }
 
-/// Get data of resources of an NFT.
+/// Get full information on each resource of an NFT, including pending.
 pub fn nft_resources<T: Config>(
 	collection_id: RmrkCollectionId,
 	nft_id: RmrkNftId,
modifiedpallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -30,7 +30,7 @@
 //! of solutions based on RMRK.
 //!
 //! RMRK Equip itself contains functionality to equip NFTs, and work with Bases,
-//! Parts, and Themes.
+//! Parts, and Themes. See [Proxy Implementation](#proxy-implementation) for details.
 //!
 //! 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`].
@@ -52,6 +52,42 @@
 //! - 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>
+//! 
+//! ## Terminology
+//! 
+//! For more information on RMRK, see RMRK's own documentation.
+//! 
+//! ### Intro to RMRK
+//! 
+//! - **Resource:** Additional piece of metadata of an NFT usually serving to add 
+//! a piece of media on top of the root metadata (NFT's own), be it a different wing 
+//! on the root template bird or something entirely unrelated.
+//! 
+//! - **Base:** A list of possible "components" - Parts, a combination of which can 
+//! be appended/equipped to/on an NFT.
+//! 
+//! - **Part:** Something that, together with other Parts, can constitute an NFT. 
+//! Parts are defined in the Base to which they belong. Parts can be either 
+//! of the `slot` type or `fixed` type. Slots are intended for equippables.
+//! Note that "part of something" and "Part of a Base" can be easily confused, 
+//! and in this documentation these words are distinguished by the capital letter.
+//! 
+//! - **Theme:** Named objects of variable => value pairs which get interpolated into 
+//! the Base's `themable` Parts. Themes can hold any value, but are often represented 
+//! in RMRK's examples as colors applied to visible Parts.
+//! 
+//! ### Peculiarities in Unique
+//! 
+//! - **Scoped properties:** Properties that are normally obscured from users. 
+//! Their purpose is to contain structured metadata that was not included in the Unique standard 
+//! for collections and tokens, meant to be operated on by proxies and other outliers. 
+//! Scoped properties are prefixed with `some-scope:`, where `some-scope` is 
+//! an arbitrary keyword, like "rmrk", and `:` is an unacceptable symbol in user-defined 
+//! properties, which, along with other safeguards, makes them impossible to tamper with.
+//! 
+//! - **Auxiliary properties:** A slightly different structure of properties, 
+//! trading universality of use for more convenient storage, writes and access. 
+//! Meant to be inaccessible to end users.
 //!
 //! ## Proxy Implementation
 //!
@@ -77,10 +113,10 @@
 //!
 //! 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,
+//! is an unacceptable symbol in user-defined properties, which, along with other safeguards,
 //! makes them impossible to tamper with.
 //!
-//! ### Collection and NFT Types
+//! ### Collection and NFT Types, and Base, Parts and Themes Handling
 //!
 //! 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
@@ -134,13 +170,13 @@
 		type WeightInfo: WeightInfo;
 	}
 
-	/// Map of a base ID and a part ID to an NFT in the base collection serving as the part.
+	/// 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".
+	/// Checkmark that a Base has a Theme NFT named "default".
 	#[pallet::storage]
 	#[pallet::getter(fn base_has_default_theme)]
 	pub type BaseHasDefaultTheme<T: Config> =
@@ -167,17 +203,17 @@
 	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.
+		/// Could not find an ID for a Base collection. It is likely there were too many collections created on the chain, causing an overflow.
 		NoAvailableBaseId,
-		/// Could not find a suitable ID for a part, likely too many part tokens were created in the base.
+		/// Could not find a suitable ID for a Part, likely too many Part tokens were created in the Base, causing an overflow
 		NoAvailablePartId,
 		/// Base collection linked to this ID does not exist.
 		BaseDoesntExist,
-		/// No theme named "default" is associated with the Base.
+		/// 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.
+		/// Cannot assign equippables to a fixed Part.
 		NoEquippableOnFixedPart,
 	}
 
@@ -185,15 +221,15 @@
 	impl<T: Config> Pallet<T> {
 		/// 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)
+		/// 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.
+		/// - Anyone - will be assigned as the issuer of the Base.
 		///
 		/// # Arguments:
 		/// - `base_type`: Arbitrary media type, e.g. "svg".
 		/// - `symbol`: Arbitrary client-chosen symbol.
-		/// - `parts`: Array of Fixed and Slot parts composing the base,
+		/// - `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))]
@@ -254,7 +290,7 @@
 		/// 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).
+		/// Modeled after [Themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md).
 		///
 		/// # Permissions:
 		/// - Base issuer
@@ -379,8 +415,7 @@
 }
 
 impl<T: Config> Pallet<T> {
-	/// Create or renew an NFT serving as a part, setting its properties
-	/// to those of the part.
+	/// Create or renew an NFT serving as a Part.
 	fn create_part(
 		sender: &T::CrossAccountId,
 		collection: &NonfungibleHandle<T>,
@@ -444,7 +479,7 @@
 		Ok(())
 	}
 
-	/// Ensure that the collection under the base ID is a base collection,
+	/// 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 =
modifiedprimitives/rmrk-traits/src/resource.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/resource.rs
+++ b/primitives/rmrk-traits/src/resource.rs
@@ -151,13 +151,13 @@
 		"#)
 )]
 pub struct ResourceInfo<BoundedString, BoundedParts> {
-	/// id is a 5-character string of reasonable uniqueness.
-	/// The combination of base ID and resource id should be unique across the entire RMRK
-	/// ecosystem which
+	/// ID a unique identifier for a resource across all those of a single NFT.
+	/// The combination of a collection ID, an NFT ID, and the resource ID must be 
+	/// unique across the entire RMRK ecosystem.
 	//#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
 	pub id: ResourceId,
 
-	/// Resource
+	/// Resource type and the accordingly structured data stored
 	pub resource: ResourceTypes<BoundedString, BoundedParts>,
 
 	/// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted