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

difftreelog

fix(rmrk) sending nft

Daniel Shiposha2022-06-05parent: #7e808da.patch.diff
in: master

5 files changed

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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::Pallet as PalletStructure;29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		CollectionFullOrLocked,132		ResourceDoesntExist,133	}134135	#[pallet::call]136	impl<T: Config> Pallet<T> {137		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]138		#[transactional]139		pub fn create_collection(140			origin: OriginFor<T>,141			metadata: RmrkString,142			max: Option<u32>,143			symbol: RmrkCollectionSymbol,144		) -> DispatchResult {145			let sender = ensure_signed(origin)?;146147			let limits = CollectionLimits {148				owner_can_transfer: Some(false),149				token_limit: max,150				..Default::default()151			};152153			let data = CreateCollectionData {154				limits: Some(limits),155				token_prefix: symbol156					.into_inner()157					.try_into()158					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,159				permissions: Some(CollectionPermissions {160					nesting: Some(NestingRule::Owner),161					..Default::default()162				}),163				..Default::default()164			};165166			let unique_collection_id = Self::init_collection(167				T::CrossAccountId::from_sub(sender.clone()),168				data,169				[170					Self::rmrk_property(Metadata, &metadata)?,171					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,172				]173				.into_iter(),174			)?;175			let rmrk_collection_id = <CollectionIndex<T>>::get();176177			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);178			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);179180			<CollectionIndex<T>>::mutate(|n| *n += 1);181182			Self::deposit_event(Event::CollectionCreated {183				issuer: sender,184				collection_id: rmrk_collection_id,185			});186187			Ok(())188		}189190		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191		#[transactional]192		pub fn destroy_collection(193			origin: OriginFor<T>,194			collection_id: RmrkCollectionId,195		) -> DispatchResult {196			let sender = ensure_signed(origin)?;197			let cross_sender = T::CrossAccountId::from_sub(sender.clone());198199			let collection = Self::get_typed_nft_collection(200				Self::unique_collection_id(collection_id)?,201				misc::CollectionType::Regular,202			)?;203204			ensure!(205				collection.total_supply() == 0,206				<Error<T>>::CollectionNotEmpty207			);208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_common_err_to_proxy)?;211212			Self::deposit_event(Event::CollectionDestroyed {213				issuer: sender,214				collection_id,215			});216217			Ok(())218		}219220		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221		#[transactional]222		pub fn change_collection_issuer(223			origin: OriginFor<T>,224			collection_id: RmrkCollectionId,225			new_issuer: <T::Lookup as StaticLookup>::Source,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228229			let new_issuer = T::Lookup::lookup(new_issuer)?;230231			Self::change_collection_owner(232				Self::unique_collection_id(collection_id)?,233				misc::CollectionType::Regular,234				sender.clone(),235				new_issuer.clone(),236			)?;237238			Self::deposit_event(Event::IssuerChanged {239				old_issuer: sender,240				new_issuer,241				collection_id,242			});243244			Ok(())245		}246247		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248		#[transactional]249		pub fn lock_collection(250			origin: OriginFor<T>,251			collection_id: RmrkCollectionId,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254			let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256			let collection = Self::get_typed_nft_collection(257				Self::unique_collection_id(collection_id)?,258				misc::CollectionType::Regular,259			)?;260261			Self::check_collection_owner(&collection, &cross_sender)?;262263			let token_count = collection.total_supply();264265			let mut collection = collection.into_inner();266			collection.limits.token_limit = Some(token_count);267			collection.save()?;268269			Self::deposit_event(Event::CollectionLocked {270				issuer: sender,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn mint_nft(280			origin: OriginFor<T>,281			owner: T::AccountId,282			collection_id: RmrkCollectionId,283			recipient: Option<T::AccountId>,284			royalty_amount: Option<Permill>,285			metadata: RmrkString,286		) -> DispatchResult {287			let sender = ensure_signed(origin)?;288			let sender = T::CrossAccountId::from_sub(sender);289			let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292				recipient: recipient.unwrap_or_else(|| owner.clone()),293				amount,294			});295296			let collection = Self::get_typed_nft_collection(297				Self::unique_collection_id(collection_id)?,298				misc::CollectionType::Regular,299			)?;300301			let nft_id = Self::create_nft(302				&sender,303				&cross_owner,304				&collection,305				[306					Self::rmrk_property(TokenType, &NftType::Regular)?,307					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,308					Self::rmrk_property(Metadata, &metadata)?,309					Self::rmrk_property(Equipped, &false)?,310					Self::rmrk_property(311						ResourceCollection,312						&Self::init_collection(313							sender.clone(),314							CreateCollectionData {315								..Default::default()316							},317							[Self::rmrk_property(318								CollectionType,319								&misc::CollectionType::Resource,320							)?]321							.into_iter(),322						)?,323					)?, // todo possibly add limits to the collection if rmrk warrants them324					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,325				]326				.into_iter(),327			)328			.map_err(|err| match err {329				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),330				err => Self::map_common_err_to_proxy(err),331			})?;332333			Self::deposit_event(Event::NftMinted {334				owner,335				collection_id,336				nft_id: nft_id.0,337			});338339			Ok(())340		}341342		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343		#[transactional]344		pub fn burn_nft(345			origin: OriginFor<T>,346			collection_id: RmrkCollectionId,347			nft_id: RmrkNftId,348		) -> DispatchResult {349			let sender = ensure_signed(origin)?;350			let cross_sender = T::CrossAccountId::from_sub(sender.clone());351352			Self::destroy_nft(353				cross_sender,354				Self::unique_collection_id(collection_id)?,355				misc::CollectionType::Regular,356				nft_id.into(),357			)?;358359			Self::deposit_event(Event::NFTBurned {360				owner: sender,361				nft_id,362			});363364			Ok(())365		}366367		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]368		#[transactional]369		pub fn send(370			origin: OriginFor<T>,371			rmrk_collection_id: RmrkCollectionId,372			rmrk_nft_id: RmrkNftId,373			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,374		) -> DispatchResult {375			let sender = ensure_signed(origin.clone())?;376			let cross_sender = T::CrossAccountId::from_sub(sender.clone());377378			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;379			let nft_id = rmrk_nft_id.into();380381			let token_data = <TokenData<T>>::get((collection_id, nft_id))382				.ok_or(<Error<T>>::NoAvailableNftId)?;383384			let from = token_data.owner;385386			let collection = Self::get_typed_nft_collection(387				collection_id,388				misc::CollectionType::Regular,389			)?;390391			let budget = budget::Value::new(NESTING_BUDGET);392393			let target_owner;394395			match new_owner {396				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {397					target_owner = T::CrossAccountId::from_sub(account_id);398				},399				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {400					let target_collection_id = Self::unique_collection_id(target_collection_id)?;401402					target_owner = T::CrossTokenAddressMapping::token_to_address(403						target_collection_id,404						target_nft_id.into(),405					);406407					let spender = <PalletStructure<T>>::get_indirect_owner(408						target_collection_id,409						target_nft_id.into(),410						Some((collection_id, nft_id)),411						&budget,412					)?;413414					let is_approval_required = cross_sender != spender;415416					if is_approval_required {417						// FIXME418						// <PalletNft<T>>::set_allowance(419						// 	&collection,420						// 	&cross_sender,421						// 	nft_id,422						// 	Some(&spender),423						// 	&budget424						// ).map_err(Self::map_common_err_to_proxy)?;425426						return Ok(());427					}428				}429			}430431			<PalletNft<T>>::transfer_from(432				&collection,433				&cross_sender,434				&from,435				&target_owner,436				nft_id,437				&budget438			).map_err(Self::map_common_err_to_proxy)?;439440			Ok(())441		}442443		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444		#[transactional]445		pub fn set_property(446			origin: OriginFor<T>,447			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,448			maybe_nft_id: Option<RmrkNftId>,449			key: RmrkKeyString,450			value: RmrkValueString,451		) -> DispatchResult {452			let sender = ensure_signed(origin)?;453			let sender = T::CrossAccountId::from_sub(sender);454455			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;456			let budget = budget::Value::new(NESTING_BUDGET);457458			match maybe_nft_id {459				Some(nft_id) => {460					let token_id: TokenId = nft_id.into();461462					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;463					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;464465					<PalletNft<T>>::set_scoped_token_property(466						collection_id,467						token_id,468						PropertyScope::Rmrk,469						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,470					)?;471				}472				None => {473					let collection = Self::get_typed_nft_collection(474						collection_id,475						misc::CollectionType::Regular,476					)?;477478					Self::check_collection_owner(&collection, &sender)?;479480					<PalletCommon<T>>::set_scoped_collection_property(481						collection_id,482						PropertyScope::Rmrk,483						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,484					)?;485				}486			}487488			Self::deposit_event(Event::PropertySet {489				collection_id: rmrk_collection_id,490				maybe_nft_id,491				key,492				value,493			});494495			Ok(())496		}497498		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]499		#[transactional]500		pub fn add_basic_resource(501			origin: OriginFor<T>,502			collection_id: RmrkCollectionId,503			nft_id: RmrkNftId,504			resource: RmrkBasicResource,505		) -> DispatchResult {506			let sender = ensure_signed(origin.clone())?;507508			let resource_id = Self::resource_add(509				sender,510				Self::unique_collection_id(collection_id)?,511				nft_id.into(),512				[513					Self::rmrk_property(TokenType, &NftType::Resource)?,514					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,515					Self::rmrk_property(Src, &resource.src)?,516					Self::rmrk_property(Metadata, &resource.metadata)?,517					Self::rmrk_property(License, &resource.license)?,518					Self::rmrk_property(Thumb, &resource.thumb)?,519				]520				.into_iter(),521			)?;522523			Self::deposit_event(Event::ResourceAdded {524				nft_id,525				resource_id,526			});527			Ok(())528		}529530		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]531		#[transactional]532		pub fn add_composable_resource(533			origin: OriginFor<T>,534			collection_id: RmrkCollectionId,535			nft_id: RmrkNftId,536			_resource_id: RmrkBoundedResource,537			resource: RmrkComposableResource,538		) -> DispatchResult {539			let sender = ensure_signed(origin.clone())?;540541			let resource_id = Self::resource_add(542				sender,543				Self::unique_collection_id(collection_id)?,544				nft_id.into(),545				[546					Self::rmrk_property(TokenType, &NftType::Resource)?,547					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,548					Self::rmrk_property(Parts, &resource.parts)?,549					Self::rmrk_property(Base, &resource.base)?,550					Self::rmrk_property(Src, &resource.src)?,551					Self::rmrk_property(Metadata, &resource.metadata)?,552					Self::rmrk_property(License, &resource.license)?,553					Self::rmrk_property(Thumb, &resource.thumb)?,554				]555				.into_iter(),556			)?;557558			Self::deposit_event(Event::ResourceAdded {559				nft_id,560				resource_id,561			});562			Ok(())563		}564565		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]566		#[transactional]567		pub fn add_slot_resource(568			origin: OriginFor<T>,569			collection_id: RmrkCollectionId,570			nft_id: RmrkNftId,571			resource: RmrkSlotResource,572		) -> DispatchResult {573			let sender = ensure_signed(origin.clone())?;574575			let resource_id = Self::resource_add(576				sender,577				Self::unique_collection_id(collection_id)?,578				nft_id.into(),579				[580					Self::rmrk_property(TokenType, &NftType::Resource)?,581					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,582					Self::rmrk_property(Base, &resource.base)?,583					Self::rmrk_property(Src, &resource.src)?,584					Self::rmrk_property(Metadata, &resource.metadata)?,585					Self::rmrk_property(Slot, &resource.slot)?,586					Self::rmrk_property(License, &resource.license)?,587					Self::rmrk_property(Thumb, &resource.thumb)?,588				]589				.into_iter(),590			)?;591592			Self::deposit_event(Event::ResourceAdded {593				nft_id,594				resource_id,595			});596			Ok(())597		}598599		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]600		#[transactional]601		pub fn remove_resource(602			origin: OriginFor<T>,603			collection_id: RmrkCollectionId,604			nft_id: RmrkNftId,605			resource_id: RmrkResourceId,606		) -> DispatchResult {607			let sender = ensure_signed(origin.clone())?;608609			Self::resource_remove(610				sender,611				Self::unique_collection_id(collection_id)?,612				nft_id.into(),613				resource_id.into(),614			)?;615616			Self::deposit_event(Event::ResourceRemoval {617				nft_id,618				resource_id,619			});620			Ok(())621		}622	}623}624625impl<T: Config> Pallet<T> {626	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {627		let key = rmrk_key.to_key::<T>()?;628629		let scoped_key = PropertyScope::Rmrk630			.apply(key)631			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;632633		Ok(scoped_key)634	}635636	// todo think about renaming these637	pub fn rmrk_property<E: Encode>(638		rmrk_key: RmrkProperty,639		value: &E,640	) -> Result<Property, DispatchError> {641		let key = rmrk_key.to_key::<T>()?;642643		let value = value644			.encode()645			.try_into()646			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;647648		let property = Property { key, value };649650		Ok(property)651	}652653	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {654		vec.decode()655			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())656	}657658	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>659	where660		BoundedVec<u8, S>: TryFrom<Vec<u8>>,661	{662		vec.rebind()663			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())664	}665666	fn init_collection(667		sender: T::CrossAccountId,668		data: CreateCollectionData<T::AccountId>,669		properties: impl Iterator<Item = Property>,670	) -> Result<CollectionId, DispatchError> {671		let collection_id = <PalletNft<T>>::init_collection(sender, data);672673		if let Err(DispatchError::Arithmetic(_)) = &collection_id {674			return Err(<Error<T>>::NoAvailableCollectionId.into());675		}676677		<PalletCommon<T>>::set_scoped_collection_properties(678			collection_id?,679			PropertyScope::Rmrk,680			properties,681		)?;682683		collection_id684	}685686	pub fn create_nft(687		sender: &T::CrossAccountId,688		owner: &T::CrossAccountId,689		collection: &NonfungibleHandle<T>,690		properties: impl Iterator<Item = Property>,691	) -> Result<TokenId, DispatchError> {692		let data = CreateNftExData {693			properties: BoundedVec::default(),694			owner: owner.clone(),695		};696697		let budget = budget::Value::new(NESTING_BUDGET);698699		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;700701		let nft_id = <PalletNft<T>>::current_token_id(collection.id);702703		<PalletNft<T>>::set_scoped_token_properties(704			collection.id,705			nft_id,706			PropertyScope::Rmrk,707			properties,708		)?;709710		Ok(nft_id)711	}712713	fn destroy_nft(714		sender: T::CrossAccountId,715		collection_id: CollectionId,716		collection_type: misc::CollectionType,717		token_id: TokenId,718	) -> DispatchResult {719		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;720721		<PalletNft<T>>::burn(&collection, &sender, token_id)722			.map_err(Self::map_common_err_to_proxy)?;723724		Ok(())725	}726727	fn resource_add(728		sender: T::AccountId,729		collection_id: CollectionId,730		token_id: TokenId,731		resource_properties: impl Iterator<Item = Property>,732	) -> Result<RmrkResourceId, DispatchError> {733		let collection =734			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;735		ensure!(collection.owner == sender, Error::<T>::NoPermission);736737		// Check NFT lock status // todo depends on market, maybe later738		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);739740		let sender = T::CrossAccountId::from_sub(sender);741		let budget = budget::Value::new(NESTING_BUDGET);742		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();743744		let resource_collection_id: CollectionId =745			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;746		let resource_collection =747			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;748749		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them750751		let resource_id = Self::create_nft(752			&sender, // todo owner of the nft?753			&sender,754			&resource_collection,755			resource_properties.chain(756				[757					Self::rmrk_property(PendingResourceAccept, &pending)?,758					Self::rmrk_property(PendingResourceRemoval, &false)?,759				]760				.into_iter(),761			),762		)763		.map_err(|err| match err {764			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),765			err => Self::map_common_err_to_proxy(err),766		})?;767768		Ok(resource_id.0)769	}770771	fn resource_remove(772		sender: T::AccountId,773		collection_id: CollectionId,774		nft_id: TokenId,775		resource_id: TokenId,776	) -> DispatchResult {777		let collection =778			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;779		ensure!(collection.owner == sender, Error::<T>::NoPermission);780781		let resource_collection_id: CollectionId =782			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;783		let resource_collection =784			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;785		ensure!(786			<PalletNft<T>>::token_exists(&resource_collection, resource_id),787			Error::<T>::ResourceDoesntExist788		);789790		let budget = up_data_structs::budget::Value::new(10);791		let topmost_owner =792			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;793794		let sender = T::CrossAccountId::from_sub(sender);795		if topmost_owner == sender {796			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)797				.map_err(Self::map_common_err_to_proxy)?;798		} else {799			<PalletNft<T>>::set_scoped_token_property(800				resource_collection_id,801				resource_id,802				PropertyScope::Rmrk,803				Self::rmrk_property(PendingResourceRemoval, &true)?,804			)?;805		}806807		Ok(())808	}809810	fn change_collection_owner(811		collection_id: CollectionId,812		collection_type: misc::CollectionType,813		sender: T::AccountId,814		new_owner: T::AccountId,815	) -> DispatchResult {816		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;817		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;818819		let mut collection = collection.into_inner();820821		collection.owner = new_owner;822		collection.save()823	}824825	fn check_collection_owner(826		collection: &NonfungibleHandle<T>,827		account: &T::CrossAccountId,828	) -> DispatchResult {829		collection830			.check_is_owner(account)831			.map_err(Self::map_common_err_to_proxy)832	}833834	pub fn last_collection_idx() -> RmrkCollectionId {835		<CollectionIndex<T>>::get()836	}837838	pub fn unique_collection_id(839		rmrk_collection_id: RmrkCollectionId,840	) -> Result<CollectionId, DispatchError> {841		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)842			.map_err(|_| <Error<T>>::CollectionUnknown.into())843	}844845	pub fn rmrk_collection_id(846		unique_collection_id: CollectionId847	) -> Result<RmrkCollectionId, DispatchError> {848		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)849			.map_err(|_| <Error<T>>::CollectionUnknown.into())850	}851852	pub fn get_nft_collection(853		collection_id: CollectionId,854	) -> Result<NonfungibleHandle<T>, DispatchError> {855		let collection = <CollectionHandle<T>>::try_get(collection_id)856			.map_err(|_| <Error<T>>::CollectionUnknown)?;857858		match collection.mode {859			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),860			_ => Err(<Error<T>>::CollectionUnknown.into()),861		}862	}863864	pub fn collection_exists(collection_id: CollectionId) -> bool {865		<CollectionHandle<T>>::try_get(collection_id).is_ok()866	}867868	pub fn get_collection_property(869		collection_id: CollectionId,870		key: RmrkProperty,871	) -> Result<PropertyValue, DispatchError> {872		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)873			.get(&Self::rmrk_property_key(key)?)874			.ok_or(<Error<T>>::CollectionUnknown)?875			.clone();876877		Ok(collection_property)878	}879880	pub fn get_collection_property_decoded<V: Decode>(881		collection_id: CollectionId,882		key: RmrkProperty,883	) -> Result<V, DispatchError> {884		Self::decode_property(Self::get_collection_property(collection_id, key)?)885	}886887	pub fn get_collection_type(888		collection_id: CollectionId,889	) -> Result<misc::CollectionType, DispatchError> {890		Self::get_collection_property_decoded(collection_id, CollectionType)891			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())892	}893894	pub fn ensure_collection_type(895		collection_id: CollectionId,896		collection_type: misc::CollectionType,897	) -> DispatchResult {898		let actual_type = Self::get_collection_type(collection_id)?;899		ensure!(900			actual_type == collection_type,901			<CommonError<T>>::NoPermission902		);903904		Ok(())905	}906907	pub fn get_typed_nft_collection(908		collection_id: CollectionId,909		collection_type: misc::CollectionType,910	) -> Result<NonfungibleHandle<T>, DispatchError> {911		Self::ensure_collection_type(collection_id, collection_type)?;912913		Self::get_nft_collection(collection_id)914	}915916	pub fn get_typed_nft_collection_mapped(917		rmrk_collection_id: RmrkCollectionId,918		collection_type: misc::CollectionType,919	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {920		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;921922		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;923924		Ok((collection, unique_collection_id))925	}926927	pub fn get_nft_property(928		collection_id: CollectionId,929		nft_id: TokenId,930		key: RmrkProperty,931	) -> Result<PropertyValue, DispatchError> {932		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))933			.get(&Self::rmrk_property_key(key)?)934			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?935			.clone();936937		Ok(nft_property)938	}939940	pub fn get_nft_property_decoded<V: Decode>(941		collection_id: CollectionId,942		nft_id: TokenId,943		key: RmrkProperty,944	) -> Result<V, DispatchError> {945		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)946	}947948	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {949		<TokenData<T>>::contains_key((collection_id, nft_id))950	}951952	pub fn get_nft_type(953		collection_id: CollectionId,954		token_id: TokenId,955	) -> Result<NftType, DispatchError> {956		Self::get_nft_property_decoded(collection_id, token_id, TokenType)957			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())958	}959960	pub fn ensure_nft_type(961		collection_id: CollectionId,962		token_id: TokenId,963		nft_type: NftType,964	) -> DispatchResult {965		let actual_type = Self::get_nft_type(collection_id, token_id)?;966		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);967968		Ok(())969	}970971	pub fn ensure_nft_owner(972		collection_id: CollectionId,973		token_id: TokenId,974		possible_owner: &T::CrossAccountId,975		nesting_budget: &dyn budget::Budget976	) -> DispatchResult {977		let is_owned = <PalletStructure<T>>::check_indirectly_owned(978			possible_owner.clone(),979			collection_id,980			token_id,981			None,982			nesting_budget,983		)?;984985		ensure!(986			is_owned,987			<Error<T>>::NoPermission988		);989990		Ok(())991	}992993	pub fn filter_user_properties<Key, Value, R, Mapper>(994		collection_id: CollectionId,995		token_id: Option<TokenId>,996		filter_keys: Option<Vec<RmrkPropertyKey>>,997		mapper: Mapper,998	) -> Result<Vec<R>, DispatchError>999	where1000		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1001		Value: Decode + Default,1002		Mapper: Fn(Key, Value) -> R,1003	{1004		filter_keys1005			.map(|keys| {1006				let properties = keys1007					.into_iter()1008					.filter_map(|key| {1009						let key: Key = key.try_into().ok()?;10101011						let value = match token_id {1012							Some(token_id) => Self::get_nft_property_decoded(1013								collection_id,1014								token_id,1015								UserProperty(key.as_ref()),1016							),1017							None => Self::get_collection_property_decoded(1018								collection_id,1019								UserProperty(key.as_ref()),1020							),1021						}1022						.ok()?;10231024						Some(mapper(key, value))1025					})1026					.collect();10271028				Ok(properties)1029			})1030			.unwrap_or_else(|| {1031				let properties =1032					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10331034				Ok(properties)1035			})1036	}10371038	pub fn iterate_user_properties<Key, Value, R, Mapper>(1039		collection_id: CollectionId,1040		token_id: Option<TokenId>,1041		mapper: Mapper,1042	) -> Result<impl Iterator<Item = R>, DispatchError>1043	where1044		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1045		Value: Decode + Default,1046		Mapper: Fn(Key, Value) -> R,1047	{1048		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10491050		let properties = match token_id {1051			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1052			None => <PalletCommon<T>>::collection_properties(collection_id),1053		};10541055		let properties = properties.into_iter().filter_map(move |(key, value)| {1056			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10571058			let key: Key = key.to_vec().try_into().ok()?;1059			let value: Value = value.decode().ok()?;10601061			Some(mapper(key, value))1062		});10631064		Ok(properties)1065	}10661067	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {1068		map_common_err_to_proxy! {1069			match err {1070				NoPermission => NoPermission,1071				CollectionTokenLimitExceeded => CollectionFullOrLocked,1072				PublicMintingNotAllowed => NoPermission,1073				TokenNotFound => NoAvailableNftId,1074				ApprovedValueTooLow => NoPermission1075			}1076		}1077	}1078}
modifiedpallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -2,10 +2,10 @@
 use codec::{Encode, Decode, Error};
 
 #[macro_export]
