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

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs65.7 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # 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 add60//! a piece of media on top of the root metadata (NFT's own), be it a different wing61//! on the root template bird or something entirely unrelated.62//!63//! - **Base:** A list of possible "components" - Parts, a combination of which can64//! 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 either68//! 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 so in this documentation these words are distinguished by the capital letter.71//!72//! - **Theme:** Named objects of variable => value pairs which get interpolated into73//! the Base's `themable` Parts. Themes can hold any value, but are often represented74//! 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 standard80//! for collections and tokens, meant to be operated on by proxies and other outliers.81//! Scoped property keys are prefixed with `some-scope:`, where `some-scope` is82//! an arbitrary keyword, like "rmrk". `:` is considered an unacceptable symbol in user-defined83//! properties, which, along with other safeguards, makes scoped ones 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 Unique 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, or Base, Parts and Themes Handling117//!118//! RMRK introduces the concept of a Base, which is a catalogue 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 this collection's NFTs. See [`CollectionType`] and [`NftType`].122//!123//! ## Interface124//!125//! ### Dispatchables126//!127//! - `create_collection` - Create a new collection of NFTs.128//! - `destroy_collection` - Destroy a collection.129//! - `change_collection_issuer` - Change the issuer of a collection.130//! Analogous to Unique's collection's [`owner`](up_data_structs::Collection).131//! - `lock_collection` - "Lock" the collection and prevent new token creation. **Cannot be undone.**132//! - `mint_nft` - Mint an NFT in a specified collection.133//! - `burn_nft` - Burn an NFT, destroying it and its nested tokens.134//! - `send` - Transfer an NFT from an account/NFT A to another account/NFT B.135//! - `accept_nft` - Accept an NFT sent from another account to self or an owned NFT.136//! - `reject_nft` - Reject an NFT sent from another account to self or owned NFT and **burn it**.137//! - `accept_resource` - Accept the addition of a newly created pending resource to an existing NFT.138//! - `accept_resource_removal` - Accept the removal of a removal-pending resource from an NFT.139//! - `set_property` - Add or edit a custom user property of a token or a collection.140//! - `set_priority` - Set a different order of resource priorities for an NFT.141//! - `add_basic_resource` - Create and set/propose a basic resource for an NFT.142//! - `add_composable_resource` - Create and set/propose a composable resource for an NFT.143//! - `add_slot_resource` - Create and set/propose a slot resource for an NFT.144//! - `remove_resource` - Remove and erase a resource from an NFT.145146#![cfg_attr(not(feature = "std"), no_std)]147148use frame_support::{pallet_prelude::*, BoundedVec, dispatch::DispatchResult};149use frame_system::{pallet_prelude::*, ensure_signed};150use sp_runtime::{DispatchError, Permill, traits::StaticLookup};151use sp_std::{152	vec::Vec,153	collections::{btree_set::BTreeSet, btree_map::BTreeMap},154};155use up_data_structs::{*, mapping::TokenAddressMapping};156use pallet_common::{157	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,158};159use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};160use pallet_structure::{Pallet as PalletStructure, Error as StructureError};161use pallet_evm::account::CrossAccountId;162use core::convert::AsRef;163164pub use pallet::*;165166#[cfg(feature = "runtime-benchmarks")]167pub mod benchmarking;168pub mod misc;169pub mod property;170pub mod rpc;171pub mod weights;172173pub type SelfWeightOf<T> = <T as Config>::WeightInfo;174175use weights::WeightInfo;176use misc::*;177pub use property::*;178179use RmrkProperty::*;180181/// Maximum number of levels of depth in the token nesting tree.182pub const NESTING_BUDGET: u32 = 5;183184type PendingTarget = (CollectionId, TokenId);185type PendingChild = (RmrkCollectionId, RmrkNftId);186type PendingChildrenSet = BTreeSet<PendingChild>;187188type BasesMap = BTreeMap<RmrkBaseId, u32>;189190#[frame_support::pallet]191pub mod pallet {192	use super::*;193	use pallet_evm::account;194195	#[pallet::config]196	pub trait Config:197		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config198	{199		/// Overarching event type.200		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;201202		/// The weight information of this pallet.203		type WeightInfo: WeightInfo;204	}205206	/// Latest yet-unused collection ID.207	#[pallet::storage]208	#[pallet::getter(fn collection_index)]209	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;210211	/// Mapping from RMRK collection ID to Unique's.212	#[pallet::storage]213	pub type UniqueCollectionId<T: Config> =214		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;215216	#[pallet::pallet]217	#[pallet::generate_store(pub(super) trait Store)]218	pub struct Pallet<T>(_);219220	#[pallet::event]221	#[pallet::generate_deposit(pub(super) fn deposit_event)]222	pub enum Event<T: Config> {223		CollectionCreated {224			issuer: T::AccountId,225			collection_id: RmrkCollectionId,226		},227		CollectionDestroyed {228			issuer: T::AccountId,229			collection_id: RmrkCollectionId,230		},231		IssuerChanged {232			old_issuer: T::AccountId,233			new_issuer: T::AccountId,234			collection_id: RmrkCollectionId,235		},236		CollectionLocked {237			issuer: T::AccountId,238			collection_id: RmrkCollectionId,239		},240		NftMinted {241			owner: T::AccountId,242			collection_id: RmrkCollectionId,243			nft_id: RmrkNftId,244		},245		NFTBurned {246			owner: T::AccountId,247			nft_id: RmrkNftId,248		},249		NFTSent {250			sender: T::AccountId,251			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,252			collection_id: RmrkCollectionId,253			nft_id: RmrkNftId,254			approval_required: bool,255		},256		NFTAccepted {257			sender: T::AccountId,258			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,259			collection_id: RmrkCollectionId,260			nft_id: RmrkNftId,261		},262		NFTRejected {263			sender: T::AccountId,264			collection_id: RmrkCollectionId,265			nft_id: RmrkNftId,266		},267		PropertySet {268			collection_id: RmrkCollectionId,269			maybe_nft_id: Option<RmrkNftId>,270			key: RmrkKeyString,271			value: RmrkValueString,272		},273		ResourceAdded {274			nft_id: RmrkNftId,275			resource_id: RmrkResourceId,276		},277		ResourceRemoval {278			nft_id: RmrkNftId,279			resource_id: RmrkResourceId,280		},281		ResourceAccepted {282			nft_id: RmrkNftId,283			resource_id: RmrkResourceId,284		},285		ResourceRemovalAccepted {286			nft_id: RmrkNftId,287			resource_id: RmrkResourceId,288		},289		PrioritySet {290			collection_id: RmrkCollectionId,291			nft_id: RmrkNftId,292		},293	}294295	#[pallet::error]296	pub enum Error<T> {297		/* Unique proxy-specific events */298		/// Property of the type of RMRK collection could not be read successfully.299		CorruptedCollectionType,300		// NftTypeEncodeError,301		/// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).302		RmrkPropertyKeyIsTooLong,303		/// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).304		RmrkPropertyValueIsTooLong,305		/// Could not find a property by the supplied key.306		RmrkPropertyIsNotFound,307		/// Something went wrong when decoding encoded data from the storage.308		/// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.309		UnableToDecodeRmrkData,310311		/* RMRK compatible events */312		/// Only destroying collections without tokens is allowed.313		CollectionNotEmpty,314		/// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.315		NoAvailableCollectionId,316		/// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.317		NoAvailableNftId,318		/// Collection does not exist, has a wrong type, or does not map to a Unique ID.319		CollectionUnknown,320		/// No permission to perform action.321		NoPermission,322		/// Token is marked as non-transferable, and thus cannot be transferred.323		NonTransferable,324		/// Too many tokens created in the collection, no new ones are allowed.325		CollectionFullOrLocked,326		/// No such resource found.327		ResourceDoesntExist,328		/// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.329		/// Sending to self is redundant.330		CannotSendToDescendentOrSelf,331		/// Not the target owner of the sent NFT.332		CannotAcceptNonOwnedNft,333		/// Not the target owner of the sent NFT.334		CannotRejectNonOwnedNft,335		/// NFT was not sent and is not pending.336		CannotRejectNonPendingNft,337		/// Resource is not pending for the operation.338		ResourceNotPending,339		/// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.340		NoAvailableResourceId,341	}342343	#[pallet::call]344	impl<T: Config> Pallet<T> {345		// todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?346347		/// Create a new collection of NFTs.348		///349		/// # Permissions:350		/// * Anyone - will be assigned as the issuer of the collection.351		///352		/// # Arguments:353		/// - `origin`: sender of the transaction354		/// - `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		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]359		pub fn create_collection(360			origin: OriginFor<T>,361			metadata: RmrkString,362			max: Option<u32>,363			symbol: RmrkCollectionSymbol,364		) -> DispatchResult {365			let sender = ensure_signed(origin)?;366367			let limits = CollectionLimits {368				owner_can_transfer: Some(false),369				token_limit: max,370				..Default::default()371			};372373			let data = CreateCollectionData {374				limits: Some(limits),375				token_prefix: symbol376					.into_inner()377					.try_into()378					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,379				permissions: Some(CollectionPermissions {380					nesting: Some(NestingPermissions {381						token_owner: true,382						collection_admin: false,383						restricted: None,384						#[cfg(feature = "runtime-benchmarks")]385						permissive: false,386					}),387					..Default::default()388				}),389				..Default::default()390			};391392			let unique_collection_id = Self::init_collection(393				T::CrossAccountId::from_sub(sender.clone()),394				data,395				[396					Self::encode_rmrk_property(Metadata, &metadata)?,397					Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,398				]399				.into_iter(),400			)?;401			let rmrk_collection_id = <CollectionIndex<T>>::get();402403			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);404405			<PalletCommon<T>>::set_scoped_collection_property(406				unique_collection_id,407				RMRK_SCOPE,408				Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,409			)?;410411			<CollectionIndex<T>>::mutate(|n| *n += 1);412413			Self::deposit_event(Event::CollectionCreated {414				issuer: sender,415				collection_id: rmrk_collection_id,416			});417418			Ok(())419		}420421		/// Destroy a collection.422		///423		/// Only empty collections can be destroyed. If it has any tokens, they must be burned first.424		///425		/// # Permissions:426		/// * Collection issuer427		///428		/// # Arguments:429		/// - `origin`: sender of the transaction430		/// - `collection_id`: RMRK ID of the collection to destroy.431		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]432		pub fn destroy_collection(433			origin: OriginFor<T>,434			collection_id: RmrkCollectionId,435		) -> DispatchResult {436			let sender = ensure_signed(origin)?;437			let cross_sender = T::CrossAccountId::from_sub(sender.clone());438439			let collection = Self::get_typed_nft_collection(440				Self::unique_collection_id(collection_id)?,441				misc::CollectionType::Regular,442			)?;443			collection.check_is_external()?;444445			<PalletNft<T>>::destroy_collection(collection, &cross_sender)446				.map_err(Self::map_unique_err_to_proxy)?;447448			Self::deposit_event(Event::CollectionDestroyed {449				issuer: sender,450				collection_id,451			});452453			Ok(())454		}455456		/// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).457		///458		/// # Permissions:459		/// * Collection issuer460		///461		/// # Arguments:462		/// - `origin`: sender of the transaction463		/// - `collection_id`: RMRK collection ID to change the issuer of.464		/// - `new_issuer`: Collection's new issuer.465		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]466		pub fn change_collection_issuer(467			origin: OriginFor<T>,468			collection_id: RmrkCollectionId,469			new_issuer: <T::Lookup as StaticLookup>::Source,470		) -> DispatchResult {471			let sender = ensure_signed(origin)?;472473			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;474			collection.check_is_external()?;475476			let new_issuer = T::Lookup::lookup(new_issuer)?;477478			Self::change_collection_owner(479				Self::unique_collection_id(collection_id)?,480				misc::CollectionType::Regular,481				sender.clone(),482				new_issuer.clone(),483			)?;484485			Self::deposit_event(Event::IssuerChanged {486				old_issuer: sender,487				new_issuer,488				collection_id,489			});490491			Ok(())492		}493494		/// "Lock" the collection and prevent new token creation. Cannot be undone.495		///496		/// # Permissions:497		/// * Collection issuer498		///499		/// # Arguments:500		/// - `origin`: sender of the transaction501		/// - `collection_id`: RMRK ID of the collection to lock.502		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]503		pub fn lock_collection(504			origin: OriginFor<T>,505			collection_id: RmrkCollectionId,506		) -> DispatchResult {507			let sender = ensure_signed(origin)?;508			let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510			let collection = Self::get_typed_nft_collection(511				Self::unique_collection_id(collection_id)?,512				misc::CollectionType::Regular,513			)?;514			collection.check_is_external()?;515516			Self::check_collection_owner(&collection, &cross_sender)?;517518			let token_count = collection.total_supply();519520			let mut collection = collection.into_inner();521			collection.limits.token_limit = Some(token_count);522			collection.save()?;523524			Self::deposit_event(Event::CollectionLocked {525				issuer: sender,526				collection_id,527			});528529			Ok(())530		}531532		/// Mint an NFT in a specified collection.533		///534		/// # Permissions:535		/// * Collection issuer536		///537		/// # Arguments:538		/// - `origin`: sender of the transaction539		/// - `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		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]547		pub fn mint_nft(548			origin: OriginFor<T>,549			owner: Option<T::AccountId>,550			collection_id: RmrkCollectionId,551			recipient: Option<T::AccountId>,552			royalty_amount: Option<Permill>,553			metadata: RmrkString,554			transferable: bool,555			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,556		) -> DispatchResult {557			let sender = ensure_signed(origin)?;558			let cross_sender = T::CrossAccountId::from_sub(sender.clone());559560			let owner = owner.unwrap_or(sender.clone());561			let cross_owner = T::CrossAccountId::from_sub(owner.clone());562563			let collection = Self::get_typed_nft_collection(564				Self::unique_collection_id(collection_id)?,565				misc::CollectionType::Regular,566			)?;567			collection.check_is_external()?;568569			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {570				recipient: recipient.unwrap_or_else(|| owner.clone()),571				amount,572			});573574			let nft_id = Self::create_nft(575				&cross_sender,576				&cross_owner,577				&collection,578				[579					Self::encode_rmrk_property(TokenType, &NftType::Regular)?,580					Self::encode_rmrk_property(Transferable, &transferable)?,581					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,582					Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,583					Self::encode_rmrk_property(Metadata, &metadata)?,584					Self::encode_rmrk_property(Equipped, &false)?,585					Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,586					Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,587					Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,588					Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,589				]590				.into_iter(),591			)592			.map_err(|err| match err {593				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),594				err => Self::map_unique_err_to_proxy(err),595			})?;596597			if let Some(resources) = resources {598				for resource in resources {599					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;600				}601			}602603			Self::deposit_event(Event::NftMinted {604				owner,605				collection_id,606				nft_id: nft_id.0,607			});608609			Ok(())610		}611612		/// Burn an NFT, destroying it and its nested tokens up to the specified limit.613		/// If the burning budget is exceeded, the transaction is reverted.614		///615		/// This is the way to burn a nested token as well.616		///617		/// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).618		///619		/// # Permissions:620		/// * Token owner621		///622		/// # Arguments:623		/// - `origin`: sender of the transaction624		/// - `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		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]630		pub fn burn_nft(631			origin: OriginFor<T>,632			collection_id: RmrkCollectionId,633			nft_id: RmrkNftId,634			max_burns: u32,635		) -> DispatchResult {636			let sender = ensure_signed(origin)?;637			let cross_sender = T::CrossAccountId::from_sub(sender.clone());638639			let collection = Self::get_typed_nft_collection(640				Self::unique_collection_id(collection_id)?,641				misc::CollectionType::Regular,642			)?;643			collection.check_is_external()?;644645			Self::destroy_nft(646				cross_sender,647				Self::unique_collection_id(collection_id)?,648				nft_id.into(),649				max_burns,650				<Error<T>>::NoPermission,651			)652			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;653654			Self::deposit_event(Event::NFTBurned {655				owner: sender,656				nft_id,657			});658659			Ok(())660		}661662		/// Transfer an NFT from an account/NFT A to another account/NFT B.663		/// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].664		///665		/// If the target owner is an NFT owned by another account, then the NFT will enter666		/// the pending state and will have to be accepted by the other account.667		///668		/// # Permissions:669		/// - Token owner670		///671		/// # Arguments:672		/// - `origin`: sender of the transaction673		/// - `rmrk_collection_id`: RMRK ID of the collection of the NFT to be transferred.674		/// - `rmrk_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		#[pallet::weight(<SelfWeightOf<T>>::send())]677		pub fn send(678			origin: OriginFor<T>,679			rmrk_collection_id: RmrkCollectionId,680			rmrk_nft_id: RmrkNftId,681			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,682		) -> DispatchResult {683			let sender = ensure_signed(origin.clone())?;684			let cross_sender = T::CrossAccountId::from_sub(sender.clone());685686			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;687			let nft_id = rmrk_nft_id.into();688689			let collection =690				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691			collection.check_is_external()?;692693			let token_data =694				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;695696			let from = token_data.owner;697698			ensure!(699				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,700				<Error<T>>::NonTransferable701			);702703			ensure!(704				Self::get_nft_property_decoded::<Option<PendingTarget>>(705					collection_id,706					nft_id,707					RmrkProperty::PendingNftAccept708				)?709				.is_none(),710				<Error<T>>::NoPermission711			);712713			let target_owner;714			let approval_required;715716			match new_owner {717				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {718					target_owner = T::CrossAccountId::from_sub(account_id.clone());719					approval_required = false;720				}721				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(722					target_collection_id,723					target_nft_id,724				) => {725					let target_collection_id = Self::unique_collection_id(target_collection_id)?;726727					let target_nft_budget = budget::Value::new(NESTING_BUDGET);728729					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(730						target_collection_id,731						target_nft_id.into(),732						Some((collection_id, nft_id)),733						&target_nft_budget,734					)735					.map_err(Self::map_unique_err_to_proxy)?;736737					approval_required = cross_sender != target_nft_owner;738739					if approval_required {740						target_owner = target_nft_owner;741742						<PalletNft<T>>::set_scoped_token_property(743							collection.id,744							nft_id,745							RMRK_SCOPE,746							Self::encode_rmrk_property::<Option<PendingTarget>>(747								PendingNftAccept,748								&Some((target_collection_id, target_nft_id.into())),749							)?,750						)?;751752						Self::insert_pending_child(753							(target_collection_id, target_nft_id.into()),754							(rmrk_collection_id, rmrk_nft_id),755						)?;756					} else {757						target_owner = T::CrossTokenAddressMapping::token_to_address(758							target_collection_id,759							target_nft_id.into(),760						);761					}762				}763			}764765			let src_nft_budget = budget::Value::new(NESTING_BUDGET);766767			<PalletNft<T>>::transfer_from(768				&collection,769				&cross_sender,770				&from,771				&target_owner,772				nft_id,773				&src_nft_budget,774			)775			.map_err(Self::map_unique_err_to_proxy)?;776777			Self::deposit_event(Event::NFTSent {778				sender,779				recipient: new_owner,780				collection_id: rmrk_collection_id,781				nft_id: rmrk_nft_id,782				approval_required,783			});784785			Ok(())786		}787788		/// Accept an NFT sent from another account to self or an owned NFT.789		///790		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.791		///792		/// # Permissions:793		/// - Token-owner-to-be794		///795		/// # Arguments:796		/// - `origin`: sender of the transaction797		/// - `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		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]802		pub fn accept_nft(803			origin: OriginFor<T>,804			rmrk_collection_id: RmrkCollectionId,805			rmrk_nft_id: RmrkNftId,806			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,807		) -> DispatchResult {808			let sender = ensure_signed(origin.clone())?;809			let cross_sender = T::CrossAccountId::from_sub(sender.clone());810811			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;812			let nft_id = rmrk_nft_id.into();813814			let collection =815				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;816			collection.check_is_external()?;817818			let new_cross_owner = match new_owner {819				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {820					T::CrossAccountId::from_sub(account_id.clone())821				}822				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(823					target_collection_id,824					target_nft_id,825				) => {826					let target_collection_id = Self::unique_collection_id(target_collection_id)?;827828					T::CrossTokenAddressMapping::token_to_address(829						target_collection_id,830						TokenId(target_nft_id),831					)832				}833			};834835			let budget = budget::Value::new(NESTING_BUDGET);836837			<PalletNft<T>>::transfer(838				&collection,839				&cross_sender,840				&new_cross_owner,841				nft_id,842				&budget,843			)844			.map_err(|err| {845				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {846					<Error<T>>::CannotAcceptNonOwnedNft.into()847				} else {848					Self::map_unique_err_to_proxy(err)849				}850			})?;851852			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(853				collection_id,854				nft_id,855				RmrkProperty::PendingNftAccept,856			)?;857858			if let Some(pending_target) = pending_target {859				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;860861				<PalletNft<T>>::set_scoped_token_property(862					collection.id,863					nft_id,864					RMRK_SCOPE,865					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,866				)?;867			}868869			Self::deposit_event(Event::NFTAccepted {870				sender,871				recipient: new_owner,872				collection_id: rmrk_collection_id,873				nft_id: rmrk_nft_id,874			});875876			Ok(())877		}878879		/// Reject an NFT sent from another account to self or owned NFT.880		/// The NFT in question will not be sent back and burnt instead.881		///882		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.883		///884		/// # Permissions:885		/// - Token-owner-to-be-not886		///887		/// # Arguments:888		/// - `origin`: sender of the transaction889		/// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.890		/// - `rmrk_nft_id`: ID of the NFT to be rejected.891		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]892		pub fn reject_nft(893			origin: OriginFor<T>,894			rmrk_collection_id: RmrkCollectionId,895			rmrk_nft_id: RmrkNftId,896		) -> DispatchResult {897			let sender = ensure_signed(origin)?;898			let cross_sender = T::CrossAccountId::from_sub(sender.clone());899900			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901			let nft_id = rmrk_nft_id.into();902903			let collection =904				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905			collection.check_is_external()?;906907			ensure!(908				<TokenData<T>>::get((collection_id, nft_id)).is_some(),909				<Error<T>>::NoAvailableNftId910			);911912			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(913				collection_id,914				nft_id,915				RmrkProperty::PendingNftAccept,916			)?;917918			match pending_target {919				Some(pending_target) => {920					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?921				}922				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),923			}924925			Self::destroy_nft(926				cross_sender,927				collection_id,928				nft_id,929				NESTING_BUDGET,930				<Error<T>>::CannotRejectNonOwnedNft,931			)932			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;933934			Self::deposit_event(Event::NFTRejected {935				sender,936				collection_id: rmrk_collection_id,937				nft_id: rmrk_nft_id,938			});939940			Ok(())941		}942943		/// Accept the addition of a newly created pending resource to an existing NFT.944		///945		/// This transaction is needed when a resource is created and assigned to an NFT946		/// by a non-owner, i.e. the collection issuer, with one of the947		/// [`add_...` transactions](Pallet::add_basic_resource).948		///949		/// # Permissions:950		/// - Token owner951		///952		/// # Arguments:953		/// - `origin`: sender of the transaction954		/// - `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		/// accept the addition of a new resource to an existing NFT958		#[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](`Pallet::remove_resource`) of a resource from an NFT.1003		///1004		/// # Permissions:1005		/// - Token owner1006		///1007		/// # Arguments:1008		/// - `origin`: sender of the transaction1009		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1010		/// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1011		/// - `resource_id`: ID of the removal-pending resource.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		/// - `origin`: sender of the transaction1089		/// - `rmrk_collection_id`: RMRK collection ID.1090		/// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1091		/// - `key`: Key of the custom property to be referenced by.1092		/// - `value`: Value of the custom property to be stored.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		/// - `origin`: sender of the transaction1163		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1164		/// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1165		/// - `priorities`: Ordered vector of resource IDs.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](Pallet::accept_resource).1211		///1212		/// # Arguments:1213		/// - `origin`: sender of the transaction1214		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1215		/// - `nft_id`: ID of the NFT to assign a resource to.1216		/// - `resource`: Data of the resource to be created.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](Pallet::accept_resource).1253		///1254		/// # Arguments:1255		/// - `origin`: sender of the transaction1256		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1257		/// - `nft_id`: ID of the NFT to assign a resource to.1258		/// - `resource`: Data of the resource to be created.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](Pallet::accept_resource).1315		///1316		/// # Arguments:1317		/// - `origin`: sender of the transaction1318		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1319		/// - `nft_id`: ID of the NFT to assign a resource to.1320		/// - `resource`: Data of the resource to be created.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](Pallet::accept_resource_removal) by the token owner.1353		///1354		/// # Permissions1355		/// - Collection issuer1356		///1357		/// # Arguments1358		/// - `origin`: sender of the transaction1359		/// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1360		/// - `nft_id`: ID of the NFT with a resource to be removed.1361		/// - `resource_id`: ID of the resource to be removed.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(1452			sender.clone(),1453			sender,1454			data,1455			up_data_structs::CollectionFlags {1456				external: true,1457				..Default::default()1458			},1459		);14601461		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1462			return Err(<Error<T>>::NoAvailableCollectionId.into());1463		}14641465		<PalletCommon<T>>::set_scoped_collection_properties(1466			collection_id?,1467			RMRK_SCOPE,1468			properties,1469		)?;14701471		collection_id1472	}14731474	/// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1475	///1476	/// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1477	pub fn create_nft(1478		sender: &T::CrossAccountId,1479		owner: &T::CrossAccountId,1480		collection: &NonfungibleHandle<T>,1481		properties: impl Iterator<Item = Property>,1482	) -> Result<TokenId, DispatchError> {1483		let data = CreateNftExData {1484			properties: BoundedVec::default(),1485			owner: owner.clone(),1486		};14871488		let budget = budget::Value::new(NESTING_BUDGET);14891490		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;14911492		let nft_id = <PalletNft<T>>::current_token_id(collection.id);14931494		<PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14951496		Ok(nft_id)1497	}14981499	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1500	///1501	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1502	fn destroy_nft(1503		sender: T::CrossAccountId,1504		collection_id: CollectionId,1505		token_id: TokenId,1506		max_burns: u32,1507		error_if_not_owned: Error<T>,1508	) -> DispatchResultWithPostInfo {1509		let collection =1510			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15111512		let token_data =1513			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15141515		let from = token_data.owner;15161517		let owner_check_budget = budget::Value::new(NESTING_BUDGET);15181519		ensure!(1520			<PalletStructure<T>>::check_indirectly_owned(1521				sender.clone(),1522				collection_id,1523				token_id,1524				None,1525				&owner_check_budget1526			)?,1527			error_if_not_owned,1528		);15291530		let burns_budget = budget::Value::new(max_burns);1531		let breadth_budget = budget::Value::new(max_burns);15321533		<PalletNft<T>>::burn_recursively(1534			&collection,1535			&from,1536			token_id,1537			&burns_budget,1538			&breadth_budget,1539		)1540	}15411542	/// Add a sent token pending acceptance to the target owning token as a property.1543	fn insert_pending_child(1544		target: (CollectionId, TokenId),1545		child: (RmrkCollectionId, RmrkNftId),1546	) -> DispatchResult {1547		Self::mutate_pending_children(target, |pending_children| {1548			pending_children.insert(child);1549		})1550	}15511552	/// Remove a sent token pending acceptance from the target token's properties.1553	fn remove_pending_child(1554		target: (CollectionId, TokenId),1555		child: (RmrkCollectionId, RmrkNftId),1556	) -> DispatchResult {1557		Self::mutate_pending_children(target, |pending_children| {1558			pending_children.remove(&child);1559		})1560	}15611562	/// Apply a mutation to the property of a token containing sent tokens1563	/// that are currently pending acceptance.1564	fn mutate_pending_children(1565		(target_collection_id, target_nft_id): (CollectionId, TokenId),1566		f: impl FnOnce(&mut PendingChildrenSet),1567	) -> DispatchResult {1568		<PalletNft<T>>::try_mutate_token_aux_property(1569			target_collection_id,1570			target_nft_id,1571			RMRK_SCOPE,1572			Self::get_scoped_property_key(PendingChildren)?,1573			|pending_children| -> DispatchResult {1574				let mut map = match pending_children {1575					Some(map) => Self::decode_property_value(map)?,1576					None => PendingChildrenSet::new(),1577				};15781579				f(&mut map);15801581				*pending_children = Some(Self::encode_property_value(&map)?);15821583				Ok(())1584			},1585		)1586	}15871588	/// Get an iterator from a token's property containing tokens sent to it1589	/// that are currently pending acceptance.1590	fn iterate_pending_children(1591		collection_id: CollectionId,1592		nft_id: TokenId,1593	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1594		let property = <PalletNft<T>>::token_aux_property((1595			collection_id,1596			nft_id,1597			RMRK_SCOPE,1598			Self::get_scoped_property_key(PendingChildren)?,1599		));16001601		let pending_children = match property {1602			Some(map) => Self::decode_property_value(&map)?,1603			None => PendingChildrenSet::new(),1604		};16051606		Ok(pending_children.into_iter())1607	}16081609	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1610	/// Thus, the returned resource ID should be used.1611	///1612	/// Resource IDs are unique only across an NFT.1613	fn acquire_next_resource_id(1614		collection_id: CollectionId,1615		nft_id: TokenId,1616	) -> Result<RmrkResourceId, DispatchError> {1617		let resource_id: RmrkResourceId =1618			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16191620		let next_id = resource_id1621			.checked_add(1)1622			.ok_or(<Error<T>>::NoAvailableResourceId)?;16231624		<PalletNft<T>>::set_scoped_token_property(1625			collection_id,1626			nft_id,1627			RMRK_SCOPE,1628			Self::encode_rmrk_property(NextResourceId, &next_id)?,1629		)?;16301631		Ok(resource_id)1632	}16331634	/// Create and add a resource for a regular NFT, mark it as pending if the sender1635	/// is not the token owner. The sender must be the collection owner.1636	fn resource_add(1637		sender: T::AccountId,1638		collection_id: CollectionId,1639		nft_id: TokenId,1640		resource: RmrkResourceTypes,1641	) -> Result<RmrkResourceId, DispatchError> {1642		let collection =1643			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1644		ensure!(collection.owner == sender, Error::<T>::NoPermission);16451646		let sender = T::CrossAccountId::from_sub(sender);1647		let budget = budget::Value::new(NESTING_BUDGET);16481649		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1650			.map_err(Self::map_unique_err_to_proxy)?;16511652		let pending = sender != nft_owner;16531654		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16551656		let resource_info = RmrkResourceInfo {1657			id,1658			resource,1659			pending,1660			pending_removal: false,1661		};16621663		<PalletNft<T>>::try_mutate_token_aux_property(1664			collection_id,1665			nft_id,1666			RMRK_SCOPE,1667			Self::get_scoped_property_key(ResourceId(id))?,1668			|value| -> DispatchResult {1669				*value = Some(Self::encode_property_value(&resource_info)?);16701671				Ok(())1672			},1673		)?;16741675		Ok(id)1676	}16771678	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1679	/// The sender must be the collection owner.1680	fn resource_remove(1681		sender: T::AccountId,1682		collection_id: CollectionId,1683		nft_id: TokenId,1684		resource_id: RmrkResourceId,1685	) -> DispatchResult {1686		let collection =1687			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1688		ensure!(collection.owner == sender, Error::<T>::NoPermission);16891690		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16911692		let resource = <PalletNft<T>>::token_aux_property((1693			collection_id,1694			nft_id,1695			RMRK_SCOPE,1696			resource_id_key.clone(),1697		))1698		.ok_or(<Error<T>>::ResourceDoesntExist)?;16991700		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17011702		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1703		let topmost_owner =1704			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17051706		let sender = T::CrossAccountId::from_sub(sender);1707		if topmost_owner == sender {1708			<PalletNft<T>>::remove_token_aux_property(1709				collection_id,1710				nft_id,1711				RMRK_SCOPE,1712				Self::get_scoped_property_key(ResourceId(resource_id))?,1713			);17141715			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1716				let base_id = resource.base;17171718				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1719			}1720		} else {1721			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1722				res.pending_removal = true;17231724				Ok(())1725			})?;1726		}17271728		Ok(())1729	}17301731	/// Remove a Base ID from an NFT if they are associated.1732	/// The Base itself is deleted if the number of associated NFTs reaches 0.1733	fn remove_associated_base_id(1734		collection_id: CollectionId,1735		nft_id: TokenId,1736		base_id: RmrkBaseId,1737	) -> DispatchResult {1738		<PalletNft<T>>::try_mutate_token_aux_property(1739			collection_id,1740			nft_id,1741			RMRK_SCOPE,1742			Self::get_scoped_property_key(AssociatedBases)?,1743			|value| -> DispatchResult {1744				let mut bases: BasesMap = match value {1745					Some(value) => Self::decode_property_value(value)?,1746					None => BasesMap::new(),1747				};17481749				let remaining = bases.get(&base_id);17501751				if let Some(remaining) = remaining {1752					if let Some(0) | None = remaining.checked_sub(1) {1753						bases.remove(&base_id);1754					}1755				}17561757				*value = Some(Self::encode_property_value(&bases)?);1758				Ok(())1759			},1760		)1761	}17621763	/// Apply a mutation to a resource stored in the token properties of an NFT.1764	fn try_mutate_resource_info(1765		collection_id: CollectionId,1766		nft_id: TokenId,1767		resource_id: RmrkResourceId,1768		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1769	) -> DispatchResult {1770		<PalletNft<T>>::try_mutate_token_aux_property(1771			collection_id,1772			nft_id,1773			RMRK_SCOPE,1774			Self::get_scoped_property_key(ResourceId(resource_id))?,1775			|value| match value {1776				Some(value) => {1777					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17781779					f(&mut resource_info)?;17801781					*value = Self::encode_property_value(&resource_info)?;17821783					Ok(())1784				}1785				None => Err(<Error<T>>::ResourceDoesntExist.into()),1786			},1787		)1788	}17891790	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1791	fn change_collection_owner(1792		collection_id: CollectionId,1793		collection_type: misc::CollectionType,1794		sender: T::AccountId,1795		new_owner: T::AccountId,1796	) -> DispatchResult {1797		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1798		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17991800		let mut collection = collection.into_inner();18011802		collection.owner = new_owner;1803		collection.save()1804	}18051806	/// Ensure that an account is the collection owner/issuer, return an error if not.1807	pub fn check_collection_owner(1808		collection: &NonfungibleHandle<T>,1809		account: &T::CrossAccountId,1810	) -> DispatchResult {1811		collection1812			.check_is_owner(account)1813			.map_err(Self::map_unique_err_to_proxy)1814	}18151816	/// Get the latest yet-unused RMRK collection index from the storage.1817	pub fn last_collection_idx() -> RmrkCollectionId {1818		<CollectionIndex<T>>::get()1819	}18201821	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1822	pub fn unique_collection_id(1823		rmrk_collection_id: RmrkCollectionId,1824	) -> Result<CollectionId, DispatchError> {1825		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1826			.map_err(|_| <Error<T>>::CollectionUnknown.into())1827	}18281829	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1830	pub fn rmrk_collection_id(1831		unique_collection_id: CollectionId,1832	) -> Result<RmrkCollectionId, DispatchError> {1833		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1834	}18351836	/// Fetch a Unique NFT collection.1837	pub fn get_nft_collection(1838		collection_id: CollectionId,1839	) -> Result<NonfungibleHandle<T>, DispatchError> {1840		let collection = <CollectionHandle<T>>::try_get(collection_id)1841			.map_err(|_| <Error<T>>::CollectionUnknown)?;18421843		match collection.mode {1844			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1845			_ => Err(<Error<T>>::CollectionUnknown.into()),1846		}1847	}18481849	/// Check if an NFT collection with such an ID exists.1850	pub fn collection_exists(collection_id: CollectionId) -> bool {1851		<CollectionHandle<T>>::try_get(collection_id).is_ok()1852	}18531854	/// Fetch and decode a RMRK-scoped collection property value in bytes.1855	pub fn get_collection_property(1856		collection_id: CollectionId,1857		key: RmrkProperty,1858	) -> Result<PropertyValue, DispatchError> {1859		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1860			.get(&Self::get_scoped_property_key(key)?)1861			.ok_or(<Error<T>>::CollectionUnknown)?1862			.clone();18631864		Ok(collection_property)1865	}18661867	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1868	pub fn get_collection_property_decoded<V: Decode>(1869		collection_id: CollectionId,1870		key: RmrkProperty,1871	) -> Result<V, DispatchError> {1872		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1873	}18741875	/// Get the type of a collection stored as a scoped property.1876	///1877	/// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1878	pub fn get_collection_type(1879		collection_id: CollectionId,1880	) -> Result<misc::CollectionType, DispatchError> {1881		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1882			if err != <Error<T>>::CollectionUnknown.into() {1883				<Error<T>>::CorruptedCollectionType.into()1884			} else {1885				err1886			}1887		})1888	}18891890	/// Ensure that the type of the collection equals the provided type,1891	/// otherwise return an error.1892	pub fn ensure_collection_type(1893		collection_id: CollectionId,1894		collection_type: misc::CollectionType,1895	) -> DispatchResult {1896		let actual_type = Self::get_collection_type(collection_id)?;1897		ensure!(1898			actual_type == collection_type,1899			<CommonError<T>>::NoPermission1900		);19011902		Ok(())1903	}19041905	/// Fetch an NFT collection, but make sure it has the appropriate type.1906	pub fn get_typed_nft_collection(1907		collection_id: CollectionId,1908		collection_type: misc::CollectionType,1909	) -> Result<NonfungibleHandle<T>, DispatchError> {1910		Self::ensure_collection_type(collection_id, collection_type)?;19111912		Self::get_nft_collection(collection_id)1913	}19141915	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1916	/// but also return the Unique collection ID.1917	pub fn get_typed_nft_collection_mapped(1918		rmrk_collection_id: RmrkCollectionId,1919		collection_type: misc::CollectionType,1920	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1921		let unique_collection_id = match collection_type {1922			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1923			_ => rmrk_collection_id.into(),1924		};19251926		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19271928		Ok((collection, unique_collection_id))1929	}19301931	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1932	pub fn get_nft_property(1933		collection_id: CollectionId,1934		nft_id: TokenId,1935		key: RmrkProperty,1936	) -> Result<PropertyValue, DispatchError> {1937		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1938			.get(&Self::get_scoped_property_key(key)?)1939			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1940			.clone();19411942		Ok(nft_property)1943	}19441945	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1946	pub fn get_nft_property_decoded<V: Decode>(1947		collection_id: CollectionId,1948		nft_id: TokenId,1949		key: RmrkProperty,1950	) -> Result<V, DispatchError> {1951		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1952	}19531954	/// Check that an NFT exists.1955	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1956		<TokenData<T>>::contains_key((collection_id, nft_id))1957	}19581959	/// Get the type of an NFT stored as a scoped property.1960	///1961	/// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1962	pub fn get_nft_type(1963		collection_id: CollectionId,1964		token_id: TokenId,1965	) -> Result<NftType, DispatchError> {1966		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1967			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1968	}19691970	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1971	pub fn ensure_nft_type(1972		collection_id: CollectionId,1973		token_id: TokenId,1974		nft_type: NftType,1975	) -> DispatchResult {1976		let actual_type = Self::get_nft_type(collection_id, token_id)?;1977		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19781979		Ok(())1980	}19811982	/// Ensure that an account is the owner of the token, either directly1983	/// or at the top of the nesting hierarchy; return an error if it is not.1984	pub fn ensure_nft_owner(1985		collection_id: CollectionId,1986		token_id: TokenId,1987		possible_owner: &T::CrossAccountId,1988		nesting_budget: &dyn budget::Budget,1989	) -> DispatchResult {1990		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1991			possible_owner.clone(),1992			collection_id,1993			token_id,1994			None,1995			nesting_budget,1996		)1997		.map_err(Self::map_unique_err_to_proxy)?;19981999		ensure!(is_owned, <Error<T>>::NoPermission);20002001		Ok(())2002	}20032004	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,2005	/// or, if None are provided, return all non-scoped properties.2006	pub fn filter_user_properties<Key, Value, R, Mapper>(2007		collection_id: CollectionId,2008		token_id: Option<TokenId>,2009		filter_keys: Option<Vec<RmrkPropertyKey>>,2010		mapper: Mapper,2011	) -> Result<Vec<R>, DispatchError>2012	where2013		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2014		Value: Decode + Default,2015		Mapper: Fn(Key, Value) -> R,2016	{2017		filter_keys2018			.map(|keys| {2019				let properties = keys2020					.into_iter()2021					.filter_map(|key| {2022						let key: Key = key.try_into().ok()?;20232024						let value = match token_id {2025							Some(token_id) => Self::get_nft_property_decoded(2026								collection_id,2027								token_id,2028								UserProperty(key.as_ref()),2029							),2030							None => Self::get_collection_property_decoded(2031								collection_id,2032								UserProperty(key.as_ref()),2033							),2034						}2035						.ok()?;20362037						Some(mapper(key, value))2038					})2039					.collect();20402041				Ok(properties)2042			})2043			.unwrap_or_else(|| {2044				let properties =2045					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20462047				Ok(properties)2048			})2049	}20502051	/// Get all non-scoped properties from a collection or a token, and apply some transformation,2052	/// supplied by `mapper`, to each key-value pair.2053	pub fn iterate_user_properties<Key, Value, R, Mapper>(2054		collection_id: CollectionId,2055		token_id: Option<TokenId>,2056		mapper: Mapper,2057	) -> Result<impl Iterator<Item = R>, DispatchError>2058	where2059		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2060		Value: Decode + Default,2061		Mapper: Fn(Key, Value) -> R,2062	{2063		let properties = match token_id {2064			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2065			None => <PalletCommon<T>>::collection_properties(collection_id),2066		};20672068		let properties = properties.into_iter().filter_map(move |(key, value)| {2069			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20702071			let key: Key = key.to_vec().try_into().ok()?;2072			let value: Value = value.decode().ok()?;20732074			Some(mapper(key, value))2075		});20762077		Ok(properties)2078	}20792080	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2081	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2082		map_unique_err_to_proxy! {2083			match err {2084				CommonError::NoPermission => NoPermission,2085				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2086				CommonError::PublicMintingNotAllowed => NoPermission,2087				CommonError::TokenNotFound => NoAvailableNftId,2088				CommonError::ApprovedValueTooLow => NoPermission,2089				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2090				StructureError::TokenNotFound => NoAvailableNftId,2091				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2092			}2093		}2094	}2095}