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

difftreelog

fix find_parent

Daniel Shiposha2023-01-19parent: #030bc0d.patch.diff
in: master

8 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -155,6 +155,11 @@
 }
 
 impl<T: Config> CollectionHandle<T> {
+	/// Get the mode of the collection: NFT/FT/RFT.
+	pub fn mode(&self) -> CollectionMode {
+		self.mode
+	}
+
 	/// Same as [CollectionHandle::new] but with an explicit gas limit.
 	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
 		<CollectionById<T>>::get(id).map(|collection| Self {
@@ -1872,6 +1877,9 @@
 /// It wraps methods in Fungible, Nonfungible and Refungible pallets
 /// and adds weight info.
 pub trait CommonCollectionOperations<T: Config> {
+	/// Get the mode of the collection: NFT/FT/RFT.
+	fn mode(&self) -> CollectionMode;
+
 	/// Create token.
 	///
 	/// * `sender` - The user who mint the token and pays for the transaction.
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -125,6 +125,10 @@
 /// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
 /// methods and adds weight info.
 impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
+	fn mode(&self) -> up_data_structs::CollectionMode {
+		self.0.mode()
+	}
+
 	fn create_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -152,6 +152,10 @@
 /// Implementation of `CommonCollectionOperations` for `NonfungibleHandle`. It wraps Nonfungible Pallete
 /// methods and adds weight info.
 impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {
+	fn mode(&self) -> up_data_structs::CollectionMode {
+		self.0.mode()
+	}
+
 	fn create_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # RMRK Core Proxy Pallet18//!19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//!35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//!38//! ### What is RMRK?39//!40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//!43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources,45//! and more.46//!47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK 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}
after · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # RMRK Core Proxy Pallet18//!19//! A pallet used as proxy for RMRK Core (<https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core>).20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The RMRK Core Proxy pallet mirrors the functionality of RMRK Core,28//! binding its externalities to Unique's own underlying structure.29//! It is purposed to mimic RMRK Core exactly, allowing seamless integrations30//! of solutions based on RMRK.31//!32//! RMRK Core itself contains essential functionality for RMRK's nested and33//! multi-resourced NFTs.34//!35//! *Note*, that while RMRK itself is subject to active development and restructuring,36//! the proxy may be caught temporarily out of date.37//!38//! ### What is RMRK?39//!40//! RMRK is a set of NFT standards which compose several "NFT 2.0 lego" primitives.41//! Putting these legos together allows a user to create NFT systems of arbitrary complexity.42//!43//! Meaning, RMRK NFTs are dynamic, able to nest into each other and form a hierarchy,44//! make use of specific changeable and partially shared metadata in the form of resources,45//! and more.46//!47//! Visit RMRK documentation and repositories to learn more:48//! - Docs: <https://docs.rmrk.app/getting-started/>49//! - FAQ: <https://coda.io/@rmrk/faq>50//! - Substrate code repository: <https://github.com/rmrk-team/rmrk-substrate>51//! - RMRK specification repository: <https://github.com/rmrk-team/rmrk-spec>52//!53//! ## Terminology54//!55//! For more information on RMRK, see RMRK's own documentation.56//!57//! ### Intro to RMRK58//!59//! - **Resource:** Additional piece of metadata of an NFT usually serving to 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}
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -186,6 +186,10 @@
 /// Implementation of `CommonCollectionOperations` for `RefungibleHandle`. It wraps Refungible Pallete
 /// methods and adds weight info.
 impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {
+	fn mode(&self) -> up_data_structs::CollectionMode {
+		self.0.mode()
+	}
+
 	fn create_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -61,6 +61,7 @@
 use frame_support::fail;
 pub use pallet::*;
 use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
+use up_data_structs::CollectionMode;
 use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};
 
 #[cfg(feature = "runtime-benchmarks")]
@@ -135,6 +136,8 @@
 	User(CrossAccountId),
 	/// Could not find the token provided as the owner.
 	TokenNotFound,
+	/// Nested token has multiple owners.
+	MultipleOwners,
 	/// Token owner is another token (still, the target token may not exist).
 	Token(CollectionId, TokenId),
 }
@@ -163,6 +166,10 @@
 				Some((collection, token)) => Parent::Token(collection, token),
 				None => Parent::User(owner),
 			},
+			None if handle.mode() == CollectionMode::ReFungible => handle
+				.total_pieces(token)
+				.map(|_| Parent::MultipleOwners)
+				.unwrap_or(Parent::TokenNotFound),
 			None => Parent::TokenNotFound,
 		})
 	}
