git.delta.rocks / unique-network / refs/commits / 24d3f67bfdff

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs66.4 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::call_index(0)]361		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]362		pub fn create_collection(363			origin: OriginFor<T>,364			metadata: RmrkString,365			max: Option<u32>,366			symbol: RmrkCollectionSymbol,367		) -> DispatchResult {368			let sender = ensure_signed(origin)?;369370			let limits = CollectionLimits {371				owner_can_transfer: Some(false),372				token_limit: max,373				..Default::default()374			};375376			let data = CreateCollectionData {377				limits: Some(limits),378				token_prefix: symbol379					.into_inner()380					.try_into()381					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,382				permissions: Some(CollectionPermissions {383					nesting: Some(NestingPermissions {384						token_owner: true,385						collection_admin: false,386						restricted: None,387						#[cfg(feature = "runtime-benchmarks")]388						permissive: false,389					}),390					..Default::default()391				}),392				..Default::default()393			};394395			let unique_collection_id = Self::init_collection(396				T::CrossAccountId::from_sub(sender.clone()),397				data,398				[399					Self::encode_rmrk_property(Metadata, &metadata)?,400					Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,401				]402				.into_iter(),403			)?;404			let rmrk_collection_id = <CollectionIndex<T>>::get();405406			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);407408			<PalletCommon<T>>::set_scoped_collection_property(409				unique_collection_id,410				RMRK_SCOPE,411				Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,412			)?;413414			<CollectionIndex<T>>::mutate(|n| *n += 1);415416			Self::deposit_event(Event::CollectionCreated {417				issuer: sender,418				collection_id: rmrk_collection_id,419			});420421			Ok(())422		}423424		/// Destroy a collection.425		///426		/// Only empty collections can be destroyed. If it has any tokens, they must be burned first.427		///428		/// # Permissions:429		/// * Collection issuer430		///431		/// # Arguments:432		/// - `origin`: sender of the transaction433		/// - `collection_id`: RMRK ID of the collection to destroy.434		#[pallet::call_index(1)]435		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]436		pub fn destroy_collection(437			origin: OriginFor<T>,438			collection_id: RmrkCollectionId,439		) -> DispatchResult {440			let sender = ensure_signed(origin)?;441			let cross_sender = T::CrossAccountId::from_sub(sender.clone());442443			let collection = Self::get_typed_nft_collection(444				Self::unique_collection_id(collection_id)?,445				misc::CollectionType::Regular,446			)?;447			collection.check_is_external()?;448449			<PalletNft<T>>::destroy_collection(collection, &cross_sender)450				.map_err(Self::map_unique_err_to_proxy)?;451452			Self::deposit_event(Event::CollectionDestroyed {453				issuer: sender,454				collection_id,455			});456457			Ok(())458		}459460		/// Change the issuer of a collection. Analogous to Unique's collection's [`owner`](up_data_structs::Collection).461		///462		/// # Permissions:463		/// * Collection issuer464		///465		/// # Arguments:466		/// - `origin`: sender of the transaction467		/// - `collection_id`: RMRK collection ID to change the issuer of.468		/// - `new_issuer`: Collection's new issuer.469		#[pallet::call_index(2)]470		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]471		pub fn change_collection_issuer(472			origin: OriginFor<T>,473			collection_id: RmrkCollectionId,474			new_issuer: <T::Lookup as StaticLookup>::Source,475		) -> DispatchResult {476			let sender = ensure_signed(origin)?;477478			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;479			collection.check_is_external()?;480481			let new_issuer = T::Lookup::lookup(new_issuer)?;482483			Self::change_collection_owner(484				Self::unique_collection_id(collection_id)?,485				misc::CollectionType::Regular,486				sender.clone(),487				new_issuer.clone(),488			)?;489490			Self::deposit_event(Event::IssuerChanged {491				old_issuer: sender,492				new_issuer,493				collection_id,494			});495496			Ok(())497		}498499		/// "Lock" the collection and prevent new token creation. Cannot be undone.500		///501		/// # Permissions:502		/// * Collection issuer503		///504		/// # Arguments:505		/// - `origin`: sender of the transaction506		/// - `collection_id`: RMRK ID of the collection to lock.507		#[pallet::call_index(3)]508		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]509		pub fn lock_collection(510			origin: OriginFor<T>,511			collection_id: RmrkCollectionId,512		) -> DispatchResult {513			let sender = ensure_signed(origin)?;514			let cross_sender = T::CrossAccountId::from_sub(sender.clone());515516			let collection = Self::get_typed_nft_collection(517				Self::unique_collection_id(collection_id)?,518				misc::CollectionType::Regular,519			)?;520			collection.check_is_external()?;521522			Self::check_collection_owner(&collection, &cross_sender)?;523524			let token_count = collection.total_supply();525526			let mut collection = collection.into_inner();527			collection.limits.token_limit = Some(token_count);528			collection.save()?;529530			Self::deposit_event(Event::CollectionLocked {531				issuer: sender,532				collection_id,533			});534535			Ok(())536		}537538		/// Mint an NFT in a specified collection.539		///540		/// # Permissions:541		/// * Collection issuer542		///543		/// # Arguments:544		/// - `origin`: sender of the transaction545		/// - `owner`: Owner account of the NFT. If set to None, defaults to the sender (collection issuer).546		/// - `collection_id`: RMRK collection ID for the NFT to be minted within. Cannot be changed.547		/// - `recipient`: Receiver account of the royalty. Has no effect if the `royalty_amount` is not set. Cannot be changed.548		/// - `royalty_amount`: Optional permillage reward from each trade for the `recipient`. Cannot be changed.549		/// - `metadata`: Arbitrary data about an NFT, e.g. IPFS hash. Cannot be changed.550		/// - `transferable`: Can this NFT be transferred? Cannot be changed.551		/// - `resources`: Resource data to be added to the NFT immediately after minting.552		#[pallet::call_index(4)]553		#[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]554		pub fn mint_nft(555			origin: OriginFor<T>,556			owner: Option<T::AccountId>,557			collection_id: RmrkCollectionId,558			recipient: Option<T::AccountId>,559			royalty_amount: Option<Permill>,560			metadata: RmrkString,561			transferable: bool,562			resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,563		) -> DispatchResult {564			let sender = ensure_signed(origin)?;565			let cross_sender = T::CrossAccountId::from_sub(sender.clone());566567			let owner = owner.unwrap_or(sender.clone());568			let cross_owner = T::CrossAccountId::from_sub(owner.clone());569570			let collection = Self::get_typed_nft_collection(571				Self::unique_collection_id(collection_id)?,572				misc::CollectionType::Regular,573			)?;574			collection.check_is_external()?;575576			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {577				recipient: recipient.unwrap_or_else(|| owner.clone()),578				amount,579			});580581			let nft_id = Self::create_nft(582				&cross_sender,583				&cross_owner,584				&collection,585				[586					Self::encode_rmrk_property(TokenType, &NftType::Regular)?,587					Self::encode_rmrk_property(Transferable, &transferable)?,588					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,589					Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,590					Self::encode_rmrk_property(Metadata, &metadata)?,591					Self::encode_rmrk_property(Equipped, &false)?,592					Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,593					Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,594					Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,595					Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,596				]597				.into_iter(),598			)599			.map_err(|err| match err {600				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),601				err => Self::map_unique_err_to_proxy(err),602			})?;603604			if let Some(resources) = resources {605				for resource in resources {606					Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;607				}608			}609610			Self::deposit_event(Event::NftMinted {611				owner,612				collection_id,613				nft_id: nft_id.0,614			});615616			Ok(())617		}618619		/// Burn an NFT, destroying it and its nested tokens up to the specified limit.620		/// If the burning budget is exceeded, the transaction is reverted.621		///622		/// This is the way to burn a nested token as well.623		///624		/// For more information, see [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively).625		///626		/// # Permissions:627		/// * Token owner628		///629		/// # Arguments:630		/// - `origin`: sender of the transaction631		/// - `collection_id`: RMRK ID of the collection in which the NFT to burn belongs to.632		/// - `nft_id`: ID of the NFT to be destroyed.633		/// - `max_burns`: Maximum number of tokens to burn, assuming nesting. The transaction634		/// is reverted if there are more tokens to burn in the nesting tree than this number.635		/// This is primarily a mechanism of transaction weight control.636		#[pallet::call_index(5)]637		#[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]638		pub fn burn_nft(639			origin: OriginFor<T>,640			collection_id: RmrkCollectionId,641			nft_id: RmrkNftId,642			max_burns: u32,643		) -> DispatchResult {644			let sender = ensure_signed(origin)?;645			let cross_sender = T::CrossAccountId::from_sub(sender.clone());646647			let collection = Self::get_typed_nft_collection(648				Self::unique_collection_id(collection_id)?,649				misc::CollectionType::Regular,650			)?;651			collection.check_is_external()?;652653			Self::destroy_nft(654				cross_sender,655				Self::unique_collection_id(collection_id)?,656				nft_id.into(),657				max_burns,658				<Error<T>>::NoPermission,659			)660			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;661662			Self::deposit_event(Event::NFTBurned {663				owner: sender,664				nft_id,665			});666667			Ok(())668		}669670		/// Transfer an NFT from an account/NFT A to another account/NFT B.671		/// The token must be transferable. Nesting cannot occur deeper than the [`NESTING_BUDGET`].672		///673		/// If the target owner is an NFT owned by another account, then the NFT will enter674		/// the pending state and will have to be accepted by the other account.675		///676		/// # Permissions:677		/// - Token owner678		///679		/// # Arguments:680		/// - `origin`: sender of the transaction681		/// - `rmrk_collection_id`: RMRK ID of the collection of the NFT to be transferred.682		/// - `rmrk_nft_id`: ID of the NFT to be transferred.683		/// - `new_owner`: New owner of the nft which can be either an account or a NFT.684		#[pallet::call_index(6)]685		#[pallet::weight(<SelfWeightOf<T>>::send())]686		pub fn send(687			origin: OriginFor<T>,688			rmrk_collection_id: RmrkCollectionId,689			rmrk_nft_id: RmrkNftId,690			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,691		) -> DispatchResult {692			let sender = ensure_signed(origin.clone())?;693			let cross_sender = T::CrossAccountId::from_sub(sender.clone());694695			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;696			let nft_id = rmrk_nft_id.into();697698			let collection =699				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;700			collection.check_is_external()?;701702			let token_data =703				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;704705			let from = token_data.owner;706707			ensure!(708				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,709				<Error<T>>::NonTransferable710			);711712			ensure!(713				Self::get_nft_property_decoded::<Option<PendingTarget>>(714					collection_id,715					nft_id,716					RmrkProperty::PendingNftAccept717				)?718				.is_none(),719				<Error<T>>::NoPermission720			);721722			let target_owner;723			let approval_required;724725			match new_owner {726				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {727					target_owner = T::CrossAccountId::from_sub(account_id.clone());728					approval_required = false;729				}730				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(731					target_collection_id,732					target_nft_id,733				) => {734					let target_collection_id = Self::unique_collection_id(target_collection_id)?;735736					let target_nft_budget = budget::Value::new(NESTING_BUDGET);737738					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(739						target_collection_id,740						target_nft_id.into(),741						Some((collection_id, nft_id)),742						&target_nft_budget,743					)744					.map_err(Self::map_unique_err_to_proxy)?745					.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;746747					approval_required = cross_sender != target_nft_owner;748749					if approval_required {750						target_owner = target_nft_owner;751752						<PalletNft<T>>::set_scoped_token_property(753							collection.id,754							nft_id,755							RMRK_SCOPE,756							Self::encode_rmrk_property::<Option<PendingTarget>>(757								PendingNftAccept,758								&Some((target_collection_id, target_nft_id.into())),759							)?,760						)?;761762						Self::insert_pending_child(763							(target_collection_id, target_nft_id.into()),764							(rmrk_collection_id, rmrk_nft_id),765						)?;766					} else {767						target_owner = T::CrossTokenAddressMapping::token_to_address(768							target_collection_id,769							target_nft_id.into(),770						);771					}772				}773			}774775			let src_nft_budget = budget::Value::new(NESTING_BUDGET);776777			<PalletNft<T>>::transfer_from(778				&collection,779				&cross_sender,780				&from,781				&target_owner,782				nft_id,783				&src_nft_budget,784			)785			.map_err(Self::map_unique_err_to_proxy)?;786787			Self::deposit_event(Event::NFTSent {788				sender,789				recipient: new_owner,790				collection_id: rmrk_collection_id,791				nft_id: rmrk_nft_id,792				approval_required,793			});794795			Ok(())796		}797798		/// Accept an NFT sent from another account to self or an owned NFT.799		///800		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.801		///802		/// # Permissions:803		/// - Token-owner-to-be804		///805		/// # Arguments:806		/// - `origin`: sender of the transaction807		/// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.808		/// - `rmrk_nft_id`: ID of the NFT to be accepted.809		/// - `new_owner`: Either the sender's account ID or a sender-owned NFT,810		/// whichever the accepted NFT was sent to.811		#[pallet::call_index(7)]812		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]813		pub fn accept_nft(814			origin: OriginFor<T>,815			rmrk_collection_id: RmrkCollectionId,816			rmrk_nft_id: RmrkNftId,817			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,818		) -> DispatchResult {819			let sender = ensure_signed(origin.clone())?;820			let cross_sender = T::CrossAccountId::from_sub(sender.clone());821822			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;823			let nft_id = rmrk_nft_id.into();824825			let collection =826				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;827			collection.check_is_external()?;828829			let new_cross_owner = match new_owner {830				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {831					T::CrossAccountId::from_sub(account_id.clone())832				}833				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(834					target_collection_id,835					target_nft_id,836				) => {837					let target_collection_id = Self::unique_collection_id(target_collection_id)?;838839					T::CrossTokenAddressMapping::token_to_address(840						target_collection_id,841						TokenId(target_nft_id),842					)843				}844			};845846			let budget = budget::Value::new(NESTING_BUDGET);847848			<PalletNft<T>>::transfer(849				&collection,850				&cross_sender,851				&new_cross_owner,852				nft_id,853				&budget,854			)855			.map_err(|err| {856				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {857					<Error<T>>::CannotAcceptNonOwnedNft.into()858				} else {859					Self::map_unique_err_to_proxy(err)860				}861			})?;862863			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(864				collection_id,865				nft_id,866				RmrkProperty::PendingNftAccept,867			)?;868869			if let Some(pending_target) = pending_target {870				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;871872				<PalletNft<T>>::set_scoped_token_property(873					collection.id,874					nft_id,875					RMRK_SCOPE,876					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,877				)?;878			}879880			Self::deposit_event(Event::NFTAccepted {881				sender,882				recipient: new_owner,883				collection_id: rmrk_collection_id,884				nft_id: rmrk_nft_id,885			});886887			Ok(())888		}889890		/// Reject an NFT sent from another account to self or owned NFT.891		/// The NFT in question will not be sent back and burnt instead.892		///893		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.894		///895		/// # Permissions:896		/// - Token-owner-to-be-not897		///898		/// # Arguments:899		/// - `origin`: sender of the transaction900		/// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.901		/// - `rmrk_nft_id`: ID of the NFT to be rejected.902		#[pallet::call_index(8)]903		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]904		pub fn reject_nft(905			origin: OriginFor<T>,906			rmrk_collection_id: RmrkCollectionId,907			rmrk_nft_id: RmrkNftId,908		) -> DispatchResult {909			let sender = ensure_signed(origin)?;910			let cross_sender = T::CrossAccountId::from_sub(sender.clone());911912			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;913			let nft_id = rmrk_nft_id.into();914915			let collection =916				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;917			collection.check_is_external()?;918919			ensure!(920				<TokenData<T>>::get((collection_id, nft_id)).is_some(),921				<Error<T>>::NoAvailableNftId922			);923924			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(925				collection_id,926				nft_id,927				RmrkProperty::PendingNftAccept,928			)?;929930			match pending_target {931				Some(pending_target) => {932					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?933				}934				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),935			}936937			Self::destroy_nft(938				cross_sender,939				collection_id,940				nft_id,941				NESTING_BUDGET,942				<Error<T>>::CannotRejectNonOwnedNft,943			)944			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;945946			Self::deposit_event(Event::NFTRejected {947				sender,948				collection_id: rmrk_collection_id,949				nft_id: rmrk_nft_id,950			});951952			Ok(())953		}954955		/// Accept the addition of a newly created pending resource to an existing NFT.956		///957		/// This transaction is needed when a resource is created and assigned to an NFT958		/// by a non-owner, i.e. the collection issuer, with one of the959		/// [`add_...` transactions](Pallet::add_basic_resource).960		///961		/// # Permissions:962		/// - Token owner963		///964		/// # Arguments:965		/// - `origin`: sender of the transaction966		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.967		/// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.968		/// - `resource_id`: ID of the newly created pending resource.969		/// accept the addition of a new resource to an existing NFT970		#[pallet::call_index(9)]971		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]972		pub fn accept_resource(973			origin: OriginFor<T>,974			rmrk_collection_id: RmrkCollectionId,975			rmrk_nft_id: RmrkNftId,976			resource_id: RmrkResourceId,977		) -> DispatchResult {978			let sender = ensure_signed(origin)?;979			let cross_sender = T::CrossAccountId::from_sub(sender);980981			let collection_id = Self::unique_collection_id(rmrk_collection_id)982				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;983			let collection =984				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;985			collection.check_is_external()?;986987			let nft_id = rmrk_nft_id.into();988989			let budget = budget::Value::new(NESTING_BUDGET);990991			let nft_owner =992				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)993					.map_err(|_| <Error<T>>::ResourceDoesntExist)?994					.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;995996			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {997				ensure!(res.pending, <Error<T>>::ResourceNotPending);998				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);9991000				res.pending = false;10011002				Ok(())1003			})?;10041005			Self::deposit_event(Event::<T>::ResourceAccepted {1006				nft_id: rmrk_nft_id,1007				resource_id,1008			});10091010			Ok(())1011		}10121013		/// Accept the removal of a removal-pending resource from an NFT.1014		///1015		/// This transaction is needed when a non-owner, i.e. the collection issuer,1016		/// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1017		///1018		/// # Permissions:1019		/// - Token owner1020		///1021		/// # Arguments:1022		/// - `origin`: sender of the transaction1023		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1024		/// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1025		/// - `resource_id`: ID of the removal-pending resource.1026		#[pallet::call_index(10)]1027		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1028		pub fn accept_resource_removal(1029			origin: OriginFor<T>,1030			rmrk_collection_id: RmrkCollectionId,1031			rmrk_nft_id: RmrkNftId,1032			resource_id: RmrkResourceId,1033		) -> DispatchResult {1034			let sender = ensure_signed(origin)?;1035			let cross_sender = T::CrossAccountId::from_sub(sender);10361037			let collection_id = Self::unique_collection_id(rmrk_collection_id)1038				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;1039			let collection =1040				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1041			collection.check_is_external()?;10421043			let nft_id = rmrk_nft_id.into();10441045			let budget = budget::Value::new(NESTING_BUDGET);10461047			let nft_owner =1048				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1049					.map_err(|_| <Error<T>>::ResourceDoesntExist)?1050					.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;10511052			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10531054			let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10551056			let resource_info = <PalletNft<T>>::token_aux_property((1057				collection_id,1058				nft_id,1059				RMRK_SCOPE,1060				resource_id_key.clone(),1061			))1062			.ok_or(<Error<T>>::ResourceDoesntExist)?;10631064			let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10651066			ensure!(1067				resource_info.pending_removal,1068				<Error<T>>::ResourceNotPending1069			);10701071			<PalletNft<T>>::remove_token_aux_property(1072				collection_id,1073				nft_id,1074				RMRK_SCOPE,1075				resource_id_key,1076			);10771078			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1079				let base_id = resource.base;10801081				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1082			}10831084			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1085				nft_id: rmrk_nft_id,1086				resource_id,1087			});10881089			Ok(())1090		}10911092		/// Add or edit a custom user property, a key-value pair, describing the metadata1093		/// of a token or a collection, on either one of these.1094		///1095		/// Note that in this proxy implementation many details regarding RMRK are stored1096		/// as scoped properties prefixed with "rmrk:", normally inaccessible1097		/// to external transactions and RPCs.1098		///1099		/// # Permissions:1100		/// - Collection issuer - in case of collection property1101		/// - Token owner - in case of NFT property1102		///1103		/// # Arguments:1104		/// - `origin`: sender of the transaction1105		/// - `rmrk_collection_id`: RMRK collection ID.1106		/// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1107		/// - `key`: Key of the custom property to be referenced by.1108		/// - `value`: Value of the custom property to be stored.1109		#[pallet::call_index(11)]1110		#[pallet::weight(<SelfWeightOf<T>>::set_property())]1111		pub fn set_property(1112			origin: OriginFor<T>,1113			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,1114			maybe_nft_id: Option<RmrkNftId>,1115			key: RmrkKeyString,1116			value: RmrkValueString,1117		) -> DispatchResult {1118			let sender = ensure_signed(origin)?;1119			let sender = T::CrossAccountId::from_sub(sender);11201121			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1122			let collection =1123				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1124			collection.check_is_external()?;11251126			let budget = budget::Value::new(NESTING_BUDGET);11271128			match maybe_nft_id {1129				Some(nft_id) => {1130					let token_id: TokenId = nft_id.into();11311132					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1133					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11341135					<PalletNft<T>>::set_scoped_token_property(1136						collection_id,1137						token_id,1138						RMRK_SCOPE,1139						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1140					)?;1141				}1142				None => {1143					let collection = Self::get_typed_nft_collection(1144						collection_id,1145						misc::CollectionType::Regular,1146					)?;11471148					Self::check_collection_owner(&collection, &sender)?;11491150					<PalletCommon<T>>::set_scoped_collection_property(1151						collection_id,1152						RMRK_SCOPE,1153						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1154					)?;1155				}1156			}11571158			Self::deposit_event(Event::PropertySet {1159				collection_id: rmrk_collection_id,1160				maybe_nft_id,1161				key,1162				value,1163			});11641165			Ok(())1166		}11671168		/// Set a different order of resource priorities for an NFT. Priorities can be used,1169		/// for example, for order of rendering.1170		///1171		/// Note that the priorities are not updated automatically, and are an empty vector1172		/// by default. There is no pre-set definition for the order to be particular,1173		/// it can be interpreted arbitrarily use-case by use-case.1174		///1175		/// # Permissions:1176		/// - Token owner1177		///1178		/// # Arguments:1179		/// - `origin`: sender of the transaction1180		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1181		/// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1182		/// - `priorities`: Ordered vector of resource IDs.1183		#[pallet::call_index(12)]1184		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]1185		pub fn set_priority(1186			origin: OriginFor<T>,1187			rmrk_collection_id: RmrkCollectionId,1188			rmrk_nft_id: RmrkNftId,1189			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1190		) -> DispatchResult {1191			let sender = ensure_signed(origin)?;1192			let sender = T::CrossAccountId::from_sub(sender);11931194			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1195			let nft_id = rmrk_nft_id.into();11961197			let collection =1198				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1199			collection.check_is_external()?;12001201			let budget = budget::Value::new(NESTING_BUDGET);12021203			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1204			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;12051206			<PalletNft<T>>::set_scoped_token_property(1207				collection_id,1208				nft_id,1209				RMRK_SCOPE,1210				Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1211			)?;12121213			Self::deposit_event(Event::<T>::PrioritySet {1214				collection_id: rmrk_collection_id,1215				nft_id: rmrk_nft_id,1216			});12171218			Ok(())1219		}12201221		/// Create and set/propose a basic resource for an NFT.1222		///1223		/// A basic resource is the simplest, lacking a Base and anything that comes with it.1224		/// See RMRK docs for more information and examples.1225		///1226		/// # Permissions:1227		/// - Collection issuer - if not the token owner, adding the resource will warrant1228		/// the owner's [acceptance](Pallet::accept_resource).1229		///1230		/// # Arguments:1231		/// - `origin`: sender of the transaction1232		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1233		/// - `nft_id`: ID of the NFT to assign a resource to.1234		/// - `resource`: Data of the resource to be created.1235		#[pallet::call_index(13)]1236		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1237		pub fn add_basic_resource(1238			origin: OriginFor<T>,1239			rmrk_collection_id: RmrkCollectionId,1240			nft_id: RmrkNftId,1241			resource: RmrkBasicResource,1242		) -> DispatchResult {1243			let sender = ensure_signed(origin.clone())?;12441245			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1246			let collection =1247				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1248			collection.check_is_external()?;12491250			let resource_id = Self::resource_add(1251				sender,1252				collection_id,1253				nft_id.into(),1254				RmrkResourceTypes::Basic(resource),1255			)?;12561257			Self::deposit_event(Event::ResourceAdded {1258				nft_id,1259				resource_id,1260			});1261			Ok(())1262		}12631264		/// Create and set/propose a composable resource for an NFT.1265		///1266		/// A composable resource links to a Base and has a subset of its Parts it is composed of.1267		/// See RMRK docs for more information and examples.1268		///1269		/// # Permissions:1270		/// - Collection issuer - if not the token owner, adding the resource will warrant1271		/// the owner's [acceptance](Pallet::accept_resource).1272		///1273		/// # Arguments:1274		/// - `origin`: sender of the transaction1275		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1276		/// - `nft_id`: ID of the NFT to assign a resource to.1277		/// - `resource`: Data of the resource to be created.1278		#[pallet::call_index(14)]1279		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1280		pub fn add_composable_resource(1281			origin: OriginFor<T>,1282			rmrk_collection_id: RmrkCollectionId,1283			nft_id: RmrkNftId,1284			resource: RmrkComposableResource,1285		) -> DispatchResult {1286			let sender = ensure_signed(origin.clone())?;12871288			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1289			let collection =1290				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1291			collection.check_is_external()?;12921293			let base_id = resource.base;12941295			let resource_id = Self::resource_add(1296				sender,1297				collection_id,1298				nft_id.into(),1299				RmrkResourceTypes::Composable(resource),1300			)?;13011302			<PalletNft<T>>::try_mutate_token_aux_property(1303				collection_id,1304				nft_id.into(),1305				RMRK_SCOPE,1306				Self::get_scoped_property_key(AssociatedBases)?,1307				|value| -> DispatchResult {1308					let mut bases: BasesMap = match value {1309						Some(value) => Self::decode_property_value(value)?,1310						None => BasesMap::new(),1311					};13121313					*bases.entry(base_id).or_insert(0) += 1;13141315					*value = Some(Self::encode_property_value(&bases)?);1316					Ok(())1317				},1318			)?;13191320			Self::deposit_event(Event::ResourceAdded {1321				nft_id,1322				resource_id,1323			});1324			Ok(())1325		}13261327		/// Create and set/propose a slot resource for an NFT.1328		///1329		/// A slot resource links to a Base and a slot ID in it which it can fit into.1330		/// See RMRK docs for more information and examples.1331		///1332		/// # Permissions:1333		/// - Collection issuer - if not the token owner, adding the resource will warrant1334		/// the owner's [acceptance](Pallet::accept_resource).1335		///1336		/// # Arguments:1337		/// - `origin`: sender of the transaction1338		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1339		/// - `nft_id`: ID of the NFT to assign a resource to.1340		/// - `resource`: Data of the resource to be created.1341		#[pallet::call_index(15)]1342		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1343		pub fn add_slot_resource(1344			origin: OriginFor<T>,1345			rmrk_collection_id: RmrkCollectionId,1346			nft_id: RmrkNftId,1347			resource: RmrkSlotResource,1348		) -> DispatchResult {1349			let sender = ensure_signed(origin.clone())?;13501351			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1352			let collection =1353				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1354			collection.check_is_external()?;13551356			let resource_id = Self::resource_add(1357				sender,1358				collection_id,1359				nft_id.into(),1360				RmrkResourceTypes::Slot(resource),1361			)?;13621363			Self::deposit_event(Event::ResourceAdded {1364				nft_id,1365				resource_id,1366			});1367			Ok(())1368		}13691370		/// Remove and erase a resource from an NFT.1371		///1372		/// If the sender does not own the NFT, then it will be pending confirmation,1373		/// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1374		///1375		/// # Permissions1376		/// - Collection issuer1377		///1378		/// # Arguments1379		/// - `origin`: sender of the transaction1380		/// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1381		/// - `nft_id`: ID of the NFT with a resource to be removed.1382		/// - `resource_id`: ID of the resource to be removed.1383		#[pallet::call_index(16)]1384		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1385		pub fn remove_resource(1386			origin: OriginFor<T>,1387			rmrk_collection_id: RmrkCollectionId,1388			nft_id: RmrkNftId,1389			resource_id: RmrkResourceId,1390		) -> DispatchResult {1391			let sender = ensure_signed(origin.clone())?;13921393			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1394			let collection =1395				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1396			collection.check_is_external()?;13971398			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13991400			Self::deposit_event(Event::ResourceRemoval {1401				nft_id,1402				resource_id,1403			});1404			Ok(())1405		}1406	}1407}14081409impl<T: Config> Pallet<T> {1410	/// Transform one of possible RMRK keys into a byte key with a RMRK scope.1411	pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1412		let key = rmrk_key.to_key::<T>()?;14131414		let scoped_key = RMRK_SCOPE1415			.apply(key)1416			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;14171418		Ok(scoped_key)1419	}14201421	/// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1422	/// and encoding the value from an arbitrary type into bytes.1423	pub fn encode_rmrk_property<E: Encode>(1424		rmrk_key: RmrkProperty,1425		value: &E,1426	) -> Result<Property, DispatchError> {1427		let key = rmrk_key.to_key::<T>()?;14281429		let value = Self::encode_property_value(value)?;14301431		let property = Property { key, value };14321433		Ok(property)1434	}14351436	/// Encode property value from an arbitrary type into bytes for storage.1437	pub fn encode_property_value<E: Encode, S: Get<u32>>(1438		value: &E,1439	) -> Result<BoundedBytes<S>, DispatchError> {1440		let value = value1441			.encode()1442			.try_into()1443			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14441445		Ok(value)1446	}14471448	/// Decode property value from bytes into an arbitrary type.1449	pub fn decode_property_value<D: Decode, S: Get<u32>>(1450		vec: &BoundedBytes<S>,1451	) -> Result<D, DispatchError> {1452		vec.decode()1453			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1454	}14551456	/// Change the limit of a property value byte vector.1457	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1458	where1459		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1460	{1461		vec.rebind()1462			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1463	}14641465	/// Initialize a new NFT collection with certain RMRK-scoped properties.1466	///1467	/// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1468	fn init_collection(1469		sender: T::CrossAccountId,1470		data: CreateCollectionData<T::AccountId>,1471		properties: impl Iterator<Item = Property>,1472	) -> Result<CollectionId, DispatchError> {1473		let collection_id = <PalletNft<T>>::init_collection(1474			sender.clone(),1475			sender,1476			data,1477			up_data_structs::CollectionFlags {1478				external: true,1479				..Default::default()1480			},1481		);14821483		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1484			return Err(<Error<T>>::NoAvailableCollectionId.into());1485		}14861487		<PalletCommon<T>>::set_scoped_collection_properties(1488			collection_id?,1489			RMRK_SCOPE,1490			properties,1491		)?;14921493		collection_id1494	}14951496	/// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1497	///1498	/// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1499	pub fn create_nft(1500		sender: &T::CrossAccountId,1501		owner: &T::CrossAccountId,1502		collection: &NonfungibleHandle<T>,1503		properties: impl Iterator<Item = Property>,1504	) -> Result<TokenId, DispatchError> {1505		let data = CreateNftExData {1506			properties: BoundedVec::default(),1507			owner: owner.clone(),1508		};15091510		let budget = budget::Value::new(NESTING_BUDGET);15111512		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;15131514		let nft_id = <PalletNft<T>>::current_token_id(collection.id);15151516		<PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;15171518		Ok(nft_id)1519	}15201521	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1522	///1523	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1524	fn destroy_nft(1525		sender: T::CrossAccountId,1526		collection_id: CollectionId,1527		token_id: TokenId,1528		max_burns: u32,1529		error_if_not_owned: Error<T>,1530	) -> DispatchResultWithPostInfo {1531		let collection =1532			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15331534		let token_data =1535			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15361537		let from = token_data.owner;15381539		let owner_check_budget = budget::Value::new(NESTING_BUDGET);15401541		ensure!(1542			<PalletStructure<T>>::check_indirectly_owned(1543				sender.clone(),1544				collection_id,1545				token_id,1546				None,1547				&owner_check_budget1548			)?,1549			error_if_not_owned,1550		);15511552		let burns_budget = budget::Value::new(max_burns);1553		let breadth_budget = budget::Value::new(max_burns);15541555		<PalletNft<T>>::burn_recursively(1556			&collection,1557			&from,1558			token_id,1559			&burns_budget,1560			&breadth_budget,1561		)1562	}15631564	/// Add a sent token pending acceptance to the target owning token as a property.1565	fn insert_pending_child(1566		target: (CollectionId, TokenId),1567		child: (RmrkCollectionId, RmrkNftId),1568	) -> DispatchResult {1569		Self::mutate_pending_children(target, |pending_children| {1570			pending_children.insert(child);1571		})1572	}15731574	/// Remove a sent token pending acceptance from the target token's properties.1575	fn remove_pending_child(1576		target: (CollectionId, TokenId),1577		child: (RmrkCollectionId, RmrkNftId),1578	) -> DispatchResult {1579		Self::mutate_pending_children(target, |pending_children| {1580			pending_children.remove(&child);1581		})1582	}15831584	/// Apply a mutation to the property of a token containing sent tokens1585	/// that are currently pending acceptance.1586	fn mutate_pending_children(1587		(target_collection_id, target_nft_id): (CollectionId, TokenId),1588		f: impl FnOnce(&mut PendingChildrenSet),1589	) -> DispatchResult {1590		<PalletNft<T>>::try_mutate_token_aux_property(1591			target_collection_id,1592			target_nft_id,1593			RMRK_SCOPE,1594			Self::get_scoped_property_key(PendingChildren)?,1595			|pending_children| -> DispatchResult {1596				let mut map = match pending_children {1597					Some(map) => Self::decode_property_value(map)?,1598					None => PendingChildrenSet::new(),1599				};16001601				f(&mut map);16021603				*pending_children = Some(Self::encode_property_value(&map)?);16041605				Ok(())1606			},1607		)1608	}16091610	/// Get an iterator from a token's property containing tokens sent to it1611	/// that are currently pending acceptance.1612	fn iterate_pending_children(1613		collection_id: CollectionId,1614		nft_id: TokenId,1615	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1616		let property = <PalletNft<T>>::token_aux_property((1617			collection_id,1618			nft_id,1619			RMRK_SCOPE,1620			Self::get_scoped_property_key(PendingChildren)?,1621		));16221623		let pending_children = match property {1624			Some(map) => Self::decode_property_value(&map)?,1625			None => PendingChildrenSet::new(),1626		};16271628		Ok(pending_children.into_iter())1629	}16301631	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1632	/// Thus, the returned resource ID should be used.1633	///1634	/// Resource IDs are unique only across an NFT.1635	fn acquire_next_resource_id(1636		collection_id: CollectionId,1637		nft_id: TokenId,1638	) -> Result<RmrkResourceId, DispatchError> {1639		let resource_id: RmrkResourceId =1640			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16411642		let next_id = resource_id1643			.checked_add(1)1644			.ok_or(<Error<T>>::NoAvailableResourceId)?;16451646		<PalletNft<T>>::set_scoped_token_property(1647			collection_id,1648			nft_id,1649			RMRK_SCOPE,1650			Self::encode_rmrk_property(NextResourceId, &next_id)?,1651		)?;16521653		Ok(resource_id)1654	}16551656	/// Create and add a resource for a regular NFT, mark it as pending if the sender1657	/// is not the token owner. The sender must be the collection owner.1658	fn resource_add(1659		sender: T::AccountId,1660		collection_id: CollectionId,1661		nft_id: TokenId,1662		resource: RmrkResourceTypes,1663	) -> Result<RmrkResourceId, DispatchError> {1664		let collection =1665			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1666		ensure!(collection.owner == sender, Error::<T>::NoPermission);16671668		let sender = T::CrossAccountId::from_sub(sender);1669		let budget = budget::Value::new(NESTING_BUDGET);16701671		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1672			.map_err(Self::map_unique_err_to_proxy)?1673			.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;16741675		let pending = sender != nft_owner;16761677		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16781679		let resource_info = RmrkResourceInfo {1680			id,1681			resource,1682			pending,1683			pending_removal: false,1684		};16851686		<PalletNft<T>>::try_mutate_token_aux_property(1687			collection_id,1688			nft_id,1689			RMRK_SCOPE,1690			Self::get_scoped_property_key(ResourceId(id))?,1691			|value| -> DispatchResult {1692				*value = Some(Self::encode_property_value(&resource_info)?);16931694				Ok(())1695			},1696		)?;16971698		Ok(id)1699	}17001701	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1702	/// The sender must be the collection owner.1703	fn resource_remove(1704		sender: T::AccountId,1705		collection_id: CollectionId,1706		nft_id: TokenId,1707		resource_id: RmrkResourceId,1708	) -> DispatchResult {1709		let collection =1710			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1711		ensure!(collection.owner == sender, Error::<T>::NoPermission);17121713		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;17141715		let resource = <PalletNft<T>>::token_aux_property((1716			collection_id,1717			nft_id,1718			RMRK_SCOPE,1719			resource_id_key.clone(),1720		))1721		.ok_or(<Error<T>>::ResourceDoesntExist)?;17221723		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17241725		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1726		let topmost_owner =1727			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?1728				.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;17291730		let sender = T::CrossAccountId::from_sub(sender);1731		if topmost_owner == sender {1732			<PalletNft<T>>::remove_token_aux_property(1733				collection_id,1734				nft_id,1735				RMRK_SCOPE,1736				Self::get_scoped_property_key(ResourceId(resource_id))?,1737			);17381739			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1740				let base_id = resource.base;17411742				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1743			}1744		} else {1745			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1746				res.pending_removal = true;17471748				Ok(())1749			})?;1750		}17511752		Ok(())1753	}17541755	/// Remove a Base ID from an NFT if they are associated.1756	/// The Base itself is deleted if the number of associated NFTs reaches 0.1757	fn remove_associated_base_id(1758		collection_id: CollectionId,1759		nft_id: TokenId,1760		base_id: RmrkBaseId,1761	) -> DispatchResult {1762		<PalletNft<T>>::try_mutate_token_aux_property(1763			collection_id,1764			nft_id,1765			RMRK_SCOPE,1766			Self::get_scoped_property_key(AssociatedBases)?,1767			|value| -> DispatchResult {1768				let mut bases: BasesMap = match value {1769					Some(value) => Self::decode_property_value(value)?,1770					None => BasesMap::new(),1771				};17721773				let remaining = bases.get(&base_id);17741775				if let Some(remaining) = remaining {1776					if let Some(0) | None = remaining.checked_sub(1) {1777						bases.remove(&base_id);1778					}1779				}17801781				*value = Some(Self::encode_property_value(&bases)?);1782				Ok(())1783			},1784		)1785	}17861787	/// Apply a mutation to a resource stored in the token properties of an NFT.1788	fn try_mutate_resource_info(1789		collection_id: CollectionId,1790		nft_id: TokenId,1791		resource_id: RmrkResourceId,1792		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1793	) -> DispatchResult {1794		<PalletNft<T>>::try_mutate_token_aux_property(1795			collection_id,1796			nft_id,1797			RMRK_SCOPE,1798			Self::get_scoped_property_key(ResourceId(resource_id))?,1799			|value| match value {1800				Some(value) => {1801					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;18021803					f(&mut resource_info)?;18041805					*value = Self::encode_property_value(&resource_info)?;18061807					Ok(())1808				}1809				None => Err(<Error<T>>::ResourceDoesntExist.into()),1810			},1811		)1812	}18131814	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1815	fn change_collection_owner(1816		collection_id: CollectionId,1817		collection_type: misc::CollectionType,1818		sender: T::AccountId,1819		new_owner: T::AccountId,1820	) -> DispatchResult {1821		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1822		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18231824		let mut collection = collection.into_inner();18251826		collection.owner = new_owner;1827		collection.save()1828	}18291830	/// Ensure that an account is the collection owner/issuer, return an error if not.1831	pub fn check_collection_owner(1832		collection: &NonfungibleHandle<T>,1833		account: &T::CrossAccountId,1834	) -> DispatchResult {1835		collection1836			.check_is_owner(account)1837			.map_err(Self::map_unique_err_to_proxy)1838	}18391840	/// Get the latest yet-unused RMRK collection index from the storage.1841	pub fn last_collection_idx() -> RmrkCollectionId {1842		<CollectionIndex<T>>::get()1843	}18441845	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1846	pub fn unique_collection_id(1847		rmrk_collection_id: RmrkCollectionId,1848	) -> Result<CollectionId, DispatchError> {1849		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1850			.map_err(|_| <Error<T>>::CollectionUnknown.into())1851	}18521853	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1854	pub fn rmrk_collection_id(1855		unique_collection_id: CollectionId,1856	) -> Result<RmrkCollectionId, DispatchError> {1857		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1858	}18591860	/// Fetch a Unique NFT collection.1861	pub fn get_nft_collection(1862		collection_id: CollectionId,1863	) -> Result<NonfungibleHandle<T>, DispatchError> {1864		let collection = <CollectionHandle<T>>::try_get(collection_id)1865			.map_err(|_| <Error<T>>::CollectionUnknown)?;18661867		match collection.mode {1868			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1869			_ => Err(<Error<T>>::CollectionUnknown.into()),1870		}1871	}18721873	/// Check if an NFT collection with such an ID exists.1874	pub fn collection_exists(collection_id: CollectionId) -> bool {1875		<CollectionHandle<T>>::try_get(collection_id).is_ok()1876	}18771878	/// Fetch and decode a RMRK-scoped collection property value in bytes.1879	pub fn get_collection_property(1880		collection_id: CollectionId,1881		key: RmrkProperty,1882	) -> Result<PropertyValue, DispatchError> {1883		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1884			.get(&Self::get_scoped_property_key(key)?)1885			.ok_or(<Error<T>>::CollectionUnknown)?1886			.clone();18871888		Ok(collection_property)1889	}18901891	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1892	pub fn get_collection_property_decoded<V: Decode>(1893		collection_id: CollectionId,1894		key: RmrkProperty,1895	) -> Result<V, DispatchError> {1896		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1897	}18981899	/// Get the type of a collection stored as a scoped property.1900	///1901	/// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1902	pub fn get_collection_type(1903		collection_id: CollectionId,1904	) -> Result<misc::CollectionType, DispatchError> {1905		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1906			if err != <Error<T>>::CollectionUnknown.into() {1907				<Error<T>>::CorruptedCollectionType.into()1908			} else {1909				err1910			}1911		})1912	}19131914	/// Ensure that the type of the collection equals the provided type,1915	/// otherwise return an error.1916	pub fn ensure_collection_type(1917		collection_id: CollectionId,1918		collection_type: misc::CollectionType,1919	) -> DispatchResult {1920		let actual_type = Self::get_collection_type(collection_id)?;1921		ensure!(1922			actual_type == collection_type,1923			<CommonError<T>>::NoPermission1924		);19251926		Ok(())1927	}19281929	/// Fetch an NFT collection, but make sure it has the appropriate type.1930	pub fn get_typed_nft_collection(1931		collection_id: CollectionId,1932		collection_type: misc::CollectionType,1933	) -> Result<NonfungibleHandle<T>, DispatchError> {1934		Self::ensure_collection_type(collection_id, collection_type)?;19351936		Self::get_nft_collection(collection_id)1937	}19381939	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1940	/// but also return the Unique collection ID.1941	pub fn get_typed_nft_collection_mapped(1942		rmrk_collection_id: RmrkCollectionId,1943		collection_type: misc::CollectionType,1944	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1945		let unique_collection_id = match collection_type {1946			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1947			_ => rmrk_collection_id.into(),1948		};19491950		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19511952		Ok((collection, unique_collection_id))1953	}19541955	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1956	pub fn get_nft_property(1957		collection_id: CollectionId,1958		nft_id: TokenId,1959		key: RmrkProperty,1960	) -> Result<PropertyValue, DispatchError> {1961		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1962			.get(&Self::get_scoped_property_key(key)?)1963			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1964			.clone();19651966		Ok(nft_property)1967	}19681969	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1970	pub fn get_nft_property_decoded<V: Decode>(1971		collection_id: CollectionId,1972		nft_id: TokenId,1973		key: RmrkProperty,1974	) -> Result<V, DispatchError> {1975		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1976	}19771978	/// Check that an NFT exists.1979	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1980		<TokenData<T>>::contains_key((collection_id, nft_id))1981	}19821983	/// Get the type of an NFT stored as a scoped property.1984	///1985	/// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1986	pub fn get_nft_type(1987		collection_id: CollectionId,1988		token_id: TokenId,1989	) -> Result<NftType, DispatchError> {1990		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1991			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1992	}19931994	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1995	pub fn ensure_nft_type(1996		collection_id: CollectionId,1997		token_id: TokenId,1998		nft_type: NftType,1999	) -> DispatchResult {2000		let actual_type = Self::get_nft_type(collection_id, token_id)?;2001		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);20022003		Ok(())2004	}20052006	/// Ensure that an account is the owner of the token, either directly2007	/// or at the top of the nesting hierarchy; return an error if it is not.2008	pub fn ensure_nft_owner(2009		collection_id: CollectionId,2010		token_id: TokenId,2011		possible_owner: &T::CrossAccountId,2012		nesting_budget: &dyn budget::Budget,2013	) -> DispatchResult {2014		let is_owned = <PalletStructure<T>>::check_indirectly_owned(2015			possible_owner.clone(),2016			collection_id,2017			token_id,2018			None,2019			nesting_budget,2020		)2021		.map_err(Self::map_unique_err_to_proxy)?;20222023		ensure!(is_owned, <Error<T>>::NoPermission);20242025		Ok(())2026	}20272028	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,2029	/// or, if None are provided, return all non-scoped properties.2030	pub fn filter_user_properties<Key, Value, R, Mapper>(2031		collection_id: CollectionId,2032		token_id: Option<TokenId>,2033		filter_keys: Option<Vec<RmrkPropertyKey>>,2034		mapper: Mapper,2035	) -> Result<Vec<R>, DispatchError>2036	where2037		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2038		Value: Decode + Default,2039		Mapper: Fn(Key, Value) -> R,2040	{2041		filter_keys2042			.map(|keys| {2043				let properties = keys2044					.into_iter()2045					.filter_map(|key| {2046						let key: Key = key.try_into().ok()?;20472048						let value = match token_id {2049							Some(token_id) => Self::get_nft_property_decoded(2050								collection_id,2051								token_id,2052								UserProperty(key.as_ref()),2053							),2054							None => Self::get_collection_property_decoded(2055								collection_id,2056								UserProperty(key.as_ref()),2057							),2058						}2059						.ok()?;20602061						Some(mapper(key, value))2062					})2063					.collect();20642065				Ok(properties)2066			})2067			.unwrap_or_else(|| {2068				let properties =2069					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20702071				Ok(properties)2072			})2073	}20742075	/// Get all non-scoped properties from a collection or a token, and apply some transformation,2076	/// supplied by `mapper`, to each key-value pair.2077	pub fn iterate_user_properties<Key, Value, R, Mapper>(2078		collection_id: CollectionId,2079		token_id: Option<TokenId>,2080		mapper: Mapper,2081	) -> Result<impl Iterator<Item = R>, DispatchError>2082	where2083		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2084		Value: Decode + Default,2085		Mapper: Fn(Key, Value) -> R,2086	{2087		let properties = match token_id {2088			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2089			None => <PalletCommon<T>>::collection_properties(collection_id),2090		};20912092		let properties = properties.into_iter().filter_map(move |(key, value)| {2093			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20942095			let key: Key = key.to_vec().try_into().ok()?;2096			let value: Value = value.decode().ok()?;20972098			Some(mapper(key, value))2099		});21002101		Ok(properties)2102	}21032104	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2105	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2106		map_unique_err_to_proxy! {2107			match err {2108				CommonError::NoPermission => NoPermission,2109				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2110				CommonError::PublicMintingNotAllowed => NoPermission,2111				CommonError::TokenNotFound => NoAvailableNftId,2112				CommonError::ApprovedValueTooLow => NoPermission,2113				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2114				StructureError::TokenNotFound => NoAvailableNftId,2115				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2116			}2117		}2118	}2119}