git.delta.rocks / unique-network / refs/commits / 23e7e864f113

difftreelog

Merge pull request #337 from UniqueNetwork/CORE-178

kozyrevdev2022-04-21parents: #41b4396 #ac711ec.patch.diff
in: master
feature/core-178

9 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -119,6 +119,15 @@
 	) -> Result<Option<Collection<AccountId>>>;
 	#[rpc(name = "unique_collectionStats")]
 	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
+
+	#[rpc(name = "unique_nextSponsored")]
+	fn next_sponsored(
+		&self,
+		collection: CollectionId,
+		account: CrossAccountId,
+		token: TokenId,
+		at: Option<BlockHash>,
+	) -> Result<Option<u64>>;
 	#[rpc(name = "unique_effectiveCollectionLimits")]
 	fn effective_collection_limits(
 		&self,
@@ -228,5 +237,6 @@
 	pass_method!(last_token_id(collection: CollectionId) -> TokenId);
 	pass_method!(collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>>);
 	pass_method!(collection_stats() -> CollectionStats);
+	pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>);
 	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>);
 }
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
before · pallets/unique/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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829pub use frame_support::{30	construct_runtime, decl_module, decl_storage, decl_error, decl_event,31	dispatch::DispatchResult,32	ensure, fail, parameter_types,33	traits::{34		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,35		IsSubType, WithdrawReasons,36	},37	weights::{38		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},39		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,40		WeightToFeePolynomial, DispatchClass,41	},42	StorageValue, transactional,43	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},44	BoundedVec,45};46use scale_info::TypeInfo;47use frame_system::{self as system, ensure_signed};48use sp_runtime::{sp_std::prelude::Vec};49use up_data_structs::{50	MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,51	OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,52	MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,53	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,54	CreateCollectionData, CustomDataLimit, CreateItemExData,55};56use pallet_common::{CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo};57use pallet_evm::account::CrossAccountId;58use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};59use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};60use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};6162#[cfg(test)]63mod mock;6465#[cfg(test)]66mod tests;6768mod eth;69mod sponsorship;70pub use sponsorship::UniqueSponsorshipHandler;71pub use eth::sponsoring::UniqueEthSponsorshipHandler;7273pub use eth::UniqueErcSupport;7475pub mod common;76use common::CommonWeights;77pub mod dispatch;78use dispatch::dispatch_call;7980#[cfg(feature = "runtime-benchmarks")]81mod benchmarking;82pub mod weights;83use weights::WeightInfo;8485decl_error! {86	/// Error for non-fungible-token module.87	pub enum Error for Module<T: Config> {88		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.89		CollectionDecimalPointLimitExceeded,90		/// This address is not set as sponsor, use setCollectionSponsor first.91		ConfirmUnsetSponsorFail,92		/// Length of items properties must be greater than 0.93		EmptyArgument,94	}95}9697pub trait Config:98	system::Config99	+ pallet_evm_coder_substrate::Config100	+ pallet_common::Config101	+ pallet_nonfungible::Config102	+ pallet_refungible::Config103	+ pallet_fungible::Config104	+ Sized105	+ TypeInfo106{107	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;108109	/// Weight information for extrinsics in this pallet.110	type WeightInfo: WeightInfo;111}112113decl_event! {114	pub enum Event<T>115	where116		<T as frame_system::Config>::AccountId,117		<T as pallet_evm::account::Config>::CrossAccountId,118	{119		/// Collection sponsor was removed120		///121		/// # Arguments122		///123		/// * collection_id: Globally unique collection identifier.124		CollectionSponsorRemoved(CollectionId),125126		/// Collection admin was added127		///128		/// # Arguments129		///130		/// * collection_id: Globally unique collection identifier.131		///132		/// * admin:  Admin address.133		CollectionAdminAdded(CollectionId, CrossAccountId),134135		/// Collection owned was change136		///137		/// # Arguments138		///139		/// * collection_id: Globally unique collection identifier.140		///141		/// * owner:  New owner address.142		CollectionOwnedChanged(CollectionId, AccountId),143144		/// Collection sponsor was set145		///146		/// # Arguments147		///148		/// * collection_id: Globally unique collection identifier.149		///150		/// * owner:  New sponsor address.151		CollectionSponsorSet(CollectionId, AccountId),152153		/// const on chain schema was set154		///155		/// # Arguments156		///157		/// * collection_id: Globally unique collection identifier.158		ConstOnChainSchemaSet(CollectionId),159160		/// New sponsor was confirm161		///162		/// # Arguments163		///164		/// * collection_id: Globally unique collection identifier.165		///166		/// * sponsor:  New sponsor address.167		SponsorshipConfirmed(CollectionId, AccountId),168169		/// Collection admin was removed170		///171		/// # Arguments172		///173		/// * collection_id: Globally unique collection identifier.174		///175		/// * admin:  Admin address.176		CollectionAdminRemoved(CollectionId, CrossAccountId),177178		/// Address was remove from allow list179		///180		/// # Arguments181		///182		/// * collection_id: Globally unique collection identifier.183		///184		/// * user:  Address.185		AllowListAddressRemoved(CollectionId, CrossAccountId),186187		/// Address was add to allow list188		///189		/// # Arguments190		///191		/// * collection_id: Globally unique collection identifier.192		///193		/// * user:  Address.194		AllowListAddressAdded(CollectionId, CrossAccountId),195196		/// Collection limits was set197		///198		/// # Arguments199		///200		/// * collection_id: Globally unique collection identifier.201		CollectionLimitSet(CollectionId),202203		/// Mint permission	was set204		///205		/// # Arguments206		///207		/// * collection_id: Globally unique collection identifier.208		MintPermissionSet(CollectionId),209210		/// Offchain schema was set211		///212		/// # Arguments213		///214		/// * collection_id: Globally unique collection identifier.215		OffchainSchemaSet(CollectionId),216217		/// Public access mode was set218		///219		/// # Arguments220		///221		/// * collection_id: Globally unique collection identifier.222		///223		/// * mode: New access state.224		PublicAccessModeSet(CollectionId, AccessMode),225226		/// Schema version was set227		///228		/// # Arguments229		///230		/// * collection_id: Globally unique collection identifier.231		SchemaVersionSet(CollectionId),232233		/// Variable on chain schema was set234		///235		/// # Arguments236		///237		/// * collection_id: Globally unique collection identifier.238		VariableOnChainSchemaSet(CollectionId),239	}240}241242type SelfWeightOf<T> = <T as Config>::WeightInfo;243244// # Used definitions245//246// ## User control levels247//248// chain-controlled - key is uncontrolled by user249//                    i.e autoincrementing index250//                    can use non-cryptographic hash251// real - key is controlled by user252//        but it is hard to generate enough colliding values, i.e owner of signed txs253//        can use non-cryptographic hash254// controlled - key is completly controlled by users255//              i.e maps with mutable keys256//              should use cryptographic hash257//258// ## User control level downgrade reasons259//260// ?1 - chain-controlled -> controlled261//      collections/tokens can be destroyed, resulting in massive holes262// ?2 - chain-controlled -> controlled263//      same as ?1, but can be only added, resulting in easier exploitation264// ?3 - real -> controlled265//      no confirmation required, so addresses can be easily generated266decl_storage! {267	trait Store for Module<T: Config> as Unique {268269		//#region Private members270		/// Used for migrations271		ChainVersion: u64;272		//#endregion273274		//#region Tokens transfer rate limit baskets275		/// (Collection id (controlled?2), who created (real))276		/// TODO: Off chain worker should remove from this map when collection gets removed277		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;278		/// Collection id (controlled?2), token id (controlled?2)279		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;280		/// Collection id (controlled?2), owning user (real)281		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;282		/// Collection id (controlled?2), token id (controlled?2)283		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;284		//#endregion285286		/// Variable metadata sponsoring287		/// Collection id (controlled?2), token id (controlled?2)288		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;289		/// Approval sponsoring290		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;291		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;292		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;293	}294}295296decl_module! {297	pub struct Module<T: Config> for enum Call298	where299		origin: T::Origin300	{301		type Error = Error<T>;302303		fn deposit_event() = default;304305		fn on_initialize(_now: T::BlockNumber) -> Weight {306			0307		}308309		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.310		///311		/// # Permissions312		///313		/// * Anyone.314		///315		/// # Arguments316		///317		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.318		///319		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.320		///321		/// * token_prefix: UTF-8 string with token prefix.322		///323		/// * mode: [CollectionMode] collection type and type dependent data.324		// returns collection ID325		#[weight = <SelfWeightOf<T>>::create_collection()]326		#[transactional]327		#[deprecated]328		pub fn create_collection(origin,329								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,330								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,331								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,332								 mode: CollectionMode) -> DispatchResult  {333			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {334				name: collection_name,335				description: collection_description,336				token_prefix,337				mode,338				..Default::default()339			};340			Self::create_collection_ex(origin, data)341		}342343		/// This method creates a collection344		///345		/// Prefer it to deprecated [`created_collection`] method346		#[weight = <SelfWeightOf<T>>::create_collection()]347		#[transactional]348		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {349			let owner = ensure_signed(origin)?;350351			let _id = match data.mode {352				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},353				CollectionMode::Fungible(decimal_points) => {354					// check params355					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);356					<PalletFungible<T>>::init_collection(owner, data)?357				}358				CollectionMode::ReFungible => {359					<PalletRefungible<T>>::init_collection(owner, data)?360				}361			};362363			Ok(())364		}365366		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.367		///368		/// # Permissions369		///370		/// * Collection Owner.371		///372		/// # Arguments373		///374		/// * collection_id: collection to destroy.375		#[weight = <SelfWeightOf<T>>::destroy_collection()]376		#[transactional]377		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {378			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);379380			let collection = <CollectionHandle<T>>::try_get(collection_id)?;381			collection.check_is_owner(&sender)?;382383			// =========384385			match collection.mode {386				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,387				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,388				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,389			}390391			<NftTransferBasket<T>>::remove_prefix(collection_id, None);392			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);393			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);394395			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);396			<NftApproveBasket<T>>::remove_prefix(collection_id, None);397			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);398			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);399400			Ok(())401		}402403		/// Add an address to allow list.404		///405		/// # Permissions406		///407		/// * Collection Owner408		/// * Collection Admin409		///410		/// # Arguments411		///412		/// * collection_id.413		///414		/// * address.415		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]416		#[transactional]417		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{418419			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);420			let collection = <CollectionHandle<T>>::try_get(collection_id)?;421422			<PalletCommon<T>>::toggle_allowlist(423				&collection,424				&sender,425				&address,426				true,427			)?;428429			Self::deposit_event(Event::<T>::AllowListAddressAdded(430				collection_id,431				address432			));433434			Ok(())435		}436437		/// Remove an address from allow list.438		///439		/// # Permissions440		///441		/// * Collection Owner442		/// * Collection Admin443		///444		/// # Arguments445		///446		/// * collection_id.447		///448		/// * address.449		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]450		#[transactional]451		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{452453			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454			let collection = <CollectionHandle<T>>::try_get(collection_id)?;455456			<PalletCommon<T>>::toggle_allowlist(457				&collection,458				&sender,459				&address,460				false,461			)?;462463			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(464				collection_id,465				address466			));467468			Ok(())469		}470471		/// Toggle between normal and allow list access for the methods with access for `Anyone`.472		///473		/// # Permissions474		///475		/// * Collection Owner.476		///477		/// # Arguments478		///479		/// * collection_id.480		///481		/// * mode: [AccessMode]482		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]483		#[transactional]484		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult485		{486			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);487488			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;489			target_collection.check_is_owner(&sender)?;490491			target_collection.access = mode.clone();492493			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(494				collection_id,495				mode496			));497498			target_collection.save()499		}500501		/// Allows Anyone to create tokens if:502		/// * Allow List is enabled, and503		/// * Address is added to allow list, and504		/// * This method was called with True parameter505		///506		/// # Permissions507		/// * Collection Owner508		///509		/// # Arguments510		///511		/// * collection_id.512		///513		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.514		#[weight = <SelfWeightOf<T>>::set_mint_permission()]515		#[transactional]516		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult517		{518			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);519520			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;521			target_collection.check_is_owner(&sender)?;522523			target_collection.mint_mode = mint_permission;524525			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(526				collection_id527			));528529			target_collection.save()530		}531532		/// Change the owner of the collection.533		///534		/// # Permissions535		///536		/// * Collection Owner.537		///538		/// # Arguments539		///540		/// * collection_id.541		///542		/// * new_owner.543		#[weight = <SelfWeightOf<T>>::change_collection_owner()]544		#[transactional]545		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {546547			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);548549			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;550			target_collection.check_is_owner(&sender)?;551552			target_collection.owner = new_owner.clone();553			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(554				collection_id,555				new_owner556			));557558			target_collection.save()559		}560561		/// Adds an admin of the Collection.562		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.563		///564		/// # Permissions565		///566		/// * Collection Owner.567		/// * Collection Admin.568		///569		/// # Arguments570		///571		/// * collection_id: ID of the Collection to add admin for.572		///573		/// * new_admin_id: Address of new admin to add.574		#[weight = <SelfWeightOf<T>>::add_collection_admin()]575		#[transactional]576		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {577			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578			let collection = <CollectionHandle<T>>::try_get(collection_id)?;579580			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(581				collection_id,582				new_admin_id.clone()583			));584585			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)586		}587588		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.589		///590		/// # Permissions591		///592		/// * Collection Owner.593		/// * Collection Admin.594		///595		/// # Arguments596		///597		/// * collection_id: ID of the Collection to remove admin for.598		///599		/// * account_id: Address of admin to remove.600		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]601		#[transactional]602		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {603			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604			let collection = <CollectionHandle<T>>::try_get(collection_id)?;605606			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(607				collection_id,608				account_id.clone()609			));610611			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)612		}613614		/// # Permissions615		///616		/// * Collection Owner617		///618		/// # Arguments619		///620		/// * collection_id.621		///622		/// * new_sponsor.623		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]624		#[transactional]625		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {626			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);627628			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;629			target_collection.check_is_owner(&sender)?;630631			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());632633			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(634				collection_id,635				new_sponsor636			));637638			target_collection.save()639		}640641		/// # Permissions642		///643		/// * Sponsor.644		///645		/// # Arguments646		///647		/// * collection_id.648		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]649		#[transactional]650		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {651			let sender = ensure_signed(origin)?;652653			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;654			ensure!(655				target_collection.sponsorship.pending_sponsor() == Some(&sender),656				Error::<T>::ConfirmUnsetSponsorFail657			);658659			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());660661			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(662				collection_id,663				sender664			));665666			target_collection.save()667		}668669		/// Switch back to pay-per-own-transaction model.670		///671		/// # Permissions672		///673		/// * Collection owner.674		///675		/// # Arguments676		///677		/// * collection_id.678		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]679		#[transactional]680		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {681			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682683			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;684			target_collection.check_is_owner(&sender)?;685686			target_collection.sponsorship = SponsorshipState::Disabled;687688			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(689				collection_id690			));691			target_collection.save()692		}693694		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.695		///696		/// # Permissions697		///698		/// * Collection Owner.699		/// * Collection Admin.700		/// * Anyone if701		///     * Allow List is enabled, and702		///     * Address is added to allow list, and703		///     * MintPermission is enabled (see SetMintPermission method)704		///705		/// # Arguments706		///707		/// * collection_id: ID of the collection.708		///709		/// * owner: Address, initial owner of the NFT.710		///711		/// * data: Token data to store on chain.712		#[weight = <CommonWeights<T>>::create_item()]713		#[transactional]714		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {715			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))718		}719720		/// This method creates multiple items in a collection created with CreateCollection method.721		///722		/// # Permissions723		///724		/// * Collection Owner.725		/// * Collection Admin.726		/// * Anyone if727		///     * Allow List is enabled, and728		///     * Address is added to allow list, and729		///     * MintPermission is enabled (see SetMintPermission method)730		///731		/// # Arguments732		///733		/// * collection_id: ID of the collection.734		///735		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].736		///737		/// * owner: Address, initial owner of the NFT.738		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]739		#[transactional]740		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {741			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);742			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743744			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))745		}746747		#[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]748		#[transactional]749		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {750			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751752			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))753		}754755		// TODO! transaction weight756757		/// Set transfers_enabled value for particular collection758		///759		/// # Permissions760		///761		/// * Collection Owner.762		///763		/// # Arguments764		///765		/// * collection_id: ID of the collection.766		///767		/// * value: New flag value.768		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]769		#[transactional]770		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {771			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;773			target_collection.check_is_owner(&sender)?;774775			// =========776777			target_collection.limits.transfers_enabled = Some(value);778			target_collection.save()779		}780781		/// Destroys a concrete instance of NFT.782		///783		/// # Permissions784		///785		/// * Collection Owner.786		/// * Collection Admin.787		/// * Current NFT Owner.788		///789		/// # Arguments790		///791		/// * collection_id: ID of the collection.792		///793		/// * item_id: ID of NFT to burn.794		#[weight = <CommonWeights<T>>::burn_item()]795		#[transactional]796		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {797			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);798799			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;800			if value == 1 {801				<NftTransferBasket<T>>::remove(collection_id, item_id);802				<NftApproveBasket<T>>::remove(collection_id, item_id);803			}804			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?805			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());806			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));807			Ok(post_info)808		}809810		/// Destroys a concrete instance of NFT on behalf of the owner811		/// See also: [`approve`]812		///813		/// # Permissions814		///815		/// * Collection Owner.816		/// * Collection Admin.817		/// * Current NFT Owner.818		///819		/// # Arguments820		///821		/// * collection_id: ID of the collection.822		///823		/// * item_id: ID of NFT to burn.824		///825		/// * from: owner of item826		#[weight = <CommonWeights<T>>::burn_from()]827		#[transactional]828		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {829			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);830831			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))832		}833834		/// Change ownership of the token.835		///836		/// # Permissions837		///838		/// * Collection Owner839		/// * Collection Admin840		/// * Current NFT owner841		///842		/// # Arguments843		///844		/// * recipient: Address of token recipient.845		///846		/// * collection_id.847		///848		/// * item_id: ID of the item849		///     * Non-Fungible Mode: Required.850		///     * Fungible Mode: Ignored.851		///     * Re-Fungible Mode: Required.852		///853		/// * value: Amount to transfer.854		///     * Non-Fungible Mode: Ignored855		///     * Fungible Mode: Must specify transferred amount856		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)857		#[weight = <CommonWeights<T>>::transfer()]858		#[transactional]859		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {860			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);861862			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))863		}864865		/// Set, change, or remove approved address to transfer the ownership of the NFT.866		///867		/// # Permissions868		///869		/// * Collection Owner870		/// * Collection Admin871		/// * Current NFT owner872		///873		/// # Arguments874		///875		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).876		///877		/// * collection_id.878		///879		/// * item_id: ID of the item.880		#[weight = <CommonWeights<T>>::approve()]881		#[transactional]882		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {883			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);884885			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))886		}887888		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.889		///890		/// # Permissions891		/// * Collection Owner892		/// * Collection Admin893		/// * Current NFT owner894		/// * Address approved by current NFT owner895		///896		/// # Arguments897		///898		/// * from: Address that owns token.899		///900		/// * recipient: Address of token recipient.901		///902		/// * collection_id.903		///904		/// * item_id: ID of the item.905		///906		/// * value: Amount to transfer.907		#[weight = <CommonWeights<T>>::transfer_from()]908		#[transactional]909		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {910			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);911912			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))913		}914915		/// Set off-chain data schema.916		///917		/// # Permissions918		///919		/// * Collection Owner920		/// * Collection Admin921		///922		/// # Arguments923		///924		/// * collection_id.925		///926		/// * schema: String representing the offchain data schema.927		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]928		#[transactional]929		pub fn set_variable_meta_data (930			origin,931			collection_id: CollectionId,932			item_id: TokenId,933			data: BoundedVec<u8, CustomDataLimit>,934		) -> DispatchResultWithPostInfo {935			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);936937			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))938		}939940		/// Set meta_update_permission value for particular collection941		///942		/// # Permissions943		///944		/// * Collection Owner.945		///946		/// # Arguments947		///948		/// * collection_id: ID of the collection.949		///950		/// * value: New flag value.951		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]952		#[transactional]953		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {954			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);955			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;956957			ensure!(958				target_collection.meta_update_permission != MetaUpdatePermission::None,959				<CommonError<T>>::MetadataFlagFrozen,960			);961			target_collection.check_is_owner(&sender)?;962963			target_collection.meta_update_permission = value;964965			target_collection.save()966		}967968		/// Set schema standard969		/// ImageURL970		/// Unique971		///972		/// # Permissions973		///974		/// * Collection Owner975		/// * Collection Admin976		///977		/// # Arguments978		///979		/// * collection_id.980		///981		/// * schema: SchemaVersion: enum982		#[weight = <SelfWeightOf<T>>::set_schema_version()]983		#[transactional]984		pub fn set_schema_version(985			origin,986			collection_id: CollectionId,987			version: SchemaVersion988		) -> DispatchResult {989			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);990			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;991			target_collection.check_is_owner_or_admin(&sender)?;992			target_collection.schema_version = version;993994			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(995				collection_id996			));997998			target_collection.save()999		}10001001		/// Set off-chain data schema.1002		///1003		/// # Permissions1004		///1005		/// * Collection Owner1006		/// * Collection Admin1007		///1008		/// # Arguments1009		///1010		/// * collection_id.1011		///1012		/// * schema: String representing the offchain data schema.1013		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1014		#[transactional]1015		pub fn set_offchain_schema(1016			origin,1017			collection_id: CollectionId,1018			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1019		) -> DispatchResult {1020			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1021			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1022			target_collection.check_is_owner_or_admin(&sender)?;10231024			target_collection.offchain_schema = schema;10251026			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1027				collection_id1028			));10291030			target_collection.save()1031		}10321033		/// Set const on-chain data schema.1034		///1035		/// # Permissions1036		///1037		/// * Collection Owner1038		/// * Collection Admin1039		///1040		/// # Arguments1041		///1042		/// * collection_id.1043		///1044		/// * schema: String representing the const on-chain data schema.1045		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1046		#[transactional]1047		pub fn set_const_on_chain_schema (1048			origin,1049			collection_id: CollectionId,1050			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1051		) -> DispatchResult {1052			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1053			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1054			target_collection.check_is_owner_or_admin(&sender)?;10551056			target_collection.const_on_chain_schema = schema;10571058			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1059				collection_id1060			));10611062			target_collection.save()1063		}10641065		/// Set variable on-chain data schema.1066		///1067		/// # Permissions1068		///1069		/// * Collection Owner1070		/// * Collection Admin1071		///1072		/// # Arguments1073		///1074		/// * collection_id.1075		///1076		/// * schema: String representing the variable on-chain data schema.1077		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1078		#[transactional]1079		pub fn set_variable_on_chain_schema (1080			origin,1081			collection_id: CollectionId,1082			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1083		) -> DispatchResult {1084			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1085			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1086			target_collection.check_is_owner_or_admin(&sender)?;10871088			target_collection.variable_on_chain_schema = schema;10891090			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1091				collection_id1092			));10931094			target_collection.save()1095		}10961097		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1098		#[transactional]1099		pub fn set_collection_limits(1100			origin,1101			collection_id: CollectionId,1102			new_limit: CollectionLimits,1103		) -> DispatchResult {1104			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1105			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1106			target_collection.check_is_owner(&sender)?;1107			let old_limit = &target_collection.limits;11081109			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11101111			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1112				collection_id1113			));11141115			target_collection.save()1116		}1117	}1118}
after · pallets/unique/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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829pub use frame_support::{30	construct_runtime, decl_module, decl_storage, decl_error, decl_event,31	dispatch::DispatchResult,32	ensure, fail, parameter_types,33	traits::{34		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,35		IsSubType, WithdrawReasons,36	},37	weights::{38		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},39		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,40		WeightToFeePolynomial, DispatchClass,41	},42	StorageValue, transactional,43	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},44	BoundedVec,45};46use scale_info::TypeInfo;47use frame_system::{self as system, ensure_signed};48use sp_runtime::{sp_std::prelude::Vec};49use up_data_structs::{50	MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,51	OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,52	MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,53	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,54	CreateCollectionData, CustomDataLimit, CreateItemExData,55};56use pallet_common::{CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo};57use pallet_evm::account::CrossAccountId;58use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};59use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};60use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};6162#[cfg(test)]63mod mock;6465#[cfg(test)]66mod tests;6768mod eth;69mod sponsorship;70pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};71pub use eth::sponsoring::UniqueEthSponsorshipHandler;7273pub use eth::UniqueErcSupport;7475pub mod common;76use common::CommonWeights;77pub mod dispatch;78use dispatch::dispatch_call;7980#[cfg(feature = "runtime-benchmarks")]81mod benchmarking;82pub mod weights;83use weights::WeightInfo;8485pub trait SponsorshipPredict<T: Config> {86	fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>87	where88		u64: From<<T as frame_system::Config>::BlockNumber>;89}9091decl_error! {92	/// Error for non-fungible-token module.93	pub enum Error for Module<T: Config> {94		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.95		CollectionDecimalPointLimitExceeded,96		/// This address is not set as sponsor, use setCollectionSponsor first.97		ConfirmUnsetSponsorFail,98		/// Length of items properties must be greater than 0.99		EmptyArgument,100	}101}102103pub trait Config:104	system::Config105	+ pallet_evm_coder_substrate::Config106	+ pallet_common::Config107	+ pallet_nonfungible::Config108	+ pallet_refungible::Config109	+ pallet_fungible::Config110	+ Sized111	+ TypeInfo112{113	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;114115	/// Weight information for extrinsics in this pallet.116	type WeightInfo: WeightInfo;117}118119decl_event! {120	pub enum Event<T>121	where122		<T as frame_system::Config>::AccountId,123		<T as pallet_evm::account::Config>::CrossAccountId,124	{125		/// Collection sponsor was removed126		///127		/// # Arguments128		///129		/// * collection_id: Globally unique collection identifier.130		CollectionSponsorRemoved(CollectionId),131132		/// Collection admin was added133		///134		/// # Arguments135		///136		/// * collection_id: Globally unique collection identifier.137		///138		/// * admin:  Admin address.139		CollectionAdminAdded(CollectionId, CrossAccountId),140141		/// Collection owned was change142		///143		/// # Arguments144		///145		/// * collection_id: Globally unique collection identifier.146		///147		/// * owner:  New owner address.148		CollectionOwnedChanged(CollectionId, AccountId),149150		/// Collection sponsor was set151		///152		/// # Arguments153		///154		/// * collection_id: Globally unique collection identifier.155		///156		/// * owner:  New sponsor address.157		CollectionSponsorSet(CollectionId, AccountId),158159		/// const on chain schema was set160		///161		/// # Arguments162		///163		/// * collection_id: Globally unique collection identifier.164		ConstOnChainSchemaSet(CollectionId),165166		/// New sponsor was confirm167		///168		/// # Arguments169		///170		/// * collection_id: Globally unique collection identifier.171		///172		/// * sponsor:  New sponsor address.173		SponsorshipConfirmed(CollectionId, AccountId),174175		/// Collection admin was removed176		///177		/// # Arguments178		///179		/// * collection_id: Globally unique collection identifier.180		///181		/// * admin:  Admin address.182		CollectionAdminRemoved(CollectionId, CrossAccountId),183184		/// Address was remove from allow list185		///186		/// # Arguments187		///188		/// * collection_id: Globally unique collection identifier.189		///190		/// * user:  Address.191		AllowListAddressRemoved(CollectionId, CrossAccountId),192193		/// Address was add to allow list194		///195		/// # Arguments196		///197		/// * collection_id: Globally unique collection identifier.198		///199		/// * user:  Address.200		AllowListAddressAdded(CollectionId, CrossAccountId),201202		/// Collection limits was set203		///204		/// # Arguments205		///206		/// * collection_id: Globally unique collection identifier.207		CollectionLimitSet(CollectionId),208209		/// Mint permission	was set210		///211		/// # Arguments212		///213		/// * collection_id: Globally unique collection identifier.214		MintPermissionSet(CollectionId),215216		/// Offchain schema was set217		///218		/// # Arguments219		///220		/// * collection_id: Globally unique collection identifier.221		OffchainSchemaSet(CollectionId),222223		/// Public access mode was set224		///225		/// # Arguments226		///227		/// * collection_id: Globally unique collection identifier.228		///229		/// * mode: New access state.230		PublicAccessModeSet(CollectionId, AccessMode),231232		/// Schema version was set233		///234		/// # Arguments235		///236		/// * collection_id: Globally unique collection identifier.237		SchemaVersionSet(CollectionId),238239		/// Variable on chain schema was set240		///241		/// # Arguments242		///243		/// * collection_id: Globally unique collection identifier.244		VariableOnChainSchemaSet(CollectionId),245	}246}247248type SelfWeightOf<T> = <T as Config>::WeightInfo;249250// # Used definitions251//252// ## User control levels253//254// chain-controlled - key is uncontrolled by user255//                    i.e autoincrementing index256//                    can use non-cryptographic hash257// real - key is controlled by user258//        but it is hard to generate enough colliding values, i.e owner of signed txs259//        can use non-cryptographic hash260// controlled - key is completly controlled by users261//              i.e maps with mutable keys262//              should use cryptographic hash263//264// ## User control level downgrade reasons265//266// ?1 - chain-controlled -> controlled267//      collections/tokens can be destroyed, resulting in massive holes268// ?2 - chain-controlled -> controlled269//      same as ?1, but can be only added, resulting in easier exploitation270// ?3 - real -> controlled271//      no confirmation required, so addresses can be easily generated272decl_storage! {273	trait Store for Module<T: Config> as Unique {274275		//#region Private members276		/// Used for migrations277		ChainVersion: u64;278		//#endregion279280		//#region Tokens transfer rate limit baskets281		/// (Collection id (controlled?2), who created (real))282		/// TODO: Off chain worker should remove from this map when collection gets removed283		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;284		/// Collection id (controlled?2), token id (controlled?2)285		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;286		/// Collection id (controlled?2), owning user (real)287		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;288		/// Collection id (controlled?2), token id (controlled?2)289		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;290		//#endregion291292		/// Variable metadata sponsoring293		/// Collection id (controlled?2), token id (controlled?2)294		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;295		/// Approval sponsoring296		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;297		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;298		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;299	}300}301302decl_module! {303	pub struct Module<T: Config> for enum Call304	where305		origin: T::Origin306	{307		type Error = Error<T>;308309		fn deposit_event() = default;310311		fn on_initialize(_now: T::BlockNumber) -> Weight {312			0313		}314315		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.316		///317		/// # Permissions318		///319		/// * Anyone.320		///321		/// # Arguments322		///323		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.324		///325		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.326		///327		/// * token_prefix: UTF-8 string with token prefix.328		///329		/// * mode: [CollectionMode] collection type and type dependent data.330		// returns collection ID331		#[weight = <SelfWeightOf<T>>::create_collection()]332		#[transactional]333		#[deprecated]334		pub fn create_collection(origin,335								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,336								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,337								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,338								 mode: CollectionMode) -> DispatchResult  {339			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {340				name: collection_name,341				description: collection_description,342				token_prefix,343				mode,344				..Default::default()345			};346			Self::create_collection_ex(origin, data)347		}348349		/// This method creates a collection350		///351		/// Prefer it to deprecated [`created_collection`] method352		#[weight = <SelfWeightOf<T>>::create_collection()]353		#[transactional]354		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {355			let owner = ensure_signed(origin)?;356357			let _id = match data.mode {358				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},359				CollectionMode::Fungible(decimal_points) => {360					// check params361					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);362					<PalletFungible<T>>::init_collection(owner, data)?363				}364				CollectionMode::ReFungible => {365					<PalletRefungible<T>>::init_collection(owner, data)?366				}367			};368369			Ok(())370		}371372		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.373		///374		/// # Permissions375		///376		/// * Collection Owner.377		///378		/// # Arguments379		///380		/// * collection_id: collection to destroy.381		#[weight = <SelfWeightOf<T>>::destroy_collection()]382		#[transactional]383		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {384			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);385386			let collection = <CollectionHandle<T>>::try_get(collection_id)?;387			collection.check_is_owner(&sender)?;388389			// =========390391			match collection.mode {392				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,393				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,394				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,395			}396397			<NftTransferBasket<T>>::remove_prefix(collection_id, None);398			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);399			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);400401			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);402			<NftApproveBasket<T>>::remove_prefix(collection_id, None);403			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);404			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);405406			Ok(())407		}408409		/// Add an address to allow list.410		///411		/// # Permissions412		///413		/// * Collection Owner414		/// * Collection Admin415		///416		/// # Arguments417		///418		/// * collection_id.419		///420		/// * address.421		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]422		#[transactional]423		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{424425			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);426			let collection = <CollectionHandle<T>>::try_get(collection_id)?;427428			<PalletCommon<T>>::toggle_allowlist(429				&collection,430				&sender,431				&address,432				true,433			)?;434435			Self::deposit_event(Event::<T>::AllowListAddressAdded(436				collection_id,437				address438			));439440			Ok(())441		}442443		/// Remove an address from allow list.444		///445		/// # Permissions446		///447		/// * Collection Owner448		/// * Collection Admin449		///450		/// # Arguments451		///452		/// * collection_id.453		///454		/// * address.455		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]456		#[transactional]457		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{458459			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);460			let collection = <CollectionHandle<T>>::try_get(collection_id)?;461462			<PalletCommon<T>>::toggle_allowlist(463				&collection,464				&sender,465				&address,466				false,467			)?;468469			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(470				collection_id,471				address472			));473474			Ok(())475		}476477		/// Toggle between normal and allow list access for the methods with access for `Anyone`.478		///479		/// # Permissions480		///481		/// * Collection Owner.482		///483		/// # Arguments484		///485		/// * collection_id.486		///487		/// * mode: [AccessMode]488		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]489		#[transactional]490		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult491		{492			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);493494			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;495			target_collection.check_is_owner(&sender)?;496497			target_collection.access = mode.clone();498499			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(500				collection_id,501				mode502			));503504			target_collection.save()505		}506507		/// Allows Anyone to create tokens if:508		/// * Allow List is enabled, and509		/// * Address is added to allow list, and510		/// * This method was called with True parameter511		///512		/// # Permissions513		/// * Collection Owner514		///515		/// # Arguments516		///517		/// * collection_id.518		///519		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.520		#[weight = <SelfWeightOf<T>>::set_mint_permission()]521		#[transactional]522		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult523		{524			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);525526			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;527			target_collection.check_is_owner(&sender)?;528529			target_collection.mint_mode = mint_permission;530531			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(532				collection_id533			));534535			target_collection.save()536		}537538		/// Change the owner of the collection.539		///540		/// # Permissions541		///542		/// * Collection Owner.543		///544		/// # Arguments545		///546		/// * collection_id.547		///548		/// * new_owner.549		#[weight = <SelfWeightOf<T>>::change_collection_owner()]550		#[transactional]551		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {552553			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;556			target_collection.check_is_owner(&sender)?;557558			target_collection.owner = new_owner.clone();559			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(560				collection_id,561				new_owner562			));563564			target_collection.save()565		}566567		/// Adds an admin of the Collection.568		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.569		///570		/// # Permissions571		///572		/// * Collection Owner.573		/// * Collection Admin.574		///575		/// # Arguments576		///577		/// * collection_id: ID of the Collection to add admin for.578		///579		/// * new_admin_id: Address of new admin to add.580		#[weight = <SelfWeightOf<T>>::add_collection_admin()]581		#[transactional]582		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {583			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);584			let collection = <CollectionHandle<T>>::try_get(collection_id)?;585586			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(587				collection_id,588				new_admin_id.clone()589			));590591			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)592		}593594		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.595		///596		/// # Permissions597		///598		/// * Collection Owner.599		/// * Collection Admin.600		///601		/// # Arguments602		///603		/// * collection_id: ID of the Collection to remove admin for.604		///605		/// * account_id: Address of admin to remove.606		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]607		#[transactional]608		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let collection = <CollectionHandle<T>>::try_get(collection_id)?;611612			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(613				collection_id,614				account_id.clone()615			));616617			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)618		}619620		/// # Permissions621		///622		/// * Collection Owner623		///624		/// # Arguments625		///626		/// * collection_id.627		///628		/// * new_sponsor.629		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]630		#[transactional]631		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {632			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633634			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;635			target_collection.check_is_owner(&sender)?;636637			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());638639			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(640				collection_id,641				new_sponsor642			));643644			target_collection.save()645		}646647		/// # Permissions648		///649		/// * Sponsor.650		///651		/// # Arguments652		///653		/// * collection_id.654		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]655		#[transactional]656		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {657			let sender = ensure_signed(origin)?;658659			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;660			ensure!(661				target_collection.sponsorship.pending_sponsor() == Some(&sender),662				Error::<T>::ConfirmUnsetSponsorFail663			);664665			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());666667			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(668				collection_id,669				sender670			));671672			target_collection.save()673		}674675		/// Switch back to pay-per-own-transaction model.676		///677		/// # Permissions678		///679		/// * Collection owner.680		///681		/// # Arguments682		///683		/// * collection_id.684		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]685		#[transactional]686		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {687			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);688689			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;690			target_collection.check_is_owner(&sender)?;691692			target_collection.sponsorship = SponsorshipState::Disabled;693694			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(695				collection_id696			));697			target_collection.save()698		}699700		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.701		///702		/// # Permissions703		///704		/// * Collection Owner.705		/// * Collection Admin.706		/// * Anyone if707		///     * Allow List is enabled, and708		///     * Address is added to allow list, and709		///     * MintPermission is enabled (see SetMintPermission method)710		///711		/// # Arguments712		///713		/// * collection_id: ID of the collection.714		///715		/// * owner: Address, initial owner of the NFT.716		///717		/// * data: Token data to store on chain.718		#[weight = <CommonWeights<T>>::create_item()]719		#[transactional]720		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {721			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);722723			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))724		}725726		/// This method creates multiple items in a collection created with CreateCollection method.727		///728		/// # Permissions729		///730		/// * Collection Owner.731		/// * Collection Admin.732		/// * Anyone if733		///     * Allow List is enabled, and734		///     * Address is added to allow list, and735		///     * MintPermission is enabled (see SetMintPermission method)736		///737		/// # Arguments738		///739		/// * collection_id: ID of the collection.740		///741		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].742		///743		/// * owner: Address, initial owner of the NFT.744		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]745		#[transactional]746		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {747			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);748			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);749750			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))751		}752753		#[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]754		#[transactional]755		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {756			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);757758			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))759		}760761		// TODO! transaction weight762763		/// Set transfers_enabled value for particular collection764		///765		/// # Permissions766		///767		/// * Collection Owner.768		///769		/// # Arguments770		///771		/// * collection_id: ID of the collection.772		///773		/// * value: New flag value.774		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]775		#[transactional]776		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {777			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;779			target_collection.check_is_owner(&sender)?;780781			// =========782783			target_collection.limits.transfers_enabled = Some(value);784			target_collection.save()785		}786787		/// Destroys a concrete instance of NFT.788		///789		/// # Permissions790		///791		/// * Collection Owner.792		/// * Collection Admin.793		/// * Current NFT Owner.794		///795		/// # Arguments796		///797		/// * collection_id: ID of the collection.798		///799		/// * item_id: ID of NFT to burn.800		#[weight = <CommonWeights<T>>::burn_item()]801		#[transactional]802		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {803			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);804805			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;806			if value == 1 {807				<NftTransferBasket<T>>::remove(collection_id, item_id);808				<NftApproveBasket<T>>::remove(collection_id, item_id);809			}810			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?811			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());812			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));813			Ok(post_info)814		}815816		/// Destroys a concrete instance of NFT on behalf of the owner817		/// See also: [`approve`]818		///819		/// # Permissions820		///821		/// * Collection Owner.822		/// * Collection Admin.823		/// * Current NFT Owner.824		///825		/// # Arguments826		///827		/// * collection_id: ID of the collection.828		///829		/// * item_id: ID of NFT to burn.830		///831		/// * from: owner of item832		#[weight = <CommonWeights<T>>::burn_from()]833		#[transactional]834		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {835			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);836837			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))838		}839840		/// Change ownership of the token.841		///842		/// # Permissions843		///844		/// * Collection Owner845		/// * Collection Admin846		/// * Current NFT owner847		///848		/// # Arguments849		///850		/// * recipient: Address of token recipient.851		///852		/// * collection_id.853		///854		/// * item_id: ID of the item855		///     * Non-Fungible Mode: Required.856		///     * Fungible Mode: Ignored.857		///     * Re-Fungible Mode: Required.858		///859		/// * value: Amount to transfer.860		///     * Non-Fungible Mode: Ignored861		///     * Fungible Mode: Must specify transferred amount862		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)863		#[weight = <CommonWeights<T>>::transfer()]864		#[transactional]865		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {866			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);867868			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))869		}870871		/// Set, change, or remove approved address to transfer the ownership of the NFT.872		///873		/// # Permissions874		///875		/// * Collection Owner876		/// * Collection Admin877		/// * Current NFT owner878		///879		/// # Arguments880		///881		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).882		///883		/// * collection_id.884		///885		/// * item_id: ID of the item.886		#[weight = <CommonWeights<T>>::approve()]887		#[transactional]888		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {889			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);890891			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))892		}893894		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.895		///896		/// # Permissions897		/// * Collection Owner898		/// * Collection Admin899		/// * Current NFT owner900		/// * Address approved by current NFT owner901		///902		/// # Arguments903		///904		/// * from: Address that owns token.905		///906		/// * recipient: Address of token recipient.907		///908		/// * collection_id.909		///910		/// * item_id: ID of the item.911		///912		/// * value: Amount to transfer.913		#[weight = <CommonWeights<T>>::transfer_from()]914		#[transactional]915		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {916			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);917918			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))919		}920921		/// Set off-chain data schema.922		///923		/// # Permissions924		///925		/// * Collection Owner926		/// * Collection Admin927		///928		/// # Arguments929		///930		/// * collection_id.931		///932		/// * schema: String representing the offchain data schema.933		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]934		#[transactional]935		pub fn set_variable_meta_data (936			origin,937			collection_id: CollectionId,938			item_id: TokenId,939			data: BoundedVec<u8, CustomDataLimit>,940		) -> DispatchResultWithPostInfo {941			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);942943			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))944		}945946		/// Set meta_update_permission value for particular collection947		///948		/// # Permissions949		///950		/// * Collection Owner.951		///952		/// # Arguments953		///954		/// * collection_id: ID of the collection.955		///956		/// * value: New flag value.957		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]958		#[transactional]959		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {960			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);961			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;962963			ensure!(964				target_collection.meta_update_permission != MetaUpdatePermission::None,965				<CommonError<T>>::MetadataFlagFrozen,966			);967			target_collection.check_is_owner(&sender)?;968969			target_collection.meta_update_permission = value;970971			target_collection.save()972		}973974		/// Set schema standard975		/// ImageURL976		/// Unique977		///978		/// # Permissions979		///980		/// * Collection Owner981		/// * Collection Admin982		///983		/// # Arguments984		///985		/// * collection_id.986		///987		/// * schema: SchemaVersion: enum988		#[weight = <SelfWeightOf<T>>::set_schema_version()]989		#[transactional]990		pub fn set_schema_version(991			origin,992			collection_id: CollectionId,993			version: SchemaVersion994		) -> DispatchResult {995			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);996			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;997			target_collection.check_is_owner_or_admin(&sender)?;998			target_collection.schema_version = version;9991000			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(1001				collection_id1002			));10031004			target_collection.save()1005		}10061007		/// Set off-chain data schema.1008		///1009		/// # Permissions1010		///1011		/// * Collection Owner1012		/// * Collection Admin1013		///1014		/// # Arguments1015		///1016		/// * collection_id.1017		///1018		/// * schema: String representing the offchain data schema.1019		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1020		#[transactional]1021		pub fn set_offchain_schema(1022			origin,1023			collection_id: CollectionId,1024			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1025		) -> DispatchResult {1026			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1027			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1028			target_collection.check_is_owner_or_admin(&sender)?;10291030			target_collection.offchain_schema = schema;10311032			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1033				collection_id1034			));10351036			target_collection.save()1037		}10381039		/// Set const on-chain data schema.1040		///1041		/// # Permissions1042		///1043		/// * Collection Owner1044		/// * Collection Admin1045		///1046		/// # Arguments1047		///1048		/// * collection_id.1049		///1050		/// * schema: String representing the const on-chain data schema.1051		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1052		#[transactional]1053		pub fn set_const_on_chain_schema (1054			origin,1055			collection_id: CollectionId,1056			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1057		) -> DispatchResult {1058			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1059			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1060			target_collection.check_is_owner_or_admin(&sender)?;10611062			target_collection.const_on_chain_schema = schema;10631064			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1065				collection_id1066			));10671068			target_collection.save()1069		}10701071		/// Set variable on-chain data schema.1072		///1073		/// # Permissions1074		///1075		/// * Collection Owner1076		/// * Collection Admin1077		///1078		/// # Arguments1079		///1080		/// * collection_id.1081		///1082		/// * schema: String representing the variable on-chain data schema.1083		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1084		#[transactional]1085		pub fn set_variable_on_chain_schema (1086			origin,1087			collection_id: CollectionId,1088			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1089		) -> DispatchResult {1090			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1091			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1092			target_collection.check_is_owner_or_admin(&sender)?;10931094			target_collection.variable_on_chain_schema = schema;10951096			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1097				collection_id1098			));10991100			target_collection.save()1101		}11021103		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1104		#[transactional]1105		pub fn set_collection_limits(1106			origin,1107			collection_id: CollectionId,1108			new_limit: CollectionLimits,1109		) -> DispatchResult {1110			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1111			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1112			target_collection.check_is_owner(&sender)?;1113			let old_limit = &target_collection.limits;11141115			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11161117			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1118				collection_id1119			));11201121			target_collection.save()1122		}1123	}1124}
modifiedpallets/unique/src/sponsorship.rsdiffbeforeafterboth
--- a/pallets/unique/src/sponsorship.rs
+++ b/pallets/unique/src/sponsorship.rs
@@ -29,6 +29,7 @@
 	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,
 	NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,
 };
