git.delta.rocks / unique-network / refs/commits / 2cf26e73094a

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/// A 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::*;193194	#[pallet::config]195	pub trait Config:196		frame_system::Config197		+ pallet_common::Config198		+ pallet_nonfungible::Config199		+ pallet_evm::Config200	{201		/// Overarching event type.202		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;203204		/// The weight information of this pallet.205		type WeightInfo: WeightInfo;206	}207208	/// Latest yet-unused collection ID.209	#[pallet::storage]210	#[pallet::getter(fn collection_index)]211	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;212213	/// Mapping from RMRK collection ID to Unique's.214	#[pallet::storage]215	pub type UniqueCollectionId<T: Config> =216		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;217218	#[pallet::pallet]219	#[pallet::generate_store(pub(super) trait Store)]220	pub struct Pallet<T>(_);221222	#[pallet::event]223	#[pallet::generate_deposit(pub(super) fn deposit_event)]224	pub enum Event<T: Config> {225		CollectionCreated {226			issuer: T::AccountId,227			collection_id: RmrkCollectionId,228		},229		CollectionDestroyed {230			issuer: T::AccountId,231			collection_id: RmrkCollectionId,232		},233		IssuerChanged {234			old_issuer: T::AccountId,235			new_issuer: T::AccountId,236			collection_id: RmrkCollectionId,237		},238		CollectionLocked {239			issuer: T::AccountId,240			collection_id: RmrkCollectionId,241		},242		NftMinted {243			owner: T::AccountId,244			collection_id: RmrkCollectionId,245			nft_id: RmrkNftId,246		},247		NFTBurned {248			owner: T::AccountId,249			nft_id: RmrkNftId,250		},251		NFTSent {252			sender: T::AccountId,253			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,254			collection_id: RmrkCollectionId,255			nft_id: RmrkNftId,256			approval_required: bool,257		},258		NFTAccepted {259			sender: T::AccountId,260			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,261			collection_id: RmrkCollectionId,262			nft_id: RmrkNftId,263		},264		NFTRejected {265			sender: T::AccountId,266			collection_id: RmrkCollectionId,267			nft_id: RmrkNftId,268		},269		PropertySet {270			collection_id: RmrkCollectionId,271			maybe_nft_id: Option<RmrkNftId>,272			key: RmrkKeyString,273			value: RmrkValueString,274		},275		ResourceAdded {276			nft_id: RmrkNftId,277			resource_id: RmrkResourceId,278		},279		ResourceRemoval {280			nft_id: RmrkNftId,281			resource_id: RmrkResourceId,282		},283		ResourceAccepted {284			nft_id: RmrkNftId,285			resource_id: RmrkResourceId,286		},287		ResourceRemovalAccepted {288			nft_id: RmrkNftId,289			resource_id: RmrkResourceId,290		},291		PrioritySet {292			collection_id: RmrkCollectionId,293			nft_id: RmrkNftId,294		},295	}296297	#[pallet::error]298	pub enum Error<T> {299		/* Unique proxy-specific events */300		/// Property of the type of RMRK collection could not be read successfully.301		CorruptedCollectionType,302		// NftTypeEncodeError,303		/// Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).304		RmrkPropertyKeyIsTooLong,305		/// Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).306		RmrkPropertyValueIsTooLong,307		/// Could not find a property by the supplied key.308		RmrkPropertyIsNotFound,309		/// Something went wrong when decoding encoded data from the storage.310		/// Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.311		UnableToDecodeRmrkData,312313		/* RMRK compatible events */314		/// Only destroying collections without tokens is allowed.315		CollectionNotEmpty,316		/// Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.317		NoAvailableCollectionId,318		/// Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.319		NoAvailableNftId,320		/// Collection does not exist, has a wrong type, or does not map to a Unique ID.321		CollectionUnknown,322		/// No permission to perform action.323		NoPermission,324		/// Token is marked as non-transferable, and thus cannot be transferred.325		NonTransferable,326		/// Too many tokens created in the collection, no new ones are allowed.327		CollectionFullOrLocked,328		/// No such resource found.329		ResourceDoesntExist,330		/// If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.331		/// Sending to self is redundant.332		CannotSendToDescendentOrSelf,333		/// Not the target owner of the sent NFT.334		CannotAcceptNonOwnedNft,335		/// Not the target owner of the sent NFT.336		CannotRejectNonOwnedNft,337		/// NFT was not sent and is not pending.338		CannotRejectNonPendingNft,339		/// Resource is not pending for the operation.340		ResourceNotPending,341		/// Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.342		NoAvailableResourceId,343	}344345	#[pallet::call]346	impl<T: Config> Pallet<T> {347		// todo :refactor replace every collection_id with rmrk_collection_id (and nft_id) in arguments for uniformity?348349		/// Create a new collection of NFTs.350		///351		/// # Permissions:352		/// * Anyone - will be assigned as the issuer of the collection.353		///354		/// # Arguments:355		/// - `origin`: sender of the transaction356		/// - `metadata`: Metadata describing the collection, e.g. IPFS hash. Cannot be changed.357		/// - `max`: Optional maximum number of tokens.358		/// - `symbol`: UTF-8 string with token prefix, by which to represent the token in wallets and UIs.359		/// Analogous to Unique's [`token_prefix`](up_data_structs::Collection). Cannot be changed.360		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]361		pub fn create_collection(362			origin: OriginFor<T>,363			metadata: RmrkString,364			max: Option<u32>,365			symbol: RmrkCollectionSymbol,366		) -> DispatchResult {367			let sender = ensure_signed(origin)?;368369			let limits = CollectionLimits {370				owner_can_transfer: Some(false),371				token_limit: max,372				..Default::default()373			};374375			let data = CreateCollectionData {376				limits: Some(limits),377				token_prefix: symbol378					.into_inner()379					.try_into()380					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,381				permissions: Some(CollectionPermissions {382					nesting: Some(NestingPermissions {383						token_owner: true,384						collection_admin: false,385						restricted: None,386						#[cfg(feature = "runtime-benchmarks")]387						permissive: false,388					}),389					..Default::default()390				}),391				..Default::default()392			};393394			let unique_collection_id = Self::init_collection(395				T::CrossAccountId::from_sub(sender.clone()),396				data,397				[398					Self::encode_rmrk_property(Metadata, &metadata)?,399					Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,400				]401				.into_iter(),402			)?;403			let rmrk_collection_id = <CollectionIndex<T>>::get();404405			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);406407			<PalletCommon<T>>::set_scoped_collection_property(408				unique_collection_id,409				RMRK_SCOPE,410				Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,411			)?;412413			<CollectionIndex<T>>::mutate(|n| *n += 1);414415			Self::deposit_event(Event::CollectionCreated {416				issuer: sender,417				collection_id: rmrk_collection_id,418			});419420			Ok(())421		}422423		/// Destroy a collection.424		///425		/// Only empty collections can be destroyed. If it has any tokens, they must be burned first.426		///427		/// # Permissions:428		/// * Collection issuer429		///430		/// # Arguments:431		/// - `origin`: sender of the transaction432		/// - `collection_id`: RMRK ID of the collection to destroy.433		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]434		pub fn destroy_collection(435			origin: OriginFor<T>,436			collection_id: RmrkCollectionId,437		) -> DispatchResult {438			let sender = ensure_signed(origin)?;439			let cross_sender = T::CrossAccountId::from_sub(sender.clone());440441			let collection = Self::get_typed_nft_collection(442				Self::unique_collection_id(collection_id)?,443				misc::CollectionType::Regular,444			)?;445			collection.check_is_external()?;446447			<PalletNft<T>>::destroy_collection(collection, &cross_sender)448				.map_err(Self::map_unique_err_to_proxy)?;449450			Self::deposit_event(Event::CollectionDestroyed {451				issuer: sender,452				collection_id,453			});454455			Ok(())456		}457458		/// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).459		///460		/// # Permissions:461		/// * Collection issuer462		///463		/// # Arguments:464		/// - `origin`: sender of the transaction465		/// - `collection_id`: RMRK collection ID to change the issuer of.466		/// - `new_issuer`: Collection's new issuer.467		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]468		pub fn change_collection_issuer(469			origin: OriginFor<T>,470			collection_id: RmrkCollectionId,471			new_issuer: <T::Lookup as StaticLookup>::Source,472		) -> DispatchResult {473			let sender = ensure_signed(origin)?;474475			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;476			collection.check_is_external()?;477478			let new_issuer = T::Lookup::lookup(new_issuer)?;479480			Self::change_collection_owner(481				Self::unique_collection_id(collection_id)?,482				misc::CollectionType::Regular,483				sender.clone(),484				new_issuer.clone(),485			)?;486487			Self::deposit_event(Event::IssuerChanged {488				old_issuer: sender,489				new_issuer,490				collection_id,491			});492493			Ok(())494		}495496		/// "Lock" the collection and prevent new token creation. Cannot be undone.497		///498		/// # Permissions:499		/// * Collection issuer500		///501		/// # Arguments:502		/// - `origin`: sender of the transaction503		/// - `collection_id`: RMRK ID of the collection to lock.504		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]505		pub fn lock_collection(506			origin: OriginFor<T>,507			collection_id: RmrkCollectionId,508		) -> DispatchResult {509			let sender = ensure_signed(origin)?;510			let cross_sender = T::CrossAccountId::from_sub(sender.clone());511512			let collection = Self::get_typed_nft_collection(513				Self::unique_collection_id(collection_id)?,514				misc::CollectionType::Regular,515			)?;516			collection.check_is_external()?;517518			Self::check_collection_owner(&collection, &cross_sender)?;519520			let token_count = collection.total_supply();521522			let mut collection = collection.into_inner();523			collection.limits.token_limit = Some(token_count);524			collection.save()?;525526			Self::deposit_event(Event::CollectionLocked {527				issuer: sender,528				collection_id,529			});530531			Ok(())532		}533534		/// Mint an NFT in a specified collection.535		///536		/// # Permissions:537		/// * Collection issuer538		///539		/// # Arguments:540		/// - `origin`: sender of the transaction541		/// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).542		/// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.543		/// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.544		/// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.545		/// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.546		/// - `transferable`: Can this NFT be transferred? Cannot be changed.547		/// - `resources`: Resource data to be added to the NFT immediately after minting.548		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]549		pub fn mint_nft(550			origin: OriginFor<T>,551			owner: Option<T::AccountId>,552			collection_id: RmrkCollectionId,553			recipient: Option<T::AccountId>,554			royalty_amount: Option<Permill>,555			metadata: RmrkString,556			transferable: bool,557			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,558		) -> DispatchResult {559			let sender = ensure_signed(origin)?;560			let cross_sender = T::CrossAccountId::from_sub(sender.clone());561562			let owner = owner.unwrap_or(sender.clone());563			let cross_owner = T::CrossAccountId::from_sub(owner.clone());564565			let collection = Self::get_typed_nft_collection(566				Self::unique_collection_id(collection_id)?,567				misc::CollectionType::Regular,568			)?;569			collection.check_is_external()?;570571			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {572				recipient: recipient.unwrap_or_else(|| owner.clone()),573				amount,574			});575576			let nft_id = Self::create_nft(577				&cross_sender,578				&cross_owner,579				&collection,580				[581					Self::encode_rmrk_property(TokenType, &NftType::Regular)?,582					Self::encode_rmrk_property(Transferable, &transferable)?,583					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,584					Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,585					Self::encode_rmrk_property(Metadata, &metadata)?,586					Self::encode_rmrk_property(Equipped, &false)?,587					Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,588					Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,589					Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,590					Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,591				]592				.into_iter(),593			)594			.map_err(|err| match err {595				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),596				err => Self::map_unique_err_to_proxy(err),597			})?;598599			if let Some(resources) = resources {600				for resource in resources {601					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;602				}603			}604605			Self::deposit_event(Event::NftMinted {606				owner,607				collection_id,608				nft_id: nft_id.0,609			});610611			Ok(())612		}613614		/// Burn an NFT, destroying it and its nested tokens up to the specified limit.615		/// If the burning budget is exceeded, the transaction is reverted.616		///617		/// This is the way to burn a nested token as well.618		///619		/// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).620		///621		/// # Permissions:622		/// * Token owner623		///624		/// # Arguments:625		/// - `origin`: sender of the transaction626		/// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.627		/// - `nft_id`: ID of the NFT to be destroyed.628		/// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction629		/// is reverted if there are more tokens to burn in the nesting tree than this number.630		/// This is primarily a mechanism of transaction weight control.631		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]632		pub fn burn_nft(633			origin: OriginFor<T>,634			collection_id: RmrkCollectionId,635			nft_id: RmrkNftId,636			max_burns: u32,637		) -> DispatchResult {638			let sender = ensure_signed(origin)?;639			let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641			let collection = Self::get_typed_nft_collection(642				Self::unique_collection_id(collection_id)?,643				misc::CollectionType::Regular,644			)?;645			collection.check_is_external()?;646647			Self::destroy_nft(648				cross_sender,649				Self::unique_collection_id(collection_id)?,650				nft_id.into(),651				max_burns,652				<Error<T>>::NoPermission,653			)654			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;655656			Self::deposit_event(Event::NFTBurned {657				owner: sender,658				nft_id,659			});660661			Ok(())662		}663664		/// Transfer an NFT from an account/NFT A to another account/NFT B.665		/// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].666		///667		/// If the target owner is an NFT owned by another account, then the NFT will enter668		/// the pending state and will have to be accepted by the other account.669		///670		/// # Permissions:671		/// - Token owner672		///673		/// # Arguments:674		/// - `origin`: sender of the transaction675		/// - `rmrk_collection_id`: RMRK ID of the collection of the NFT to be transferred.676		/// - `rmrk_nft_id`: ID of the NFT to be transferred.677		/// - `new_owner`: New owner of the nft which can be either an account or a NFT.678		#[pallet::weight(<SelfWeightOf<T>>::send())]679		pub fn send(680			origin: OriginFor<T>,681			rmrk_collection_id: RmrkCollectionId,682			rmrk_nft_id: RmrkNftId,683			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,684		) -> DispatchResult {685			let sender = ensure_signed(origin.clone())?;686			let cross_sender = T::CrossAccountId::from_sub(sender.clone());687688			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;689			let nft_id = rmrk_nft_id.into();690691			let collection =692				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;693			collection.check_is_external()?;694695			let token_data =696				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;697698			let from = token_data.owner;699700			ensure!(701				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,702				<Error<T>>::NonTransferable703			);704705			ensure!(706				Self::get_nft_property_decoded::<Option<PendingTarget>>(707					collection_id,708					nft_id,709					RmrkProperty::PendingNftAccept710				)?711				.is_none(),712				<Error<T>>::NoPermission713			);714715			let target_owner;716			let approval_required;717718			match new_owner {719				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {720					target_owner = T::CrossAccountId::from_sub(account_id.clone());721					approval_required = false;722				}723				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(724					target_collection_id,725					target_nft_id,726				) => {727					let target_collection_id = Self::unique_collection_id(target_collection_id)?;728729					let target_nft_budget = budget::Value::new(NESTING_BUDGET);730731					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(732						target_collection_id,733						target_nft_id.into(),734						Some((collection_id, nft_id)),735						&target_nft_budget,736					)737					.map_err(Self::map_unique_err_to_proxy)?;738739					approval_required = cross_sender != target_nft_owner;740741					if approval_required {742						target_owner = target_nft_owner;743744						<PalletNft<T>>::set_scoped_token_property(745							collection.id,746							nft_id,747							RMRK_SCOPE,748							Self::encode_rmrk_property::<Option<PendingTarget>>(749								PendingNftAccept,750								&Some((target_collection_id, target_nft_id.into())),751							)?,752						)?;753754						Self::insert_pending_child(755							(target_collection_id, target_nft_id.into()),756							(rmrk_collection_id, rmrk_nft_id),757						)?;758					} else {759						target_owner = T::CrossTokenAddressMapping::token_to_address(760							target_collection_id,761							target_nft_id.into(),762						);763					}764				}765			}766767			let src_nft_budget = budget::Value::new(NESTING_BUDGET);768769			<PalletNft<T>>::transfer_from(770				&collection,771				&cross_sender,772				&from,773				&target_owner,774				nft_id,775				&src_nft_budget,776			)777			.map_err(Self::map_unique_err_to_proxy)?;778779			Self::deposit_event(Event::NFTSent {780				sender,781				recipient: new_owner,782				collection_id: rmrk_collection_id,783				nft_id: rmrk_nft_id,784				approval_required,785			});786787			Ok(())788		}789790		/// Accept an NFT sent from another account to self or an owned NFT.791		///792		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.793		///794		/// # Permissions:795		/// - Token-owner-to-be796		///797		/// # Arguments:798		/// - `origin`: sender of the transaction799		/// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.800		/// - `rmrk_nft_id`: ID of the NFT to be accepted.801		/// - `new_owner`: Either the sender's account ID or a sender-owned NFT,802		/// whichever the accepted NFT was sent to.803		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]804		pub fn accept_nft(805			origin: OriginFor<T>,806			rmrk_collection_id: RmrkCollectionId,807			rmrk_nft_id: RmrkNftId,808			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,809		) -> DispatchResult {810			let sender = ensure_signed(origin.clone())?;811			let cross_sender = T::CrossAccountId::from_sub(sender.clone());812813			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;814			let nft_id = rmrk_nft_id.into();815816			let collection =817				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;818			collection.check_is_external()?;819820			let new_cross_owner = match new_owner {821				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {822					T::CrossAccountId::from_sub(account_id.clone())823				}824				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(825					target_collection_id,826					target_nft_id,827				) => {828					let target_collection_id = Self::unique_collection_id(target_collection_id)?;829830					T::CrossTokenAddressMapping::token_to_address(831						target_collection_id,832						TokenId(target_nft_id),833					)834				}835			};836837			let budget = budget::Value::new(NESTING_BUDGET);838839			<PalletNft<T>>::transfer(840				&collection,841				&cross_sender,842				&new_cross_owner,843				nft_id,844				&budget,845			)846			.map_err(|err| {847				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {848					<Error<T>>::CannotAcceptNonOwnedNft.into()849				} else {850					Self::map_unique_err_to_proxy(err)851				}852			})?;853854			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(855				collection_id,856				nft_id,857				RmrkProperty::PendingNftAccept,858			)?;859860			if let Some(pending_target) = pending_target {861				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;862863				<PalletNft<T>>::set_scoped_token_property(864					collection.id,865					nft_id,866					RMRK_SCOPE,867					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,868				)?;869			}870871			Self::deposit_event(Event::NFTAccepted {872				sender,873				recipient: new_owner,874				collection_id: rmrk_collection_id,875				nft_id: rmrk_nft_id,876			});877878			Ok(())879		}880881		/// Reject an NFT sent from another account to self or owned NFT.882		/// The NFT in question will not be sent back and burnt instead.883		///884		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.885		///886		/// # Permissions:887		/// - Token-owner-to-be-not888		///889		/// # Arguments:890		/// - `origin`: sender of the transaction891		/// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.892		/// - `rmrk_nft_id`: ID of the NFT to be rejected.893		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]894		pub fn reject_nft(895			origin: OriginFor<T>,896			rmrk_collection_id: RmrkCollectionId,897			rmrk_nft_id: RmrkNftId,898		) -> DispatchResult {899			let sender = ensure_signed(origin)?;900			let cross_sender = T::CrossAccountId::from_sub(sender.clone());901902			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;903			let nft_id = rmrk_nft_id.into();904905			let collection =906				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;907			collection.check_is_external()?;908909			ensure!(910				<TokenData<T>>::get((collection_id, nft_id)).is_some(),911				<Error<T>>::NoAvailableNftId912			);913914			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(915				collection_id,916				nft_id,917				RmrkProperty::PendingNftAccept,918			)?;919920			match pending_target {921				Some(pending_target) => {922					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?923				}924				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),925			}926927			Self::destroy_nft(928				cross_sender,929				collection_id,930				nft_id,931				NESTING_BUDGET,932				<Error<T>>::CannotRejectNonOwnedNft,933			)934			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;935936			Self::deposit_event(Event::NFTRejected {937				sender,938				collection_id: rmrk_collection_id,939				nft_id: rmrk_nft_id,940			});941942			Ok(())943		}944945		/// Accept the addition of a newly created pending resource to an existing NFT.946		///947		/// This transaction is needed when a resource is created and assigned to an NFT948		/// by a non-owner, i.e. the collection issuer, with one of the949		/// [`add_...` transactions](Pallet::add_basic_resource).950		///951		/// # Permissions:952		/// - Token owner953		///954		/// # Arguments:955		/// - `origin`: sender of the transaction956		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.957		/// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.958		/// - `resource_id`: ID of the newly created pending resource.959		/// accept the addition of a new resource to an existing NFT960		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]961		pub fn accept_resource(962			origin: OriginFor<T>,963			rmrk_collection_id: RmrkCollectionId,964			rmrk_nft_id: RmrkNftId,965			resource_id: RmrkResourceId,966		) -> DispatchResult {967			let sender = ensure_signed(origin)?;968			let cross_sender = T::CrossAccountId::from_sub(sender);969970			let collection_id = Self::unique_collection_id(rmrk_collection_id)971				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;972			let collection =973				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;974			collection.check_is_external()?;975976			let nft_id = rmrk_nft_id.into();977978			let budget = budget::Value::new(NESTING_BUDGET);979980			let nft_owner =981				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)982					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;983984			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {985				ensure!(res.pending, <Error<T>>::ResourceNotPending);986				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);987988				res.pending = false;989990				Ok(())991			})?;992993			Self::deposit_event(Event::<T>::ResourceAccepted {994				nft_id: rmrk_nft_id,995				resource_id,996			});997998			Ok(())999		}10001001		/// Accept the removal of a removal-pending resource from an NFT.1002		///1003		/// This transaction is needed when a non-owner, i.e. the collection issuer,1004		/// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1005		///1006		/// # Permissions:1007		/// - Token owner1008		///1009		/// # Arguments:1010		/// - `origin`: sender of the transaction1011		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1012		/// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1013		/// - `resource_id`: ID of the removal-pending resource.1014		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1015		pub fn accept_resource_removal(1016			origin: OriginFor<T>,1017			rmrk_collection_id: RmrkCollectionId,1018			rmrk_nft_id: RmrkNftId,1019			resource_id: RmrkResourceId,1020		) -> DispatchResult {1021			let sender = ensure_signed(origin)?;1022			let cross_sender = T::CrossAccountId::from_sub(sender);10231024			let collection_id = Self::unique_collection_id(rmrk_collection_id)1025				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;1026			let collection =1027				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1028			collection.check_is_external()?;10291030			let nft_id = rmrk_nft_id.into();10311032			let budget = budget::Value::new(NESTING_BUDGET);10331034			let nft_owner =1035				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1036					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;10371038			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10391040			let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10411042			let resource_info = <PalletNft<T>>::token_aux_property((1043				collection_id,1044				nft_id,1045				RMRK_SCOPE,1046				resource_id_key.clone(),1047			))1048			.ok_or(<Error<T>>::ResourceDoesntExist)?;10491050			let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10511052			ensure!(1053				resource_info.pending_removal,1054				<Error<T>>::ResourceNotPending1055			);10561057			<PalletNft<T>>::remove_token_aux_property(1058				collection_id,1059				nft_id,1060				RMRK_SCOPE,1061				resource_id_key,1062			);10631064			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1065				let base_id = resource.base;10661067				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1068			}10691070			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1071				nft_id: rmrk_nft_id,1072				resource_id,1073			});10741075			Ok(())1076		}10771078		/// Add or edit a custom user property, a key-value pair, describing the metadata1079		/// of a token or a collection, on either one of these.1080		///1081		/// Note that in this proxy implementation many details regarding RMRK are stored1082		/// as scoped properties prefixed with "rmrk:", normally inaccessible1083		/// to external transactions and RPCs.1084		///1085		/// # Permissions:1086		/// - Collection issuer - in case of collection property1087		/// - Token owner - in case of NFT property1088		///1089		/// # Arguments:1090		/// - `origin`: sender of the transaction1091		/// - `rmrk_collection_id`: RMRK collection ID.1092		/// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1093		/// - `key`: Key of the custom property to be referenced by.1094		/// - `value`: Value of the custom property to be stored.1095		#[pallet::weight(<SelfWeightOf<T>>::set_property())]1096		pub fn set_property(1097			origin: OriginFor<T>,1098			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,1099			maybe_nft_id: Option<RmrkNftId>,1100			key: RmrkKeyString,1101			value: RmrkValueString,1102		) -> DispatchResult {1103			let sender = ensure_signed(origin)?;1104			let sender = T::CrossAccountId::from_sub(sender);11051106			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1107			let collection =1108				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1109			collection.check_is_external()?;11101111			let budget = budget::Value::new(NESTING_BUDGET);11121113			match maybe_nft_id {1114				Some(nft_id) => {1115					let token_id: TokenId = nft_id.into();11161117					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1118					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11191120					<PalletNft<T>>::set_scoped_token_property(1121						collection_id,1122						token_id,1123						RMRK_SCOPE,1124						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1125					)?;1126				}1127				None => {1128					let collection = Self::get_typed_nft_collection(1129						collection_id,1130						misc::CollectionType::Regular,1131					)?;11321133					Self::check_collection_owner(&collection, &sender)?;11341135					<PalletCommon<T>>::set_scoped_collection_property(1136						collection_id,1137						RMRK_SCOPE,1138						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1139					)?;1140				}1141			}11421143			Self::deposit_event(Event::PropertySet {1144				collection_id: rmrk_collection_id,1145				maybe_nft_id,1146				key,1147				value,1148			});11491150			Ok(())1151		}11521153		/// Set a different order of resource priorities for an NFT. Priorities can be used,1154		/// for example, for order of rendering.1155		///1156		/// Note that the priorities are not updated automatically, and are an empty vector1157		/// by default. There is no pre-set definition for the order to be particular,1158		/// it can be interpreted arbitrarily use-case by use-case.1159		///1160		/// # Permissions:1161		/// - Token owner1162		///1163		/// # Arguments:1164		/// - `origin`: sender of the transaction1165		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1166		/// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1167		/// - `priorities`: Ordered vector of resource IDs.1168		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]1169		pub fn set_priority(1170			origin: OriginFor<T>,1171			rmrk_collection_id: RmrkCollectionId,1172			rmrk_nft_id: RmrkNftId,1173			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1174		) -> DispatchResult {1175			let sender = ensure_signed(origin)?;1176			let sender = T::CrossAccountId::from_sub(sender);11771178			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1179			let nft_id = rmrk_nft_id.into();11801181			let collection =1182				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1183			collection.check_is_external()?;11841185			let budget = budget::Value::new(NESTING_BUDGET);11861187			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1188			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11891190			<PalletNft<T>>::set_scoped_token_property(1191				collection_id,1192				nft_id,1193				RMRK_SCOPE,1194				Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1195			)?;11961197			Self::deposit_event(Event::<T>::PrioritySet {1198				collection_id: rmrk_collection_id,1199				nft_id: rmrk_nft_id,1200			});12011202			Ok(())1203		}12041205		/// Create and set/propose a basic resource for an NFT.1206		///1207		/// A basic resource is the simplest, lacking a Base and anything that comes with it.1208		/// See RMRK docs for more information and examples.1209		///1210		/// # Permissions:1211		/// - Collection issuer - if not the token owner, adding the resource will warrant1212		/// the owner's [acceptance](Pallet::accept_resource).1213		///1214		/// # Arguments:1215		/// - `origin`: sender of the transaction1216		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1217		/// - `nft_id`: ID of the NFT to assign a resource to.1218		/// - `resource`: Data of the resource to be created.1219		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1220		pub fn add_basic_resource(1221			origin: OriginFor<T>,1222			rmrk_collection_id: RmrkCollectionId,1223			nft_id: RmrkNftId,1224			resource: RmrkBasicResource,1225		) -> DispatchResult {1226			let sender = ensure_signed(origin.clone())?;12271228			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1229			let collection =1230				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1231			collection.check_is_external()?;12321233			let resource_id = Self::resource_add(1234				sender,1235				collection_id,1236				nft_id.into(),1237				RmrkResourceTypes::Basic(resource),1238			)?;12391240			Self::deposit_event(Event::ResourceAdded {1241				nft_id,1242				resource_id,1243			});1244			Ok(())1245		}12461247		/// Create and set/propose a composable resource for an NFT.1248		///1249		/// A composable resource links to a Base and has a subset of its Parts it is composed of.1250		/// See RMRK docs for more information and examples.1251		///1252		/// # Permissions:1253		/// - Collection issuer - if not the token owner, adding the resource will warrant1254		/// the owner's [acceptance](Pallet::accept_resource).1255		///1256		/// # Arguments:1257		/// - `origin`: sender of the transaction1258		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1259		/// - `nft_id`: ID of the NFT to assign a resource to.1260		/// - `resource`: Data of the resource to be created.1261		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1262		pub fn add_composable_resource(1263			origin: OriginFor<T>,1264			rmrk_collection_id: RmrkCollectionId,1265			nft_id: RmrkNftId,1266			resource: RmrkComposableResource,1267		) -> DispatchResult {1268			let sender = ensure_signed(origin.clone())?;12691270			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1271			let collection =1272				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1273			collection.check_is_external()?;12741275			let base_id = resource.base;12761277			let resource_id = Self::resource_add(1278				sender,1279				collection_id,1280				nft_id.into(),1281				RmrkResourceTypes::Composable(resource),1282			)?;12831284			<PalletNft<T>>::try_mutate_token_aux_property(1285				collection_id,1286				nft_id.into(),1287				RMRK_SCOPE,1288				Self::get_scoped_property_key(AssociatedBases)?,1289				|value| -> DispatchResult {1290					let mut bases: BasesMap = match value {1291						Some(value) => Self::decode_property_value(value)?,1292						None => BasesMap::new(),1293					};12941295					*bases.entry(base_id).or_insert(0) += 1;12961297					*value = Some(Self::encode_property_value(&bases)?);1298					Ok(())1299				},1300			)?;13011302			Self::deposit_event(Event::ResourceAdded {1303				nft_id,1304				resource_id,1305			});1306			Ok(())1307		}13081309		/// Create and set/propose a slot resource for an NFT.1310		///1311		/// A slot resource links to a Base and a slot ID in it which it can fit into.1312		/// See RMRK docs for more information and examples.1313		///1314		/// # Permissions:1315		/// - Collection issuer - if not the token owner, adding the resource will warrant1316		/// the owner's [acceptance](Pallet::accept_resource).1317		///1318		/// # Arguments:1319		/// - `origin`: sender of the transaction1320		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1321		/// - `nft_id`: ID of the NFT to assign a resource to.1322		/// - `resource`: Data of the resource to be created.1323		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1324		pub fn add_slot_resource(1325			origin: OriginFor<T>,1326			rmrk_collection_id: RmrkCollectionId,1327			nft_id: RmrkNftId,1328			resource: RmrkSlotResource,1329		) -> DispatchResult {1330			let sender = ensure_signed(origin.clone())?;13311332			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1333			let collection =1334				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1335			collection.check_is_external()?;13361337			let resource_id = Self::resource_add(1338				sender,1339				collection_id,1340				nft_id.into(),1341				RmrkResourceTypes::Slot(resource),1342			)?;13431344			Self::deposit_event(Event::ResourceAdded {1345				nft_id,1346				resource_id,1347			});1348			Ok(())1349		}13501351		/// Remove and erase a resource from an NFT.1352		///1353		/// If the sender does not own the NFT, then it will be pending confirmation,1354		/// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1355		///1356		/// # Permissions1357		/// - Collection issuer1358		///1359		/// # Arguments1360		/// - `origin`: sender of the transaction1361		/// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1362		/// - `nft_id`: ID of the NFT with a resource to be removed.1363		/// - `resource_id`: ID of the resource to be removed.1364		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1365		pub fn remove_resource(1366			origin: OriginFor<T>,1367			rmrk_collection_id: RmrkCollectionId,1368			nft_id: RmrkNftId,1369			resource_id: RmrkResourceId,1370		) -> DispatchResult {1371			let sender = ensure_signed(origin.clone())?;13721373			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1374			let collection =1375				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1376			collection.check_is_external()?;13771378			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13791380			Self::deposit_event(Event::ResourceRemoval {1381				nft_id,1382				resource_id,1383			});1384			Ok(())1385		}1386	}1387}13881389impl<T: Config> Pallet<T> {1390	/// Transform one of possible RMRK keys into a byte key with a RMRK scope.1391	pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1392		let key = rmrk_key.to_key::<T>()?;13931394		let scoped_key = RMRK_SCOPE1395			.apply(key)1396			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13971398		Ok(scoped_key)1399	}14001401	/// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1402	/// and encoding the value from an arbitrary type into bytes.1403	pub fn encode_rmrk_property<E: Encode>(1404		rmrk_key: RmrkProperty,1405		value: &E,1406	) -> Result<Property, DispatchError> {1407		let key = rmrk_key.to_key::<T>()?;14081409		let value = Self::encode_property_value(value)?;14101411		let property = Property { key, value };14121413		Ok(property)1414	}14151416	/// Encode property value from an arbitrary type into bytes for storage.1417	pub fn encode_property_value<E: Encode, S: Get<u32>>(1418		value: &E,1419	) -> Result<BoundedBytes<S>, DispatchError> {1420		let value = value1421			.encode()1422			.try_into()1423			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14241425		Ok(value)1426	}14271428	/// Decode property value from bytes into an arbitrary type.1429	pub fn decode_property_value<D: Decode, S: Get<u32>>(1430		vec: &BoundedBytes<S>,1431	) -> Result<D, DispatchError> {1432		vec.decode()1433			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1434	}14351436	/// Change the limit of a property value byte vector.1437	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1438	where1439		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1440	{1441		vec.rebind()1442			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1443	}14441445	/// Initialize a new NFT collection with certain RMRK-scoped properties.1446	///1447	/// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1448	fn init_collection(1449		sender: T::CrossAccountId,1450		data: CreateCollectionData<T::AccountId>,1451		properties: impl Iterator<Item = Property>,1452	) -> Result<CollectionId, DispatchError> {1453		let collection_id = <PalletNft<T>>::init_collection(1454			sender.clone(),1455			sender,1456			data,1457			up_data_structs::CollectionFlags {1458				external: true,1459				..Default::default()1460			},1461		);14621463		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1464			return Err(<Error<T>>::NoAvailableCollectionId.into());1465		}14661467		<PalletCommon<T>>::set_scoped_collection_properties(1468			collection_id?,1469			RMRK_SCOPE,1470			properties,1471		)?;14721473		collection_id1474	}14751476	/// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1477	///1478	/// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1479	pub fn create_nft(1480		sender: &T::CrossAccountId,1481		owner: &T::CrossAccountId,1482		collection: &NonfungibleHandle<T>,1483		properties: impl Iterator<Item = Property>,1484	) -> Result<TokenId, DispatchError> {1485		let data = CreateNftExData {1486			properties: BoundedVec::default(),1487			owner: owner.clone(),1488		};14891490		let budget = budget::Value::new(NESTING_BUDGET);14911492		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;14931494		let nft_id = <PalletNft<T>>::current_token_id(collection.id);14951496		<PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14971498		Ok(nft_id)1499	}15001501	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1502	///1503	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1504	fn destroy_nft(1505		sender: T::CrossAccountId,1506		collection_id: CollectionId,1507		token_id: TokenId,1508		max_burns: u32,1509		error_if_not_owned: Error<T>,1510	) -> DispatchResultWithPostInfo {1511		let collection =1512			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15131514		let token_data =1515			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15161517		let from = token_data.owner;15181519		let owner_check_budget = budget::Value::new(NESTING_BUDGET);15201521		ensure!(1522			<PalletStructure<T>>::check_indirectly_owned(1523				sender.clone(),1524				collection_id,1525				token_id,1526				None,1527				&owner_check_budget1528			)?,1529			error_if_not_owned,1530		);15311532		let burns_budget = budget::Value::new(max_burns);1533		let breadth_budget = budget::Value::new(max_burns);15341535		<PalletNft<T>>::burn_recursively(1536			&collection,1537			&from,1538			token_id,1539			&burns_budget,1540			&breadth_budget,1541		)1542	}15431544	/// Add a sent token pending acceptance to the target owning token as a property.1545	fn insert_pending_child(1546		target: (CollectionId, TokenId),1547		child: (RmrkCollectionId, RmrkNftId),1548	) -> DispatchResult {1549		Self::mutate_pending_children(target, |pending_children| {1550			pending_children.insert(child);1551		})1552	}15531554	/// Remove a sent token pending acceptance from the target token's properties.1555	fn remove_pending_child(1556		target: (CollectionId, TokenId),1557		child: (RmrkCollectionId, RmrkNftId),1558	) -> DispatchResult {1559		Self::mutate_pending_children(target, |pending_children| {1560			pending_children.remove(&child);1561		})1562	}15631564	/// Apply a mutation to the property of a token containing sent tokens1565	/// that are currently pending acceptance.1566	fn mutate_pending_children(1567		(target_collection_id, target_nft_id): (CollectionId, TokenId),1568		f: impl FnOnce(&mut PendingChildrenSet),1569	) -> DispatchResult {1570		<PalletNft<T>>::try_mutate_token_aux_property(1571			target_collection_id,1572			target_nft_id,1573			RMRK_SCOPE,1574			Self::get_scoped_property_key(PendingChildren)?,1575			|pending_children| -> DispatchResult {1576				let mut map = match pending_children {1577					Some(map) => Self::decode_property_value(map)?,1578					None => PendingChildrenSet::new(),1579				};15801581				f(&mut map);15821583				*pending_children = Some(Self::encode_property_value(&map)?);15841585				Ok(())1586			},1587		)1588	}15891590	/// Get an iterator from a token's property containing tokens sent to it1591	/// that are currently pending acceptance.1592	fn iterate_pending_children(1593		collection_id: CollectionId,1594		nft_id: TokenId,1595	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1596		let property = <PalletNft<T>>::token_aux_property((1597			collection_id,1598			nft_id,1599			RMRK_SCOPE,1600			Self::get_scoped_property_key(PendingChildren)?,1601		));16021603		let pending_children = match property {1604			Some(map) => Self::decode_property_value(&map)?,1605			None => PendingChildrenSet::new(),1606		};16071608		Ok(pending_children.into_iter())1609	}16101611	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1612	/// Thus, the returned resource ID should be used.1613	///1614	/// Resource IDs are unique only across an NFT.1615	fn acquire_next_resource_id(1616		collection_id: CollectionId,1617		nft_id: TokenId,1618	) -> Result<RmrkResourceId, DispatchError> {1619		let resource_id: RmrkResourceId =1620			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16211622		let next_id = resource_id1623			.checked_add(1)1624			.ok_or(<Error<T>>::NoAvailableResourceId)?;16251626		<PalletNft<T>>::set_scoped_token_property(1627			collection_id,1628			nft_id,1629			RMRK_SCOPE,1630			Self::encode_rmrk_property(NextResourceId, &next_id)?,1631		)?;16321633		Ok(resource_id)1634	}16351636	/// Create and add a resource for a regular NFT, mark it as pending if the sender1637	/// is not the token owner. The sender must be the collection owner.1638	fn resource_add(1639		sender: T::AccountId,1640		collection_id: CollectionId,1641		nft_id: TokenId,1642		resource: RmrkResourceTypes,1643	) -> Result<RmrkResourceId, DispatchError> {1644		let collection =1645			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1646		ensure!(collection.owner == sender, Error::<T>::NoPermission);16471648		let sender = T::CrossAccountId::from_sub(sender);1649		let budget = budget::Value::new(NESTING_BUDGET);16501651		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1652			.map_err(Self::map_unique_err_to_proxy)?;16531654		let pending = sender != nft_owner;16551656		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16571658		let resource_info = RmrkResourceInfo {1659			id,1660			resource,1661			pending,1662			pending_removal: false,1663		};16641665		<PalletNft<T>>::try_mutate_token_aux_property(1666			collection_id,1667			nft_id,1668			RMRK_SCOPE,1669			Self::get_scoped_property_key(ResourceId(id))?,1670			|value| -> DispatchResult {1671				*value = Some(Self::encode_property_value(&resource_info)?);16721673				Ok(())1674			},1675		)?;16761677		Ok(id)1678	}16791680	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1681	/// The sender must be the collection owner.1682	fn resource_remove(1683		sender: T::AccountId,1684		collection_id: CollectionId,1685		nft_id: TokenId,1686		resource_id: RmrkResourceId,1687	) -> DispatchResult {1688		let collection =1689			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1690		ensure!(collection.owner == sender, Error::<T>::NoPermission);16911692		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16931694		let resource = <PalletNft<T>>::token_aux_property((1695			collection_id,1696			nft_id,1697			RMRK_SCOPE,1698			resource_id_key.clone(),1699		))1700		.ok_or(<Error<T>>::ResourceDoesntExist)?;17011702		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17031704		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1705		let topmost_owner =1706			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17071708		let sender = T::CrossAccountId::from_sub(sender);1709		if topmost_owner == sender {1710			<PalletNft<T>>::remove_token_aux_property(1711				collection_id,1712				nft_id,1713				RMRK_SCOPE,1714				Self::get_scoped_property_key(ResourceId(resource_id))?,1715			);17161717			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1718				let base_id = resource.base;17191720				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1721			}1722		} else {1723			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1724				res.pending_removal = true;17251726				Ok(())1727			})?;1728		}17291730		Ok(())1731	}17321733	/// Remove a Base ID from an NFT if they are associated.1734	/// The Base itself is deleted if the number of associated NFTs reaches 0.1735	fn remove_associated_base_id(1736		collection_id: CollectionId,1737		nft_id: TokenId,1738		base_id: RmrkBaseId,1739	) -> DispatchResult {1740		<PalletNft<T>>::try_mutate_token_aux_property(1741			collection_id,1742			nft_id,1743			RMRK_SCOPE,1744			Self::get_scoped_property_key(AssociatedBases)?,1745			|value| -> DispatchResult {1746				let mut bases: BasesMap = match value {1747					Some(value) => Self::decode_property_value(value)?,1748					None => BasesMap::new(),1749				};17501751				let remaining = bases.get(&base_id);17521753				if let Some(remaining) = remaining {1754					if let Some(0) | None = remaining.checked_sub(1) {1755						bases.remove(&base_id);1756					}1757				}17581759				*value = Some(Self::encode_property_value(&bases)?);1760				Ok(())1761			},1762		)1763	}17641765	/// Apply a mutation to a resource stored in the token properties of an NFT.1766	fn try_mutate_resource_info(1767		collection_id: CollectionId,1768		nft_id: TokenId,1769		resource_id: RmrkResourceId,1770		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1771	) -> DispatchResult {1772		<PalletNft<T>>::try_mutate_token_aux_property(1773			collection_id,1774			nft_id,1775			RMRK_SCOPE,1776			Self::get_scoped_property_key(ResourceId(resource_id))?,1777			|value| match value {1778				Some(value) => {1779					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17801781					f(&mut resource_info)?;17821783					*value = Self::encode_property_value(&resource_info)?;17841785					Ok(())1786				}1787				None => Err(<Error<T>>::ResourceDoesntExist.into()),1788			},1789		)1790	}17911792	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1793	fn change_collection_owner(1794		collection_id: CollectionId,1795		collection_type: misc::CollectionType,1796		sender: T::AccountId,1797		new_owner: T::AccountId,1798	) -> DispatchResult {1799		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1800		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18011802		let mut collection = collection.into_inner();18031804		collection.owner = new_owner;1805		collection.save()1806	}18071808	/// Ensure that an account is the collection owner/issuer, return an error if not.1809	pub fn check_collection_owner(1810		collection: &NonfungibleHandle<T>,1811		account: &T::CrossAccountId,1812	) -> DispatchResult {1813		collection1814			.check_is_owner(account)1815			.map_err(Self::map_unique_err_to_proxy)1816	}18171818	/// Get the latest yet-unused RMRK collection index from the storage.1819	pub fn last_collection_idx() -> RmrkCollectionId {1820		<CollectionIndex<T>>::get()1821	}18221823	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1824	pub fn unique_collection_id(1825		rmrk_collection_id: RmrkCollectionId,1826	) -> Result<CollectionId, DispatchError> {1827		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1828			.map_err(|_| <Error<T>>::CollectionUnknown.into())1829	}18301831	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1832	pub fn rmrk_collection_id(1833		unique_collection_id: CollectionId,1834	) -> Result<RmrkCollectionId, DispatchError> {1835		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1836	}18371838	/// Fetch a Unique NFT collection.1839	pub fn get_nft_collection(1840		collection_id: CollectionId,1841	) -> Result<NonfungibleHandle<T>, DispatchError> {1842		let collection = <CollectionHandle<T>>::try_get(collection_id)1843			.map_err(|_| <Error<T>>::CollectionUnknown)?;18441845		match collection.mode {1846			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1847			_ => Err(<Error<T>>::CollectionUnknown.into()),1848		}1849	}18501851	/// Check if an NFT collection with such an ID exists.1852	pub fn collection_exists(collection_id: CollectionId) -> bool {1853		<CollectionHandle<T>>::try_get(collection_id).is_ok()1854	}18551856	/// Fetch and decode a RMRK-scoped collection property value in bytes.1857	pub fn get_collection_property(1858		collection_id: CollectionId,1859		key: RmrkProperty,1860	) -> Result<PropertyValue, DispatchError> {1861		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1862			.get(&Self::get_scoped_property_key(key)?)1863			.ok_or(<Error<T>>::CollectionUnknown)?1864			.clone();18651866		Ok(collection_property)1867	}18681869	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1870	pub fn get_collection_property_decoded<V: Decode>(1871		collection_id: CollectionId,1872		key: RmrkProperty,1873	) -> Result<V, DispatchError> {1874		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1875	}18761877	/// Get the type of a collection stored as a scoped property.1878	///1879	/// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1880	pub fn get_collection_type(1881		collection_id: CollectionId,1882	) -> Result<misc::CollectionType, DispatchError> {1883		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1884			if err != <Error<T>>::CollectionUnknown.into() {1885				<Error<T>>::CorruptedCollectionType.into()1886			} else {1887				err1888			}1889		})1890	}18911892	/// Ensure that the type of the collection equals the provided type,1893	/// otherwise return an error.1894	pub fn ensure_collection_type(1895		collection_id: CollectionId,1896		collection_type: misc::CollectionType,1897	) -> DispatchResult {1898		let actual_type = Self::get_collection_type(collection_id)?;1899		ensure!(1900			actual_type == collection_type,1901			<CommonError<T>>::NoPermission1902		);19031904		Ok(())1905	}19061907	/// Fetch an NFT collection, but make sure it has the appropriate type.1908	pub fn get_typed_nft_collection(1909		collection_id: CollectionId,1910		collection_type: misc::CollectionType,1911	) -> Result<NonfungibleHandle<T>, DispatchError> {1912		Self::ensure_collection_type(collection_id, collection_type)?;19131914		Self::get_nft_collection(collection_id)1915	}19161917	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1918	/// but also return the Unique collection ID.1919	pub fn get_typed_nft_collection_mapped(1920		rmrk_collection_id: RmrkCollectionId,1921		collection_type: misc::CollectionType,1922	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1923		let unique_collection_id = match collection_type {1924			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1925			_ => rmrk_collection_id.into(),1926		};19271928		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19291930		Ok((collection, unique_collection_id))1931	}19321933	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1934	pub fn get_nft_property(1935		collection_id: CollectionId,1936		nft_id: TokenId,1937		key: RmrkProperty,1938	) -> Result<PropertyValue, DispatchError> {1939		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1940			.get(&Self::get_scoped_property_key(key)?)1941			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1942			.clone();19431944		Ok(nft_property)1945	}19461947	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1948	pub fn get_nft_property_decoded<V: Decode>(1949		collection_id: CollectionId,1950		nft_id: TokenId,1951		key: RmrkProperty,1952	) -> Result<V, DispatchError> {1953		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1954	}19551956	/// Check that an NFT exists.1957	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1958		<TokenData<T>>::contains_key((collection_id, nft_id))1959	}19601961	/// Get the type of an NFT stored as a scoped property.1962	///1963	/// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1964	pub fn get_nft_type(1965		collection_id: CollectionId,1966		token_id: TokenId,1967	) -> Result<NftType, DispatchError> {1968		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1969			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1970	}19711972	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1973	pub fn ensure_nft_type(1974		collection_id: CollectionId,1975		token_id: TokenId,1976		nft_type: NftType,1977	) -> DispatchResult {1978		let actual_type = Self::get_nft_type(collection_id, token_id)?;1979		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19801981		Ok(())1982	}19831984	/// Ensure that an account is the owner of the token, either directly1985	/// or at the top of the nesting hierarchy; return an error if it is not.1986	pub fn ensure_nft_owner(1987		collection_id: CollectionId,1988		token_id: TokenId,1989		possible_owner: &T::CrossAccountId,1990		nesting_budget: &dyn budget::Budget,1991	) -> DispatchResult {1992		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1993			possible_owner.clone(),1994			collection_id,1995			token_id,1996			None,1997			nesting_budget,1998		)1999		.map_err(Self::map_unique_err_to_proxy)?;20002001		ensure!(is_owned, <Error<T>>::NoPermission);20022003		Ok(())2004	}20052006	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,2007	/// or, if None are provided, return all non-scoped properties.2008	pub fn filter_user_properties<Key, Value, R, Mapper>(2009		collection_id: CollectionId,2010		token_id: Option<TokenId>,2011		filter_keys: Option<Vec<RmrkPropertyKey>>,2012		mapper: Mapper,2013	) -> Result<Vec<R>, DispatchError>2014	where2015		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2016		Value: Decode + Default,2017		Mapper: Fn(Key, Value) -> R,2018	{2019		filter_keys2020			.map(|keys| {2021				let properties = keys2022					.into_iter()2023					.filter_map(|key| {2024						let key: Key = key.try_into().ok()?;20252026						let value = match token_id {2027							Some(token_id) => Self::get_nft_property_decoded(2028								collection_id,2029								token_id,2030								UserProperty(key.as_ref()),2031							),2032							None => Self::get_collection_property_decoded(2033								collection_id,2034								UserProperty(key.as_ref()),2035							),2036						}2037						.ok()?;20382039						Some(mapper(key, value))2040					})2041					.collect();20422043				Ok(properties)2044			})2045			.unwrap_or_else(|| {2046				let properties =2047					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20482049				Ok(properties)2050			})2051	}20522053	/// Get all non-scoped properties from a collection or a token, and apply some transformation,2054	/// supplied by `mapper`, to each key-value pair.2055	pub fn iterate_user_properties<Key, Value, R, Mapper>(2056		collection_id: CollectionId,2057		token_id: Option<TokenId>,2058		mapper: Mapper,2059	) -> Result<impl Iterator<Item = R>, DispatchError>2060	where2061		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2062		Value: Decode + Default,2063		Mapper: Fn(Key, Value) -> R,2064	{2065		let properties = match token_id {2066			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2067			None => <PalletCommon<T>>::collection_properties(collection_id),2068		};20692070		let properties = properties.into_iter().filter_map(move |(key, value)| {2071			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20722073			let key: Key = key.to_vec().try_into().ok()?;2074			let value: Value = value.decode().ok()?;20752076			Some(mapper(key, value))2077		});20782079		Ok(properties)2080	}20812082	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2083	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2084		map_unique_err_to_proxy! {2085			match err {2086				CommonError::NoPermission => NoPermission,2087				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2088				CommonError::PublicMintingNotAllowed => NoPermission,2089				CommonError::TokenNotFound => NoAvailableNftId,2090				CommonError::ApprovedValueTooLow => NoPermission,2091				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2092				StructureError::TokenNotFound => NoAvailableNftId,2093				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2094			}2095		}2096	}2097}