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

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs66.1 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)?;745746					approval_required = cross_sender != target_nft_owner;747748					if approval_required {749						target_owner = target_nft_owner;750751						<PalletNft<T>>::set_scoped_token_property(752							collection.id,753							nft_id,754							RMRK_SCOPE,755							Self::encode_rmrk_property::<Option<PendingTarget>>(756								PendingNftAccept,757								&Some((target_collection_id, target_nft_id.into())),758							)?,759						)?;760761						Self::insert_pending_child(762							(target_collection_id, target_nft_id.into()),763							(rmrk_collection_id, rmrk_nft_id),764						)?;765					} else {766						target_owner = T::CrossTokenAddressMapping::token_to_address(767							target_collection_id,768							target_nft_id.into(),769						);770					}771				}772			}773774			let src_nft_budget = budget::Value::new(NESTING_BUDGET);775776			<PalletNft<T>>::transfer_from(777				&collection,778				&cross_sender,779				&from,780				&target_owner,781				nft_id,782				&src_nft_budget,783			)784			.map_err(Self::map_unique_err_to_proxy)?;785786			Self::deposit_event(Event::NFTSent {787				sender,788				recipient: new_owner,789				collection_id: rmrk_collection_id,790				nft_id: rmrk_nft_id,791				approval_required,792			});793794			Ok(())795		}796797		/// Accept an NFT sent from another account to self or an owned NFT.798		///799		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.800		///801		/// # Permissions:802		/// - Token-owner-to-be803		///804		/// # Arguments:805		/// - `origin`: sender of the transaction806		/// - `rmrk_collection_id`: RMRK collection ID of the NFT to be accepted.807		/// - `rmrk_nft_id`: ID of the NFT to be accepted.808		/// - `new_owner`: Either the sender's account ID or a sender-owned NFT,809		/// whichever the accepted NFT was sent to.810		#[pallet::call_index(7)]811		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]812		pub fn accept_nft(813			origin: OriginFor<T>,814			rmrk_collection_id: RmrkCollectionId,815			rmrk_nft_id: RmrkNftId,816			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,817		) -> DispatchResult {818			let sender = ensure_signed(origin.clone())?;819			let cross_sender = T::CrossAccountId::from_sub(sender.clone());820821			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;822			let nft_id = rmrk_nft_id.into();823824			let collection =825				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;826			collection.check_is_external()?;827828			let new_cross_owner = match new_owner {829				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {830					T::CrossAccountId::from_sub(account_id.clone())831				}832				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(833					target_collection_id,834					target_nft_id,835				) => {836					let target_collection_id = Self::unique_collection_id(target_collection_id)?;837838					T::CrossTokenAddressMapping::token_to_address(839						target_collection_id,840						TokenId(target_nft_id),841					)842				}843			};844845			let budget = budget::Value::new(NESTING_BUDGET);846847			<PalletNft<T>>::transfer(848				&collection,849				&cross_sender,850				&new_cross_owner,851				nft_id,852				&budget,853			)854			.map_err(|err| {855				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {856					<Error<T>>::CannotAcceptNonOwnedNft.into()857				} else {858					Self::map_unique_err_to_proxy(err)859				}860			})?;861862			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(863				collection_id,864				nft_id,865				RmrkProperty::PendingNftAccept,866			)?;867868			if let Some(pending_target) = pending_target {869				Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;870871				<PalletNft<T>>::set_scoped_token_property(872					collection.id,873					nft_id,874					RMRK_SCOPE,875					Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,876				)?;877			}878879			Self::deposit_event(Event::NFTAccepted {880				sender,881				recipient: new_owner,882				collection_id: rmrk_collection_id,883				nft_id: rmrk_nft_id,884			});885886			Ok(())887		}888889		/// Reject an NFT sent from another account to self or owned NFT.890		/// The NFT in question will not be sent back and burnt instead.891		///892		/// The NFT in question must be pending, and, thus, be [sent](`Pallet::send`) first.893		///894		/// # Permissions:895		/// - Token-owner-to-be-not896		///897		/// # Arguments:898		/// - `origin`: sender of the transaction899		/// - `rmrk_collection_id`: RMRK ID of the NFT to be rejected.900		/// - `rmrk_nft_id`: ID of the NFT to be rejected.901		#[pallet::call_index(8)]902		#[pallet::weight(<SelfWeightOf<T>>::reject_nft())]903		pub fn reject_nft(904			origin: OriginFor<T>,905			rmrk_collection_id: RmrkCollectionId,906			rmrk_nft_id: RmrkNftId,907		) -> DispatchResult {908			let sender = ensure_signed(origin)?;909			let cross_sender = T::CrossAccountId::from_sub(sender.clone());910911			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;912			let nft_id = rmrk_nft_id.into();913914			let collection =915				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;916			collection.check_is_external()?;917918			ensure!(919				<TokenData<T>>::get((collection_id, nft_id)).is_some(),920				<Error<T>>::NoAvailableNftId921			);922923			let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(924				collection_id,925				nft_id,926				RmrkProperty::PendingNftAccept,927			)?;928929			match pending_target {930				Some(pending_target) => {931					Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?932				}933				None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),934			}935936			Self::destroy_nft(937				cross_sender,938				collection_id,939				nft_id,940				NESTING_BUDGET,941				<Error<T>>::CannotRejectNonOwnedNft,942			)943			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;944945			Self::deposit_event(Event::NFTRejected {946				sender,947				collection_id: rmrk_collection_id,948				nft_id: rmrk_nft_id,949			});950951			Ok(())952		}953954		/// Accept the addition of a newly created pending resource to an existing NFT.955		///956		/// This transaction is needed when a resource is created and assigned to an NFT957		/// by a non-owner, i.e. the collection issuer, with one of the958		/// [`add_...` transactions](Pallet::add_basic_resource).959		///960		/// # Permissions:961		/// - Token owner962		///963		/// # Arguments:964		/// - `origin`: sender of the transaction965		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.966		/// - `rmrk_nft_id`: ID of the NFT with a pending resource to be accepted.967		/// - `resource_id`: ID of the newly created pending resource.968		/// accept the addition of a new resource to an existing NFT969		#[pallet::call_index(9)]970		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]971		pub fn accept_resource(972			origin: OriginFor<T>,973			rmrk_collection_id: RmrkCollectionId,974			rmrk_nft_id: RmrkNftId,975			resource_id: RmrkResourceId,976		) -> DispatchResult {977			let sender = ensure_signed(origin)?;978			let cross_sender = T::CrossAccountId::from_sub(sender);979980			let collection_id = Self::unique_collection_id(rmrk_collection_id)981				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;982			let collection =983				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;984			collection.check_is_external()?;985986			let nft_id = rmrk_nft_id.into();987988			let budget = budget::Value::new(NESTING_BUDGET);989990			let nft_owner =991				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)992					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;993994			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {995				ensure!(res.pending, <Error<T>>::ResourceNotPending);996				ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);997998				res.pending = false;9991000				Ok(())1001			})?;10021003			Self::deposit_event(Event::<T>::ResourceAccepted {1004				nft_id: rmrk_nft_id,1005				resource_id,1006			});10071008			Ok(())1009		}10101011		/// Accept the removal of a removal-pending resource from an NFT.1012		///1013		/// This transaction is needed when a non-owner, i.e. the collection issuer,1014		/// requests a [removal](`Pallet::remove_resource`) of a resource from an NFT.1015		///1016		/// # Permissions:1017		/// - Token owner1018		///1019		/// # Arguments:1020		/// - `origin`: sender of the transaction1021		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1022		/// - `rmrk_nft_id`: ID of the NFT with a resource to be removed.1023		/// - `resource_id`: ID of the removal-pending resource.1024		#[pallet::call_index(10)]1025		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1026		pub fn accept_resource_removal(1027			origin: OriginFor<T>,1028			rmrk_collection_id: RmrkCollectionId,1029			rmrk_nft_id: RmrkNftId,1030			resource_id: RmrkResourceId,1031		) -> DispatchResult {1032			let sender = ensure_signed(origin)?;1033			let cross_sender = T::CrossAccountId::from_sub(sender);10341035			let collection_id = Self::unique_collection_id(rmrk_collection_id)1036				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;1037			let collection =1038				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1039			collection.check_is_external()?;10401041			let nft_id = rmrk_nft_id.into();10421043			let budget = budget::Value::new(NESTING_BUDGET);10441045			let nft_owner =1046				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1047					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;10481049			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10501051			let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10521053			let resource_info = <PalletNft<T>>::token_aux_property((1054				collection_id,1055				nft_id,1056				RMRK_SCOPE,1057				resource_id_key.clone(),1058			))1059			.ok_or(<Error<T>>::ResourceDoesntExist)?;10601061			let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10621063			ensure!(1064				resource_info.pending_removal,1065				<Error<T>>::ResourceNotPending1066			);10671068			<PalletNft<T>>::remove_token_aux_property(1069				collection_id,1070				nft_id,1071				RMRK_SCOPE,1072				resource_id_key,1073			);10741075			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1076				let base_id = resource.base;10771078				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1079			}10801081			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1082				nft_id: rmrk_nft_id,1083				resource_id,1084			});10851086			Ok(())1087		}10881089		/// Add or edit a custom user property, a key-value pair, describing the metadata1090		/// of a token or a collection, on either one of these.1091		///1092		/// Note that in this proxy implementation many details regarding RMRK are stored1093		/// as scoped properties prefixed with "rmrk:", normally inaccessible1094		/// to external transactions and RPCs.1095		///1096		/// # Permissions:1097		/// - Collection issuer - in case of collection property1098		/// - Token owner - in case of NFT property1099		///1100		/// # Arguments:1101		/// - `origin`: sender of the transaction1102		/// - `rmrk_collection_id`: RMRK collection ID.1103		/// - `maybe_nft_id`: Optional ID of the NFT. If left empty, then the property is set for the collection.1104		/// - `key`: Key of the custom property to be referenced by.1105		/// - `value`: Value of the custom property to be stored.1106		#[pallet::call_index(11)]1107		#[pallet::weight(<SelfWeightOf<T>>::set_property())]1108		pub fn set_property(1109			origin: OriginFor<T>,1110			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,1111			maybe_nft_id: Option<RmrkNftId>,1112			key: RmrkKeyString,1113			value: RmrkValueString,1114		) -> DispatchResult {1115			let sender = ensure_signed(origin)?;1116			let sender = T::CrossAccountId::from_sub(sender);11171118			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1119			let collection =1120				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1121			collection.check_is_external()?;11221123			let budget = budget::Value::new(NESTING_BUDGET);11241125			match maybe_nft_id {1126				Some(nft_id) => {1127					let token_id: TokenId = nft_id.into();11281129					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1130					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11311132					<PalletNft<T>>::set_scoped_token_property(1133						collection_id,1134						token_id,1135						RMRK_SCOPE,1136						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137					)?;1138				}1139				None => {1140					let collection = Self::get_typed_nft_collection(1141						collection_id,1142						misc::CollectionType::Regular,1143					)?;11441145					Self::check_collection_owner(&collection, &sender)?;11461147					<PalletCommon<T>>::set_scoped_collection_property(1148						collection_id,1149						RMRK_SCOPE,1150						Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1151					)?;1152				}1153			}11541155			Self::deposit_event(Event::PropertySet {1156				collection_id: rmrk_collection_id,1157				maybe_nft_id,1158				key,1159				value,1160			});11611162			Ok(())1163		}11641165		/// Set a different order of resource priorities for an NFT. Priorities can be used,1166		/// for example, for order of rendering.1167		///1168		/// Note that the priorities are not updated automatically, and are an empty vector1169		/// by default. There is no pre-set definition for the order to be particular,1170		/// it can be interpreted arbitrarily use-case by use-case.1171		///1172		/// # Permissions:1173		/// - Token owner1174		///1175		/// # Arguments:1176		/// - `origin`: sender of the transaction1177		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1178		/// - `rmrk_nft_id`: ID of the NFT to rearrange resource priorities for.1179		/// - `priorities`: Ordered vector of resource IDs.1180		#[pallet::call_index(12)]1181		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]1182		pub fn set_priority(1183			origin: OriginFor<T>,1184			rmrk_collection_id: RmrkCollectionId,1185			rmrk_nft_id: RmrkNftId,1186			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1187		) -> DispatchResult {1188			let sender = ensure_signed(origin)?;1189			let sender = T::CrossAccountId::from_sub(sender);11901191			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1192			let nft_id = rmrk_nft_id.into();11931194			let collection =1195				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1196			collection.check_is_external()?;11971198			let budget = budget::Value::new(NESTING_BUDGET);11991200			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1201			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;12021203			<PalletNft<T>>::set_scoped_token_property(1204				collection_id,1205				nft_id,1206				RMRK_SCOPE,1207				Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1208			)?;12091210			Self::deposit_event(Event::<T>::PrioritySet {1211				collection_id: rmrk_collection_id,1212				nft_id: rmrk_nft_id,1213			});12141215			Ok(())1216		}12171218		/// Create and set/propose a basic resource for an NFT.1219		///1220		/// A basic resource is the simplest, lacking a Base and anything that comes with it.1221		/// See RMRK docs for more information and examples.1222		///1223		/// # Permissions:1224		/// - Collection issuer - if not the token owner, adding the resource will warrant1225		/// the owner's [acceptance](Pallet::accept_resource).1226		///1227		/// # Arguments:1228		/// - `origin`: sender of the transaction1229		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1230		/// - `nft_id`: ID of the NFT to assign a resource to.1231		/// - `resource`: Data of the resource to be created.1232		#[pallet::call_index(13)]1233		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1234		pub fn add_basic_resource(1235			origin: OriginFor<T>,1236			rmrk_collection_id: RmrkCollectionId,1237			nft_id: RmrkNftId,1238			resource: RmrkBasicResource,1239		) -> DispatchResult {1240			let sender = ensure_signed(origin.clone())?;12411242			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1243			let collection =1244				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1245			collection.check_is_external()?;12461247			let resource_id = Self::resource_add(1248				sender,1249				collection_id,1250				nft_id.into(),1251				RmrkResourceTypes::Basic(resource),1252			)?;12531254			Self::deposit_event(Event::ResourceAdded {1255				nft_id,1256				resource_id,1257			});1258			Ok(())1259		}12601261		/// Create and set/propose a composable resource for an NFT.1262		///1263		/// A composable resource links to a Base and has a subset of its Parts it is composed of.1264		/// See RMRK docs for more information and examples.1265		///1266		/// # Permissions:1267		/// - Collection issuer - if not the token owner, adding the resource will warrant1268		/// the owner's [acceptance](Pallet::accept_resource).1269		///1270		/// # Arguments:1271		/// - `origin`: sender of the transaction1272		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1273		/// - `nft_id`: ID of the NFT to assign a resource to.1274		/// - `resource`: Data of the resource to be created.1275		#[pallet::call_index(14)]1276		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1277		pub fn add_composable_resource(1278			origin: OriginFor<T>,1279			rmrk_collection_id: RmrkCollectionId,1280			nft_id: RmrkNftId,1281			resource: RmrkComposableResource,1282		) -> DispatchResult {1283			let sender = ensure_signed(origin.clone())?;12841285			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1286			let collection =1287				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1288			collection.check_is_external()?;12891290			let base_id = resource.base;12911292			let resource_id = Self::resource_add(1293				sender,1294				collection_id,1295				nft_id.into(),1296				RmrkResourceTypes::Composable(resource),1297			)?;12981299			<PalletNft<T>>::try_mutate_token_aux_property(1300				collection_id,1301				nft_id.into(),1302				RMRK_SCOPE,1303				Self::get_scoped_property_key(AssociatedBases)?,1304				|value| -> DispatchResult {1305					let mut bases: BasesMap = match value {1306						Some(value) => Self::decode_property_value(value)?,1307						None => BasesMap::new(),1308					};13091310					*bases.entry(base_id).or_insert(0) += 1;13111312					*value = Some(Self::encode_property_value(&bases)?);1313					Ok(())1314				},1315			)?;13161317			Self::deposit_event(Event::ResourceAdded {1318				nft_id,1319				resource_id,1320			});1321			Ok(())1322		}13231324		/// Create and set/propose a slot resource for an NFT.1325		///1326		/// A slot resource links to a Base and a slot ID in it which it can fit into.1327		/// See RMRK docs for more information and examples.1328		///1329		/// # Permissions:1330		/// - Collection issuer - if not the token owner, adding the resource will warrant1331		/// the owner's [acceptance](Pallet::accept_resource).1332		///1333		/// # Arguments:1334		/// - `origin`: sender of the transaction1335		/// - `rmrk_collection_id`: RMRK collection ID of the NFT.1336		/// - `nft_id`: ID of the NFT to assign a resource to.1337		/// - `resource`: Data of the resource to be created.1338		#[pallet::call_index(15)]1339		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1340		pub fn add_slot_resource(1341			origin: OriginFor<T>,1342			rmrk_collection_id: RmrkCollectionId,1343			nft_id: RmrkNftId,1344			resource: RmrkSlotResource,1345		) -> DispatchResult {1346			let sender = ensure_signed(origin.clone())?;13471348			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1349			let collection =1350				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1351			collection.check_is_external()?;13521353			let resource_id = Self::resource_add(1354				sender,1355				collection_id,1356				nft_id.into(),1357				RmrkResourceTypes::Slot(resource),1358			)?;13591360			Self::deposit_event(Event::ResourceAdded {1361				nft_id,1362				resource_id,1363			});1364			Ok(())1365		}13661367		/// Remove and erase a resource from an NFT.1368		///1369		/// If the sender does not own the NFT, then it will be pending confirmation,1370		/// and will have to be [accepted](Pallet::accept_resource_removal) by the token owner.1371		///1372		/// # Permissions1373		/// - Collection issuer1374		///1375		/// # Arguments1376		/// - `origin`: sender of the transaction1377		/// - `rmrk_collection_id`: RMRK ID of a collection to which the NFT making use of the resource belongs to.1378		/// - `nft_id`: ID of the NFT with a resource to be removed.1379		/// - `resource_id`: ID of the resource to be removed.1380		#[pallet::call_index(16)]1381		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1382		pub fn remove_resource(1383			origin: OriginFor<T>,1384			rmrk_collection_id: RmrkCollectionId,1385			nft_id: RmrkNftId,1386			resource_id: RmrkResourceId,1387		) -> DispatchResult {1388			let sender = ensure_signed(origin.clone())?;13891390			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1391			let collection =1392				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1393			collection.check_is_external()?;13941395			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13961397			Self::deposit_event(Event::ResourceRemoval {1398				nft_id,1399				resource_id,1400			});1401			Ok(())1402		}1403	}1404}14051406impl<T: Config> Pallet<T> {1407	/// Transform one of possible RMRK keys into a byte key with a RMRK scope.1408	pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1409		let key = rmrk_key.to_key::<T>()?;14101411		let scoped_key = RMRK_SCOPE1412			.apply(key)1413			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;14141415		Ok(scoped_key)1416	}14171418	/// Form a Unique property, transforming a RMRK key into bytes (without assigning the scope yet)1419	/// and encoding the value from an arbitrary type into bytes.1420	pub fn encode_rmrk_property<E: Encode>(1421		rmrk_key: RmrkProperty,1422		value: &E,1423	) -> Result<Property, DispatchError> {1424		let key = rmrk_key.to_key::<T>()?;14251426		let value = Self::encode_property_value(value)?;14271428		let property = Property { key, value };14291430		Ok(property)1431	}14321433	/// Encode property value from an arbitrary type into bytes for storage.1434	pub fn encode_property_value<E: Encode, S: Get<u32>>(1435		value: &E,1436	) -> Result<BoundedBytes<S>, DispatchError> {1437		let value = value1438			.encode()1439			.try_into()1440			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14411442		Ok(value)1443	}14441445	/// Decode property value from bytes into an arbitrary type.1446	pub fn decode_property_value<D: Decode, S: Get<u32>>(1447		vec: &BoundedBytes<S>,1448	) -> Result<D, DispatchError> {1449		vec.decode()1450			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1451	}14521453	/// Change the limit of a property value byte vector.1454	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1455	where1456		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1457	{1458		vec.rebind()1459			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1460	}14611462	/// Initialize a new NFT collection with certain RMRK-scoped properties.1463	///1464	/// See [`init_collection`](pallet_nonfungible::pallet::Pallet::init_collection) for more details.1465	fn init_collection(1466		sender: T::CrossAccountId,1467		data: CreateCollectionData<T::AccountId>,1468		properties: impl Iterator<Item = Property>,1469	) -> Result<CollectionId, DispatchError> {1470		let collection_id = <PalletNft<T>>::init_collection(1471			sender.clone(),1472			sender,1473			data,1474			up_data_structs::CollectionFlags {1475				external: true,1476				..Default::default()1477			},1478		);14791480		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1481			return Err(<Error<T>>::NoAvailableCollectionId.into());1482		}14831484		<PalletCommon<T>>::set_scoped_collection_properties(1485			collection_id?,1486			RMRK_SCOPE,1487			properties,1488		)?;14891490		collection_id1491	}14921493	/// Mint a new NFT with certain RMRK-scoped properties. Sender must be the collection owner.1494	///1495	/// See [`create_item`](pallet_nonfungible::pallet::Pallet::create_item) for more details.1496	pub fn create_nft(1497		sender: &T::CrossAccountId,1498		owner: &T::CrossAccountId,1499		collection: &NonfungibleHandle<T>,1500		properties: impl Iterator<Item = Property>,1501	) -> Result<TokenId, DispatchError> {1502		let data = CreateNftExData {1503			properties: BoundedVec::default(),1504			owner: owner.clone(),1505		};15061507		let budget = budget::Value::new(NESTING_BUDGET);15081509		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;15101511		let nft_id = <PalletNft<T>>::current_token_id(collection.id);15121513		<PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;15141515		Ok(nft_id)1516	}15171518	/// Burn an NFT, along with its nested children, limited by `max_burns`. The sender must be the token owner.1519	///1520	/// See [`burn_recursively`](pallet_nonfungible::pallet::Pallet::burn_recursively) for more details.1521	fn destroy_nft(1522		sender: T::CrossAccountId,1523		collection_id: CollectionId,1524		token_id: TokenId,1525		max_burns: u32,1526		error_if_not_owned: Error<T>,1527	) -> DispatchResultWithPostInfo {1528		let collection =1529			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15301531		let token_data =1532			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15331534		let from = token_data.owner;15351536		let owner_check_budget = budget::Value::new(NESTING_BUDGET);15371538		ensure!(1539			<PalletStructure<T>>::check_indirectly_owned(1540				sender.clone(),1541				collection_id,1542				token_id,1543				None,1544				&owner_check_budget1545			)?,1546			error_if_not_owned,1547		);15481549		let burns_budget = budget::Value::new(max_burns);1550		let breadth_budget = budget::Value::new(max_burns);15511552		<PalletNft<T>>::burn_recursively(1553			&collection,1554			&from,1555			token_id,1556			&burns_budget,1557			&breadth_budget,1558		)1559	}15601561	/// Add a sent token pending acceptance to the target owning token as a property.1562	fn insert_pending_child(1563		target: (CollectionId, TokenId),1564		child: (RmrkCollectionId, RmrkNftId),1565	) -> DispatchResult {1566		Self::mutate_pending_children(target, |pending_children| {1567			pending_children.insert(child);1568		})1569	}15701571	/// Remove a sent token pending acceptance from the target token's properties.1572	fn remove_pending_child(1573		target: (CollectionId, TokenId),1574		child: (RmrkCollectionId, RmrkNftId),1575	) -> DispatchResult {1576		Self::mutate_pending_children(target, |pending_children| {1577			pending_children.remove(&child);1578		})1579	}15801581	/// Apply a mutation to the property of a token containing sent tokens1582	/// that are currently pending acceptance.1583	fn mutate_pending_children(1584		(target_collection_id, target_nft_id): (CollectionId, TokenId),1585		f: impl FnOnce(&mut PendingChildrenSet),1586	) -> DispatchResult {1587		<PalletNft<T>>::try_mutate_token_aux_property(1588			target_collection_id,1589			target_nft_id,1590			RMRK_SCOPE,1591			Self::get_scoped_property_key(PendingChildren)?,1592			|pending_children| -> DispatchResult {1593				let mut map = match pending_children {1594					Some(map) => Self::decode_property_value(map)?,1595					None => PendingChildrenSet::new(),1596				};15971598				f(&mut map);15991600				*pending_children = Some(Self::encode_property_value(&map)?);16011602				Ok(())1603			},1604		)1605	}16061607	/// Get an iterator from a token's property containing tokens sent to it1608	/// that are currently pending acceptance.1609	fn iterate_pending_children(1610		collection_id: CollectionId,1611		nft_id: TokenId,1612	) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1613		let property = <PalletNft<T>>::token_aux_property((1614			collection_id,1615			nft_id,1616			RMRK_SCOPE,1617			Self::get_scoped_property_key(PendingChildren)?,1618		));16191620		let pending_children = match property {1621			Some(map) => Self::decode_property_value(&map)?,1622			None => PendingChildrenSet::new(),1623		};16241625		Ok(pending_children.into_iter())1626	}16271628	/// Get incremented resource ID from within an NFT's properties and store the new latest ID.1629	/// Thus, the returned resource ID should be used.1630	///1631	/// Resource IDs are unique only across an NFT.1632	fn acquire_next_resource_id(1633		collection_id: CollectionId,1634		nft_id: TokenId,1635	) -> Result<RmrkResourceId, DispatchError> {1636		let resource_id: RmrkResourceId =1637			Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16381639		let next_id = resource_id1640			.checked_add(1)1641			.ok_or(<Error<T>>::NoAvailableResourceId)?;16421643		<PalletNft<T>>::set_scoped_token_property(1644			collection_id,1645			nft_id,1646			RMRK_SCOPE,1647			Self::encode_rmrk_property(NextResourceId, &next_id)?,1648		)?;16491650		Ok(resource_id)1651	}16521653	/// Create and add a resource for a regular NFT, mark it as pending if the sender1654	/// is not the token owner. The sender must be the collection owner.1655	fn resource_add(1656		sender: T::AccountId,1657		collection_id: CollectionId,1658		nft_id: TokenId,1659		resource: RmrkResourceTypes,1660	) -> Result<RmrkResourceId, DispatchError> {1661		let collection =1662			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1663		ensure!(collection.owner == sender, Error::<T>::NoPermission);16641665		let sender = T::CrossAccountId::from_sub(sender);1666		let budget = budget::Value::new(NESTING_BUDGET);16671668		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1669			.map_err(Self::map_unique_err_to_proxy)?;16701671		let pending = sender != nft_owner;16721673		let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16741675		let resource_info = RmrkResourceInfo {1676			id,1677			resource,1678			pending,1679			pending_removal: false,1680		};16811682		<PalletNft<T>>::try_mutate_token_aux_property(1683			collection_id,1684			nft_id,1685			RMRK_SCOPE,1686			Self::get_scoped_property_key(ResourceId(id))?,1687			|value| -> DispatchResult {1688				*value = Some(Self::encode_property_value(&resource_info)?);16891690				Ok(())1691			},1692		)?;16931694		Ok(id)1695	}16961697	/// Designate a resource for erasure from an NFT, and remove it if the sender is the token owner.1698	/// The sender must be the collection owner.1699	fn resource_remove(1700		sender: T::AccountId,1701		collection_id: CollectionId,1702		nft_id: TokenId,1703		resource_id: RmrkResourceId,1704	) -> DispatchResult {1705		let collection =1706			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1707		ensure!(collection.owner == sender, Error::<T>::NoPermission);17081709		let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;17101711		let resource = <PalletNft<T>>::token_aux_property((1712			collection_id,1713			nft_id,1714			RMRK_SCOPE,1715			resource_id_key.clone(),1716		))1717		.ok_or(<Error<T>>::ResourceDoesntExist)?;17181719		let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17201721		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1722		let topmost_owner =1723			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17241725		let sender = T::CrossAccountId::from_sub(sender);1726		if topmost_owner == sender {1727			<PalletNft<T>>::remove_token_aux_property(1728				collection_id,1729				nft_id,1730				RMRK_SCOPE,1731				Self::get_scoped_property_key(ResourceId(resource_id))?,1732			);17331734			if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1735				let base_id = resource.base;17361737				Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1738			}1739		} else {1740			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1741				res.pending_removal = true;17421743				Ok(())1744			})?;1745		}17461747		Ok(())1748	}17491750	/// Remove a Base ID from an NFT if they are associated.1751	/// The Base itself is deleted if the number of associated NFTs reaches 0.1752	fn remove_associated_base_id(1753		collection_id: CollectionId,1754		nft_id: TokenId,1755		base_id: RmrkBaseId,1756	) -> DispatchResult {1757		<PalletNft<T>>::try_mutate_token_aux_property(1758			collection_id,1759			nft_id,1760			RMRK_SCOPE,1761			Self::get_scoped_property_key(AssociatedBases)?,1762			|value| -> DispatchResult {1763				let mut bases: BasesMap = match value {1764					Some(value) => Self::decode_property_value(value)?,1765					None => BasesMap::new(),1766				};17671768				let remaining = bases.get(&base_id);17691770				if let Some(remaining) = remaining {1771					if let Some(0) | None = remaining.checked_sub(1) {1772						bases.remove(&base_id);1773					}1774				}17751776				*value = Some(Self::encode_property_value(&bases)?);1777				Ok(())1778			},1779		)1780	}17811782	/// Apply a mutation to a resource stored in the token properties of an NFT.1783	fn try_mutate_resource_info(1784		collection_id: CollectionId,1785		nft_id: TokenId,1786		resource_id: RmrkResourceId,1787		f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1788	) -> DispatchResult {1789		<PalletNft<T>>::try_mutate_token_aux_property(1790			collection_id,1791			nft_id,1792			RMRK_SCOPE,1793			Self::get_scoped_property_key(ResourceId(resource_id))?,1794			|value| match value {1795				Some(value) => {1796					let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17971798					f(&mut resource_info)?;17991800					*value = Self::encode_property_value(&resource_info)?;18011802					Ok(())1803				}1804				None => Err(<Error<T>>::ResourceDoesntExist.into()),1805			},1806		)1807	}18081809	/// Change the owner of an NFT collection, ensuring that the sender is the current owner.1810	fn change_collection_owner(1811		collection_id: CollectionId,1812		collection_type: misc::CollectionType,1813		sender: T::AccountId,1814		new_owner: T::AccountId,1815	) -> DispatchResult {1816		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1817		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18181819		let mut collection = collection.into_inner();18201821		collection.owner = new_owner;1822		collection.save()1823	}18241825	/// Ensure that an account is the collection owner/issuer, return an error if not.1826	pub fn check_collection_owner(1827		collection: &NonfungibleHandle<T>,1828		account: &T::CrossAccountId,1829	) -> DispatchResult {1830		collection1831			.check_is_owner(account)1832			.map_err(Self::map_unique_err_to_proxy)1833	}18341835	/// Get the latest yet-unused RMRK collection index from the storage.1836	pub fn last_collection_idx() -> RmrkCollectionId {1837		<CollectionIndex<T>>::get()1838	}18391840	/// Get a mapping from a RMRK collection ID to its corresponding Unique collection ID.1841	pub fn unique_collection_id(1842		rmrk_collection_id: RmrkCollectionId,1843	) -> Result<CollectionId, DispatchError> {1844		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1845			.map_err(|_| <Error<T>>::CollectionUnknown.into())1846	}18471848	/// Get a mapping from a Unique collection ID to its RMRK collection ID counterpart, if it exists.1849	pub fn rmrk_collection_id(1850		unique_collection_id: CollectionId,1851	) -> Result<RmrkCollectionId, DispatchError> {1852		Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1853	}18541855	/// Fetch a Unique NFT collection.1856	pub fn get_nft_collection(1857		collection_id: CollectionId,1858	) -> Result<NonfungibleHandle<T>, DispatchError> {1859		let collection = <CollectionHandle<T>>::try_get(collection_id)1860			.map_err(|_| <Error<T>>::CollectionUnknown)?;18611862		match collection.mode {1863			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1864			_ => Err(<Error<T>>::CollectionUnknown.into()),1865		}1866	}18671868	/// Check if an NFT collection with such an ID exists.1869	pub fn collection_exists(collection_id: CollectionId) -> bool {1870		<CollectionHandle<T>>::try_get(collection_id).is_ok()1871	}18721873	/// Fetch and decode a RMRK-scoped collection property value in bytes.1874	pub fn get_collection_property(1875		collection_id: CollectionId,1876		key: RmrkProperty,1877	) -> Result<PropertyValue, DispatchError> {1878		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1879			.get(&Self::get_scoped_property_key(key)?)1880			.ok_or(<Error<T>>::CollectionUnknown)?1881			.clone();18821883		Ok(collection_property)1884	}18851886	/// Fetch a RMRK-scoped collection property and decode it from bytes into an appropriate type.1887	pub fn get_collection_property_decoded<V: Decode>(1888		collection_id: CollectionId,1889		key: RmrkProperty,1890	) -> Result<V, DispatchError> {1891		Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1892	}18931894	/// Get the type of a collection stored as a scoped property.1895	///1896	/// RMRK Core proxy differentiates between regular collections as well as RMRK Bases as collections.1897	pub fn get_collection_type(1898		collection_id: CollectionId,1899	) -> Result<misc::CollectionType, DispatchError> {1900		Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1901			if err != <Error<T>>::CollectionUnknown.into() {1902				<Error<T>>::CorruptedCollectionType.into()1903			} else {1904				err1905			}1906		})1907	}19081909	/// Ensure that the type of the collection equals the provided type,1910	/// otherwise return an error.1911	pub fn ensure_collection_type(1912		collection_id: CollectionId,1913		collection_type: misc::CollectionType,1914	) -> DispatchResult {1915		let actual_type = Self::get_collection_type(collection_id)?;1916		ensure!(1917			actual_type == collection_type,1918			<CommonError<T>>::NoPermission1919		);19201921		Ok(())1922	}19231924	/// Fetch an NFT collection, but make sure it has the appropriate type.1925	pub fn get_typed_nft_collection(1926		collection_id: CollectionId,1927		collection_type: misc::CollectionType,1928	) -> Result<NonfungibleHandle<T>, DispatchError> {1929		Self::ensure_collection_type(collection_id, collection_type)?;19301931		Self::get_nft_collection(collection_id)1932	}19331934	/// Same as [`get_typed_nft_collection`](crate::pallet::Pallet::get_typed_nft_collection),1935	/// but also return the Unique collection ID.1936	pub fn get_typed_nft_collection_mapped(1937		rmrk_collection_id: RmrkCollectionId,1938		collection_type: misc::CollectionType,1939	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1940		let unique_collection_id = match collection_type {1941			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1942			_ => rmrk_collection_id.into(),1943		};19441945		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19461947		Ok((collection, unique_collection_id))1948	}19491950	/// Fetch and decode a RMRK-scoped NFT property value in bytes.1951	pub fn get_nft_property(1952		collection_id: CollectionId,1953		nft_id: TokenId,1954		key: RmrkProperty,1955	) -> Result<PropertyValue, DispatchError> {1956		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1957			.get(&Self::get_scoped_property_key(key)?)1958			.ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1959			.clone();19601961		Ok(nft_property)1962	}19631964	/// Fetch a RMRK-scoped NFT property and decode it from bytes into an appropriate type.1965	pub fn get_nft_property_decoded<V: Decode>(1966		collection_id: CollectionId,1967		nft_id: TokenId,1968		key: RmrkProperty,1969	) -> Result<V, DispatchError> {1970		Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1971	}19721973	/// Check that an NFT exists.1974	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1975		<TokenData<T>>::contains_key((collection_id, nft_id))1976	}19771978	/// Get the type of an NFT stored as a scoped property.1979	///1980	/// RMRK Core proxy differentiates between regular NFTs, and RMRK Parts and Themes.1981	pub fn get_nft_type(1982		collection_id: CollectionId,1983		token_id: TokenId,1984	) -> Result<NftType, DispatchError> {1985		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1986			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1987	}19881989	/// Ensure that the type of the NFT equals the provided type, otherwise return an error.1990	pub fn ensure_nft_type(1991		collection_id: CollectionId,1992		token_id: TokenId,1993		nft_type: NftType,1994	) -> DispatchResult {1995		let actual_type = Self::get_nft_type(collection_id, token_id)?;1996		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19971998		Ok(())1999	}20002001	/// Ensure that an account is the owner of the token, either directly2002	/// or at the top of the nesting hierarchy; return an error if it is not.2003	pub fn ensure_nft_owner(2004		collection_id: CollectionId,2005		token_id: TokenId,2006		possible_owner: &T::CrossAccountId,2007		nesting_budget: &dyn budget::Budget,2008	) -> DispatchResult {2009		let is_owned = <PalletStructure<T>>::check_indirectly_owned(2010			possible_owner.clone(),2011			collection_id,2012			token_id,2013			None,2014			nesting_budget,2015		)2016		.map_err(Self::map_unique_err_to_proxy)?;20172018		ensure!(is_owned, <Error<T>>::NoPermission);20192020		Ok(())2021	}20222023	/// Fetch non-scoped properties of a collection or a token that match the filter keys supplied,2024	/// or, if None are provided, return all non-scoped properties.2025	pub fn filter_user_properties<Key, Value, R, Mapper>(2026		collection_id: CollectionId,2027		token_id: Option<TokenId>,2028		filter_keys: Option<Vec<RmrkPropertyKey>>,2029		mapper: Mapper,2030	) -> Result<Vec<R>, DispatchError>2031	where2032		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2033		Value: Decode + Default,2034		Mapper: Fn(Key, Value) -> R,2035	{2036		filter_keys2037			.map(|keys| {2038				let properties = keys2039					.into_iter()2040					.filter_map(|key| {2041						let key: Key = key.try_into().ok()?;20422043						let value = match token_id {2044							Some(token_id) => Self::get_nft_property_decoded(2045								collection_id,2046								token_id,2047								UserProperty(key.as_ref()),2048							),2049							None => Self::get_collection_property_decoded(2050								collection_id,2051								UserProperty(key.as_ref()),2052							),2053						}2054						.ok()?;20552056						Some(mapper(key, value))2057					})2058					.collect();20592060				Ok(properties)2061			})2062			.unwrap_or_else(|| {2063				let properties =2064					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20652066				Ok(properties)2067			})2068	}20692070	/// Get all non-scoped properties from a collection or a token, and apply some transformation,2071	/// supplied by `mapper`, to each key-value pair.2072	pub fn iterate_user_properties<Key, Value, R, Mapper>(2073		collection_id: CollectionId,2074		token_id: Option<TokenId>,2075		mapper: Mapper,2076	) -> Result<impl Iterator<Item = R>, DispatchError>2077	where2078		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2079		Value: Decode + Default,2080		Mapper: Fn(Key, Value) -> R,2081	{2082		let properties = match token_id {2083			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2084			None => <PalletCommon<T>>::collection_properties(collection_id),2085		};20862087		let properties = properties.into_iter().filter_map(move |(key, value)| {2088			let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20892090			let key: Key = key.to_vec().try_into().ok()?;2091			let value: Value = value.decode().ok()?;20922093			Some(mapper(key, value))2094		});20952096		Ok(properties)2097	}20982099	/// Match Unique errors to RMRK's own and return the RMRK error if a match is successful.2100	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2101		map_unique_err_to_proxy! {2102			match err {2103				CommonError::NoPermission => NoPermission,2104				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2105				CommonError::PublicMintingNotAllowed => NoPermission,2106				CommonError::TokenNotFound => NoAvailableNftId,2107				CommonError::ApprovedValueTooLow => NoPermission,2108				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2109				StructureError::TokenNotFound => NoAvailableNftId,2110				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2111			}2112		}2113	}2114}