@@ -203,19 +210,27 @@
 	///
 	/// May return token address if parent token not yet exists
 	///
+	/// Returns `None` if the token has multiple owners.
+	///
 	/// - `budget`: Limit for searching parents in depth.
 	pub fn find_topmost_owner(
 		collection: CollectionId,
 		token: TokenId,
 		budget: &dyn Budget,
-	) -> Result<T::CrossAccountId, DispatchError> {
+	) -> Result<Option<T::CrossAccountId>, DispatchError> {
 		let owner = Self::parent_chain(collection, token)
 			.take_while(|_| budget.consume())
-			.find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))
+			.find(|p| {
+				matches!(
+					p,
+					Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)
+				)
+			})
 			.ok_or(<Error<T>>::DepthLimit)??;
 
 		Ok(match owner {
-			Parent::User(v) => v,
+			Parent::User(v) => Some(v),
+			Parent::MultipleOwners => None,
 			_ => fail!(<Error<T>>::TokenNotFound),
 		})
 	}
@@ -223,13 +238,15 @@
 	/// Find the topmost parent and check that assigning `for_nest` token as a child for
 	/// `token` wouldn't create a cycle.
 	///
+	/// Returns `None` if the token has multiple owners.
+	///
 	/// - `budget`: Limit for searching parents in depth.
 	pub fn get_checked_topmost_owner(
 		collection: CollectionId,
 		token: TokenId,
 		for_nest: Option<(CollectionId, TokenId)>,
 		budget: &dyn Budget,
-	) -> Result<T::CrossAccountId, DispatchError> {
+	) -> Result<Option<T::CrossAccountId>, DispatchError> {
 		// Tried to nest token in itself
 		if Some((collection, token)) == for_nest {
 			return Err(<Error<T>>::OuroborosDetected.into());
@@ -242,8 +259,9 @@
 					return Err(<Error<T>>::OuroborosDetected.into())
 				}
 				// Token is owned by other user
-				Parent::User(user) => return Ok(user),
+				Parent::User(user) => return Ok(Some(user)),
 				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
+				Parent::MultipleOwners => return Ok(None),
 				// Continue parent chain
 				Parent::Token(_, _) => {}
 			}
@@ -284,12 +302,17 @@
 		budget: &dyn Budget,
 	) -> Result<bool, DispatchError> {
 		let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
-			Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+			Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?
+			{
+				Some(topmost_owner) => topmost_owner,
+				None => return Ok(false),
+			},
 			None => user,
 		};
 
-		Self::get_checked_topmost_owner(collection, token, for_nest, budget)
-			.map(|indirect_owner| indirect_owner == target_parent)
+		Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {
+			indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)
+		})
 	}
 
 	/// Checks that `under` is valid token and that `token_id` could be nested under it
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -252,7 +252,7 @@
 /// Collection can represent various types of tokens.
 /// Each collection can contain only one type of tokens at a time.
 /// This type helps to understand which tokens the collection contains.
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub enum CollectionMode {
 	/// Non fungible tokens.
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -83,7 +83,7 @@
                 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
                     let budget = up_data_structs::budget::Value::new(10);
 
-                    Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+                    Ok(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?)
                 }
                 fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
                     Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))