+use sp_runtime::traits::Saturating;
 use pallet_common::{CollectionHandle};
 use pallet_evm::account::CrossAccountId;
 
@@ -301,3 +302,65 @@
 		}
 	}
 }
+
+use crate::SponsorshipPredict;
+use up_data_structs::SponsorshipState;
+pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
+
+impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>
+where
+	T: Config,
+{
+	fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
+	where
+		u64: From<<T as frame_system::Config>::BlockNumber>,
+	{
+		let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
+		let _ = collection.sponsorship.sponsor()?;
+
+		// sponsor timeout
+		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+		let limit = collection
+			.limits
+			.sponsor_transfer_timeout(match collection.mode {
+				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			});
+
+		let last_tx_block = match collection.mode {
+			CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),
+			CollectionMode::Fungible(_) => {
+				<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+			}
+			CollectionMode::ReFungible => {
+				<ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))
+			}
+		};
+
+		if let Some(last_tx_block) = last_tx_block {
+			return Some(
+				last_tx_block
+					.saturating_add(limit.into())
+					.saturating_sub(block_number)
+					.into(),
+			);
+		}
+
+		let token_exists = match collection.mode {
+			CollectionMode::NFT => {
+				<pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
+			}
+			CollectionMode::Fungible(_) => true,
+			CollectionMode::ReFungible => {
+				<pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
+			}
+		};
+
+		if token_exists {
+			Some(0)
+		} else {
+			None
+		}
+	}
+}
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -59,6 +59,7 @@
 		fn last_token_id(collection: CollectionId) -> Result<TokenId>;
 		fn collection_by_id(collection: CollectionId) -> Result<Option<Collection<AccountId>>>;
 		fn collection_stats() -> Result<CollectionStats>;