-macro_rules! map_common_err_to_proxy {
-    (match $err:ident { $($common_err:ident => $proxy_err:ident),+ }) => {
+macro_rules! map_unique_err_to_proxy {
+    (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ }) => {
         $(
-            if $err == <CommonError<T>>::$common_err.into() {
+            if $err == <$unique_err_ty<T>>::$unique_err.into() {
                 return <Error<T>>::$proxy_err.into()
             } else
         )+ {
modifiedpallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -5,11 +5,13 @@
 	Metadata,
 	CollectionType,
 	TokenType,
+	Transferable,
 	RoyaltyInfo,
 	Equipped,
 	ResourceCollection,
 	ResourcePriorities,
 	ResourceType,
+	PendingNftAccept,
 	PendingResourceAccept,
 	PendingResourceRemoval,
 	Parts,
@@ -49,13 +51,15 @@
 			Self::Metadata => key!("metadata"),
 			Self::CollectionType => key!("collection-type"),
 			Self::TokenType => key!("token-type"),
+			Self::Transferable => key!("transferable"),
 			Self::RoyaltyInfo => key!("royalty-info"),
 			Self::Equipped => key!("equipped"),
 			Self::ResourceCollection => key!("resource-collection"),
 			Self::ResourcePriorities => key!("resource-priorities"),
 			Self::ResourceType => key!("resource-type"),
-			Self::PendingResourceAccept => key!("pending-accept"),
-			Self::PendingResourceRemoval => key!("pending-removal"),
+			Self::PendingNftAccept => key!("pending-nft-accept"),
+			Self::PendingResourceAccept => key!("pending-resource-accept"),
+			Self::PendingResourceRemoval => key!("pending-resource-removal"),
 			Self::Parts => key!("parts"),
 			Self::Base => key!("base"),
 			Self::Src => key!("src"),
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -149,7 +149,7 @@
 		})
 	}
 
-	pub fn get_indirect_owner(
+	pub fn get_checked_indirect_owner(
 		collection: CollectionId,
 		token: TokenId,
 		for_nest: Option<(CollectionId, TokenId)>,
@@ -203,7 +203,7 @@
 			None => user,
 		};
 
-		Self::get_indirect_owner(
+		Self::get_checked_indirect_owner(
 			collection,
 			token,
 			for_nest,
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -188,19 +188,17 @@
                         None => return Ok(None)
                     };
 
-                    let allowance = pallet_nonfungible::Allowance::<Runtime>::get((collection_id, nft_id));
-
                     Ok(Some(RmrkInstanceInfo {
                         owner: owner,
                         royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
                         metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
                         equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
-                        pending: allowance.is_some(),
+                        pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
                     }))
                 }
 
                 fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
-                    use pallet_proxy_rmrk_core::misc::CollectionType;
+                    use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
                     let cross_account_id = CrossAccountId::from_sub(account_id);
@@ -210,15 +208,26 @@
                         Err(_) => return Ok(Vec::new()),
                     };
 
-                    Ok(
-                        collection.account_tokens(cross_account_id)
-                            .into_iter()
-                            .map(|token| token.0)
-                            .collect()
-                    )
+                    let tokens = collection.account_tokens(cross_account_id)
+                        .into_iter()
+                        .filter(|token| {
+                            let is_pending = RmrkCore::get_nft_property_decoded(
+                                collection_id,
+                                *token,
+                                RmrkProperty::PendingNftAccept
+                            ).unwrap_or(true);
+
+                            !is_pending
+                        })
+                        .map(|token| token.0)
+                        .collect();
+
+                    Ok(tokens)
                 }
 
                 fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+                    use pallet_proxy_rmrk_core::RmrkProperty;
+
                     let collection_id = match RmrkCore::unique_collection_id(collection_id) {
                         Ok(id) => id,
                         Err(_) => return Ok(Vec::new())
@@ -229,6 +238,16 @@
                     Ok(
                         pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
                             .filter_map(|((child_collection, child_token), _)| {
+                                let is_pending = RmrkCore::get_nft_property_decoded(
+                                    child_collection,
+                                    child_token,
+                                    RmrkProperty::PendingNftAccept
+                                ).ok()?;
+
+                                if is_pending {
+                                    return None;
+                                }
+
                                 let rmrk_child_collection = RmrkCore::rmrk_collection_id(
                                     child_collection
                                 ).ok()?;
@@ -398,7 +417,7 @@
                         Ok(c) => c,
                         Err(_) => return Ok(Vec::new()),
                     };
-                    
+
                     let parts = collection.collection_tokens()
                         .into_iter()
                         .filter_map(|token_id| {