+		fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>>;
 		fn effective_collection_limits(collection_id: CollectionId) -> Result<Option<CollectionLimits>>;
 	}
 }
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -69,6 +69,13 @@
                 fn collection_stats() -> Result<CollectionStats, DispatchError> {
                     Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
                 }
+                fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
+                    Ok(<pallet_unique::UniqueSponsorshipPredict<Runtime> as
+                            pallet_unique::SponsorshipPredict<Runtime>>::predict(
+                        collection,
+                        account,
+                        token))
+                }
 
                 fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
                     Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -623,6 +623,10 @@
        * Get token variable metadata
        **/
       variableMetadata: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Bytes>>;
+      /**
+       * nextSponsored transaction
+       **/
+      nextSponsored: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array,  account:  AccountId | string | Uint8Array | { Substrate: any } | { Ethereum: any } | string | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<u64>>>;
     };
     web3: {
       /**
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -55,6 +55,7 @@
     collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsCollection>'),
     collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
     allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
+    nextSponsored: fun('Get number of blocks when sponsored transaction is available', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),
     effectiveCollectionLimits: fun('Get effective collection limits', [collectionParam], 'Option<UpDataStructsCollectionLimits>'),
   },
 };
addedtests/src/nextSponsoring.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/nextSponsoring.test.ts
@@ -0,0 +1,110 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {ApiPromise} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import {default as usingApi} from './substrate/substrate-api';
+import {
+  createCollectionExpectSuccess,
+  setCollectionSponsorExpectSuccess,
+  confirmSponsorshipExpectSuccess,
+  createItemExpectSuccess,
+  transferExpectSuccess,
+  normalizeAccountId,
+  getNextSponsored,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+
+
+describe('Integration Test getNextSponsored(collection_id, owner, item_id):', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+    });
+  });
+
+  it('NFT', async () => {
+    await usingApi(async (api: ApiPromise) => {
+
+      // Not existing collection 
+      expect(await getNextSponsored(api, 0, normalizeAccountId(alice), 0)).to.be.equal(-1);
+
+      const collectionId = await createCollectionExpectSuccess();
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+      // Check with Disabled sponsoring state
+      expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(-1);
+      await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+
+      // Check with Unconfirmed sponsoring state
+      expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(-1);
+      await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+      // Check with Confirmed sponsoring state
+      expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(0);
+
+      // After transfer
+      await transferExpectSuccess(collectionId, itemId, alice, bob, 1);
+      expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(5);
+
+      // Not existing token 
+      expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId+1)).to.be.equal(-1);
+    });
+  });
+
+  it('Fungible', async () => {
+    await usingApi(async (api: ApiPromise) => {
+
+      const createMode = 'Fungible';
+      const funCollectionId = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 0}});
+      await createItemExpectSuccess(alice, funCollectionId, createMode);
+      await setCollectionSponsorExpectSuccess(funCollectionId, bob.address);
+      await confirmSponsorshipExpectSuccess(funCollectionId, '//Bob');
+      expect(await getNextSponsored(api, funCollectionId, normalizeAccountId(alice), 0)).to.be.equal(0);
+
+      await transferExpectSuccess(funCollectionId, 0, alice, bob, 10, 'Fungible');
+      expect(await getNextSponsored(api, funCollectionId, normalizeAccountId(alice), 0)).to.be.equal(5);
+    });
+  });
+
+  it('ReFungible', async () => {
+    await usingApi(async (api: ApiPromise) => {
+
+      const createMode = 'ReFungible';
+      const refunCollectionId = await createCollectionExpectSuccess({mode: {type: createMode}});
+      const refunItemId = await createItemExpectSuccess(alice, refunCollectionId, createMode);
+      await setCollectionSponsorExpectSuccess(refunCollectionId, bob.address);
+      await confirmSponsorshipExpectSuccess(refunCollectionId, '//Bob');
+      expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId)).to.be.equal(0);
+
+      await transferExpectSuccess(refunCollectionId, refunItemId, alice, bob, 10, 'ReFungible');
+      expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId)).to.be.equal(5);
+
+      // Not existing token 
+      expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId+1)).to.be.equal(-1);
+    });
+  });
+});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -608,6 +608,15 @@
   });
 }
 
+export async function getNextSponsored(
+  api: ApiPromise,
+  collectionId: number,
+  account: string | CrossAccountId,
+  tokenId: number,
+): Promise<number> {
+  return Number((await api.rpc.unique.nextSponsored(collectionId, account, tokenId)).unwrapOr(-1));
+}
+
 export async function toggleContractAllowlistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) {
   await usingApi(async (api) => {
     const tx = api.tx.unique.toggleContractAllowList(contractAddress, value);