git.delta.rocks / unique-network / refs/commits / 8bbc7539fcb3

difftreelog

CORE-178

str-mv2022-04-08parent: #219f5f0.patch.diff
in: master

7 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>>;
 }
 
 pub struct Unique<C, P> {
@@ -222,4 +231,5 @@
 	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>);
 }
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::{57	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,58	CommonWeightInfo,59};60use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};61use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};62use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};6364#[cfg(test)]65mod mock;6667#[cfg(test)]68mod tests;6970mod eth;71mod sponsorship;72pub use sponsorship::UniqueSponsorshipHandler;73pub use eth::sponsoring::UniqueEthSponsorshipHandler;7475pub use eth::UniqueErcSupport;7677pub mod common;78use common::CommonWeights;79pub mod dispatch;80use dispatch::dispatch_call;8182#[cfg(feature = "runtime-benchmarks")]83mod benchmarking;84pub mod weights;85use weights::WeightInfo;8687decl_error! {88	/// Error for non-fungible-token module.89	pub enum Error for Module<T: Config> {90		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.91		CollectionDecimalPointLimitExceeded,92		/// This address is not set as sponsor, use setCollectionSponsor first.93		ConfirmUnsetSponsorFail,94		/// Length of items properties must be greater than 0.95		EmptyArgument,96	}97}9899pub trait Config:100	system::Config101	+ pallet_evm_coder_substrate::Config102	+ pallet_common::Config103	+ pallet_nonfungible::Config104	+ pallet_refungible::Config105	+ pallet_fungible::Config106	+ Sized107	+ TypeInfo108{109	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;110111	/// Weight information for extrinsics in this pallet.112	type WeightInfo: WeightInfo;113}114115decl_event! {116	pub enum Event<T>117	where118		<T as frame_system::Config>::AccountId,119		<T as pallet_common::Config>::CrossAccountId,120	{121		/// Collection sponsor was removed122		///123		/// # Arguments124		///125		/// * collection_id: Globally unique collection identifier.126		CollectionSponsorRemoved(CollectionId),127128		/// Collection admin was added129		///130		/// # Arguments131		///132		/// * collection_id: Globally unique collection identifier.133		///134		/// * admin:  Admin address.135		CollectionAdminAdded(CollectionId, CrossAccountId),136137		/// Collection owned was change138		///139		/// # Arguments140		///141		/// * collection_id: Globally unique collection identifier.142		///143		/// * owner:  New owner address.144		CollectionOwnedChanged(CollectionId, AccountId),145146		/// Collection sponsor was set147		///148		/// # Arguments149		///150		/// * collection_id: Globally unique collection identifier.151		///152		/// * owner:  New sponsor address.153		CollectionSponsorSet(CollectionId, AccountId),154155		/// const on chain schema was set156		///157		/// # Arguments158		///159		/// * collection_id: Globally unique collection identifier.160		ConstOnChainSchemaSet(CollectionId),161162		/// New sponsor was confirm163		///164		/// # Arguments165		///166		/// * collection_id: Globally unique collection identifier.167		///168		/// * sponsor:  New sponsor address.169		SponsorshipConfirmed(CollectionId, AccountId),170171		/// Collection admin was removed172		///173		/// # Arguments174		///175		/// * collection_id: Globally unique collection identifier.176		///177		/// * admin:  Admin address.178		CollectionAdminRemoved(CollectionId, CrossAccountId),179180		/// Address was remove from allow list181		///182		/// # Arguments183		///184		/// * collection_id: Globally unique collection identifier.185		///186		/// * user:  Address.187		AllowListAddressRemoved(CollectionId, CrossAccountId),188189		/// Address was add to allow list190		///191		/// # Arguments192		///193		/// * collection_id: Globally unique collection identifier.194		///195		/// * user:  Address.196		AllowListAddressAdded(CollectionId, CrossAccountId),197198		/// Collection limits was set199		///200		/// # Arguments201		///202		/// * collection_id: Globally unique collection identifier.203		CollectionLimitSet(CollectionId),204205		/// Mint permission	was set206		///207		/// # Arguments208		///209		/// * collection_id: Globally unique collection identifier.210		MintPermissionSet(CollectionId),211212		/// Offchain schema was set213		///214		/// # Arguments215		///216		/// * collection_id: Globally unique collection identifier.217		OffchainSchemaSet(CollectionId),218219		/// Public access mode was set220		///221		/// # Arguments222		///223		/// * collection_id: Globally unique collection identifier.224		///225		/// * mode: New access state.226		PublicAccessModeSet(CollectionId, AccessMode),227228		/// Schema version was set229		///230		/// # Arguments231		///232		/// * collection_id: Globally unique collection identifier.233		SchemaVersionSet(CollectionId),234235		/// Variable on chain schema was set236		///237		/// # Arguments238		///239		/// * collection_id: Globally unique collection identifier.240		VariableOnChainSchemaSet(CollectionId),241	}242}243244type SelfWeightOf<T> = <T as Config>::WeightInfo;245246// # Used definitions247//248// ## User control levels249//250// chain-controlled - key is uncontrolled by user251//                    i.e autoincrementing index252//                    can use non-cryptographic hash253// real - key is controlled by user254//        but it is hard to generate enough colliding values, i.e owner of signed txs255//        can use non-cryptographic hash256// controlled - key is completly controlled by users257//              i.e maps with mutable keys258//              should use cryptographic hash259//260// ## User control level downgrade reasons261//262// ?1 - chain-controlled -> controlled263//      collections/tokens can be destroyed, resulting in massive holes264// ?2 - chain-controlled -> controlled265//      same as ?1, but can be only added, resulting in easier exploitation266// ?3 - real -> controlled267//      no confirmation required, so addresses can be easily generated268decl_storage! {269	trait Store for Module<T: Config> as Unique {270271		//#region Private members272		/// Used for migrations273		ChainVersion: u64;274		//#endregion275276		//#region Tokens transfer rate limit baskets277		/// (Collection id (controlled?2), who created (real))278		/// TODO: Off chain worker should remove from this map when collection gets removed279		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;280		/// Collection id (controlled?2), token id (controlled?2)281		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;282		/// Collection id (controlled?2), owning user (real)283		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;284		/// Collection id (controlled?2), token id (controlled?2)285		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>;286		//#endregion287288		/// Variable metadata sponsoring289		/// Collection id (controlled?2), token id (controlled?2)290		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;291		/// Approval sponsoring292		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;293		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;294		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>;295	}296}297298decl_module! {299	pub struct Module<T: Config> for enum Call300	where301		origin: T::Origin302	{303		type Error = Error<T>;304305		fn deposit_event() = default;306307		fn on_initialize(_now: T::BlockNumber) -> Weight {308			0309		}310311		/// 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.312		///313		/// # Permissions314		///315		/// * Anyone.316		///317		/// # Arguments318		///319		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.320		///321		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.322		///323		/// * token_prefix: UTF-8 string with token prefix.324		///325		/// * mode: [CollectionMode] collection type and type dependent data.326		// returns collection ID327		#[weight = <SelfWeightOf<T>>::create_collection()]328		#[transactional]329		#[deprecated]330		pub fn create_collection(origin,331								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,332								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,333								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,334								 mode: CollectionMode) -> DispatchResult  {335			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {336				name: collection_name,337				description: collection_description,338				token_prefix,339				mode,340				..Default::default()341			};342			Self::create_collection_ex(origin, data)343		}344345		/// This method creates a collection346		///347		/// Prefer it to deprecated [`created_collection`] method348		#[weight = <SelfWeightOf<T>>::create_collection()]349		#[transactional]350		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {351			let owner = ensure_signed(origin)?;352353			let _id = match data.mode {354				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},355				CollectionMode::Fungible(decimal_points) => {356					// check params357					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);358					<PalletFungible<T>>::init_collection(owner, data)?359				}360				CollectionMode::ReFungible => {361					<PalletRefungible<T>>::init_collection(owner, data)?362				}363			};364365			Ok(())366		}367368		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.369		///370		/// # Permissions371		///372		/// * Collection Owner.373		///374		/// # Arguments375		///376		/// * collection_id: collection to destroy.377		#[weight = <SelfWeightOf<T>>::destroy_collection()]378		#[transactional]379		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {380			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);381382			let collection = <CollectionHandle<T>>::try_get(collection_id)?;383			collection.check_is_owner(&sender)?;384385			// =========386387			match collection.mode {388				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,389				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,390				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,391			}392393			<NftTransferBasket<T>>::remove_prefix(collection_id, None);394			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);395			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);396397			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);398			<NftApproveBasket<T>>::remove_prefix(collection_id, None);399			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);400			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);401402			Ok(())403		}404405		/// Add an address to allow list.406		///407		/// # Permissions408		///409		/// * Collection Owner410		/// * Collection Admin411		///412		/// # Arguments413		///414		/// * collection_id.415		///416		/// * address.417		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]418		#[transactional]419		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{420421			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);422			let collection = <CollectionHandle<T>>::try_get(collection_id)?;423424			<PalletCommon<T>>::toggle_allowlist(425				&collection,426				&sender,427				&address,428				true,429			)?;430431			Self::deposit_event(Event::<T>::AllowListAddressAdded(432				collection_id,433				address434			));435436			Ok(())437		}438439		/// Remove an address from allow list.440		///441		/// # Permissions442		///443		/// * Collection Owner444		/// * Collection Admin445		///446		/// # Arguments447		///448		/// * collection_id.449		///450		/// * address.451		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]452		#[transactional]453		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{454455			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);456			let collection = <CollectionHandle<T>>::try_get(collection_id)?;457458			<PalletCommon<T>>::toggle_allowlist(459				&collection,460				&sender,461				&address,462				false,463			)?;464465			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(466				collection_id,467				address468			));469470			Ok(())471		}472473		/// Toggle between normal and allow list access for the methods with access for `Anyone`.474		///475		/// # Permissions476		///477		/// * Collection Owner.478		///479		/// # Arguments480		///481		/// * collection_id.482		///483		/// * mode: [AccessMode]484		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]485		#[transactional]486		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult487		{488			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);489490			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;491			target_collection.check_is_owner(&sender)?;492493			target_collection.access = mode.clone();494495			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(496				collection_id,497				mode498			));499500			target_collection.save()501		}502503		/// Allows Anyone to create tokens if:504		/// * Allow List is enabled, and505		/// * Address is added to allow list, and506		/// * This method was called with True parameter507		///508		/// # Permissions509		/// * Collection Owner510		///511		/// # Arguments512		///513		/// * collection_id.514		///515		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.516		#[weight = <SelfWeightOf<T>>::set_mint_permission()]517		#[transactional]518		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult519		{520			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);521522			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;523			target_collection.check_is_owner(&sender)?;524525			target_collection.mint_mode = mint_permission;526527			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(528				collection_id529			));530531			target_collection.save()532		}533534		/// Change the owner of the collection.535		///536		/// # Permissions537		///538		/// * Collection Owner.539		///540		/// # Arguments541		///542		/// * collection_id.543		///544		/// * new_owner.545		#[weight = <SelfWeightOf<T>>::change_collection_owner()]546		#[transactional]547		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {548549			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);550551			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;552			target_collection.check_is_owner(&sender)?;553554			target_collection.owner = new_owner.clone();555			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(556				collection_id,557				new_owner558			));559560			target_collection.save()561		}562563		/// Adds an admin of the Collection.564		/// 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.565		///566		/// # Permissions567		///568		/// * Collection Owner.569		/// * Collection Admin.570		///571		/// # Arguments572		///573		/// * collection_id: ID of the Collection to add admin for.574		///575		/// * new_admin_id: Address of new admin to add.576		#[weight = <SelfWeightOf<T>>::add_collection_admin()]577		#[transactional]578		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {579			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);580			let collection = <CollectionHandle<T>>::try_get(collection_id)?;581582			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(583				collection_id,584				new_admin_id.clone()585			));586587			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)588		}589590		/// 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.591		///592		/// # Permissions593		///594		/// * Collection Owner.595		/// * Collection Admin.596		///597		/// # Arguments598		///599		/// * collection_id: ID of the Collection to remove admin for.600		///601		/// * account_id: Address of admin to remove.602		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]603		#[transactional]604		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {605			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606			let collection = <CollectionHandle<T>>::try_get(collection_id)?;607608			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(609				collection_id,610				account_id.clone()611			));612613			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)614		}615616		/// # Permissions617		///618		/// * Collection Owner619		///620		/// # Arguments621		///622		/// * collection_id.623		///624		/// * new_sponsor.625		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]626		#[transactional]627		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {628			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);629630			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;631			target_collection.check_is_owner(&sender)?;632633			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());634635			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(636				collection_id,637				new_sponsor638			));639640			target_collection.save()641		}642643		/// # Permissions644		///645		/// * Sponsor.646		///647		/// # Arguments648		///649		/// * collection_id.650		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]651		#[transactional]652		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {653			let sender = ensure_signed(origin)?;654655			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;656			ensure!(657				target_collection.sponsorship.pending_sponsor() == Some(&sender),658				Error::<T>::ConfirmUnsetSponsorFail659			);660661			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());662663			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(664				collection_id,665				sender666			));667668			target_collection.save()669		}670671		/// Switch back to pay-per-own-transaction model.672		///673		/// # Permissions674		///675		/// * Collection owner.676		///677		/// # Arguments678		///679		/// * collection_id.680		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]681		#[transactional]682		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {683			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);684685			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;686			target_collection.check_is_owner(&sender)?;687688			target_collection.sponsorship = SponsorshipState::Disabled;689690			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(691				collection_id692			));693			target_collection.save()694		}695696		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.697		///698		/// # Permissions699		///700		/// * Collection Owner.701		/// * Collection Admin.702		/// * Anyone if703		///     * Allow List is enabled, and704		///     * Address is added to allow list, and705		///     * MintPermission is enabled (see SetMintPermission method)706		///707		/// # Arguments708		///709		/// * collection_id: ID of the collection.710		///711		/// * owner: Address, initial owner of the NFT.712		///713		/// * data: Token data to store on chain.714		#[weight = <CommonWeights<T>>::create_item()]715		#[transactional]716		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {717			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);718719			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))720		}721722		/// This method creates multiple items in a collection created with CreateCollection method.723		///724		/// # Permissions725		///726		/// * Collection Owner.727		/// * Collection Admin.728		/// * Anyone if729		///     * Allow List is enabled, and730		///     * Address is added to allow list, and731		///     * MintPermission is enabled (see SetMintPermission method)732		///733		/// # Arguments734		///735		/// * collection_id: ID of the collection.736		///737		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].738		///739		/// * owner: Address, initial owner of the NFT.740		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]741		#[transactional]742		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {743			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);744			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);745746			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))747		}748749		#[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]750		#[transactional]751		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {752			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);753754			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))755		}756757		// TODO! transaction weight758759		/// Set transfers_enabled value for particular collection760		///761		/// # Permissions762		///763		/// * Collection Owner.764		///765		/// # Arguments766		///767		/// * collection_id: ID of the collection.768		///769		/// * value: New flag value.770		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]771		#[transactional]772		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {773			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);774			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;775			target_collection.check_is_owner(&sender)?;776777			// =========778779			target_collection.limits.transfers_enabled = Some(value);780			target_collection.save()781		}782783		/// Destroys a concrete instance of NFT.784		///785		/// # Permissions786		///787		/// * Collection Owner.788		/// * Collection Admin.789		/// * Current NFT Owner.790		///791		/// # Arguments792		///793		/// * collection_id: ID of the collection.794		///795		/// * item_id: ID of NFT to burn.796		#[weight = <CommonWeights<T>>::burn_item()]797		#[transactional]798		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {799			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);800801			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;802			if value == 1 {803				<NftTransferBasket<T>>::remove(collection_id, item_id);804				<NftApproveBasket<T>>::remove(collection_id, item_id);805			}806			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?807			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());808			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));809			Ok(post_info)810		}811812		/// Destroys a concrete instance of NFT on behalf of the owner813		/// See also: [`approve`]814		///815		/// # Permissions816		///817		/// * Collection Owner.818		/// * Collection Admin.819		/// * Current NFT Owner.820		///821		/// # Arguments822		///823		/// * collection_id: ID of the collection.824		///825		/// * item_id: ID of NFT to burn.826		///827		/// * from: owner of item828		#[weight = <CommonWeights<T>>::burn_from()]829		#[transactional]830		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {831			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);832833			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))834		}835836		/// Change ownership of the token.837		///838		/// # Permissions839		///840		/// * Collection Owner841		/// * Collection Admin842		/// * Current NFT owner843		///844		/// # Arguments845		///846		/// * recipient: Address of token recipient.847		///848		/// * collection_id.849		///850		/// * item_id: ID of the item851		///     * Non-Fungible Mode: Required.852		///     * Fungible Mode: Ignored.853		///     * Re-Fungible Mode: Required.854		///855		/// * value: Amount to transfer.856		///     * Non-Fungible Mode: Ignored857		///     * Fungible Mode: Must specify transferred amount858		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)859		#[weight = <CommonWeights<T>>::transfer()]860		#[transactional]861		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {862			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);863864			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))865		}866867		/// Set, change, or remove approved address to transfer the ownership of the NFT.868		///869		/// # Permissions870		///871		/// * Collection Owner872		/// * Collection Admin873		/// * Current NFT owner874		///875		/// # Arguments876		///877		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).878		///879		/// * collection_id.880		///881		/// * item_id: ID of the item.882		#[weight = <CommonWeights<T>>::approve()]883		#[transactional]884		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {885			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);886887			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))888		}889890		/// 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.891		///892		/// # Permissions893		/// * Collection Owner894		/// * Collection Admin895		/// * Current NFT owner896		/// * Address approved by current NFT owner897		///898		/// # Arguments899		///900		/// * from: Address that owns token.901		///902		/// * recipient: Address of token recipient.903		///904		/// * collection_id.905		///906		/// * item_id: ID of the item.907		///908		/// * value: Amount to transfer.909		#[weight = <CommonWeights<T>>::transfer_from()]910		#[transactional]911		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {912			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);913914			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))915		}916917		/// Set off-chain data schema.918		///919		/// # Permissions920		///921		/// * Collection Owner922		/// * Collection Admin923		///924		/// # Arguments925		///926		/// * collection_id.927		///928		/// * schema: String representing the offchain data schema.929		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]930		#[transactional]931		pub fn set_variable_meta_data (932			origin,933			collection_id: CollectionId,934			item_id: TokenId,935			data: BoundedVec<u8, CustomDataLimit>,936		) -> DispatchResultWithPostInfo {937			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);938939			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))940		}941942		/// Set meta_update_permission value for particular collection943		///944		/// # Permissions945		///946		/// * Collection Owner.947		///948		/// # Arguments949		///950		/// * collection_id: ID of the collection.951		///952		/// * value: New flag value.953		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]954		#[transactional]955		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {956			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);957			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;958959			ensure!(960				target_collection.meta_update_permission != MetaUpdatePermission::None,961				<CommonError<T>>::MetadataFlagFrozen,962			);963			target_collection.check_is_owner(&sender)?;964965			target_collection.meta_update_permission = value;966967			target_collection.save()968		}969970		/// Set schema standard971		/// ImageURL972		/// Unique973		///974		/// # Permissions975		///976		/// * Collection Owner977		/// * Collection Admin978		///979		/// # Arguments980		///981		/// * collection_id.982		///983		/// * schema: SchemaVersion: enum984		#[weight = <SelfWeightOf<T>>::set_schema_version()]985		#[transactional]986		pub fn set_schema_version(987			origin,988			collection_id: CollectionId,989			version: SchemaVersion990		) -> DispatchResult {991			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);992			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;993			target_collection.check_is_owner_or_admin(&sender)?;994			target_collection.schema_version = version;995996			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(997				collection_id998			));9991000			target_collection.save()1001		}10021003		/// Set off-chain data schema.1004		///1005		/// # Permissions1006		///1007		/// * Collection Owner1008		/// * Collection Admin1009		///1010		/// # Arguments1011		///1012		/// * collection_id.1013		///1014		/// * schema: String representing the offchain data schema.1015		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1016		#[transactional]1017		pub fn set_offchain_schema(1018			origin,1019			collection_id: CollectionId,1020			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1021		) -> DispatchResult {1022			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1023			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1024			target_collection.check_is_owner_or_admin(&sender)?;10251026			target_collection.offchain_schema = schema;10271028			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1029				collection_id1030			));10311032			target_collection.save()1033		}10341035		/// Set const on-chain data schema.1036		///1037		/// # Permissions1038		///1039		/// * Collection Owner1040		/// * Collection Admin1041		///1042		/// # Arguments1043		///1044		/// * collection_id.1045		///1046		/// * schema: String representing the const on-chain data schema.1047		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1048		#[transactional]1049		pub fn set_const_on_chain_schema (1050			origin,1051			collection_id: CollectionId,1052			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1053		) -> DispatchResult {1054			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1055			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1056			target_collection.check_is_owner_or_admin(&sender)?;10571058			target_collection.const_on_chain_schema = schema;10591060			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1061				collection_id1062			));10631064			target_collection.save()1065		}10661067		/// Set variable on-chain data schema.1068		///1069		/// # Permissions1070		///1071		/// * Collection Owner1072		/// * Collection Admin1073		///1074		/// # Arguments1075		///1076		/// * collection_id.1077		///1078		/// * schema: String representing the variable on-chain data schema.1079		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1080		#[transactional]1081		pub fn set_variable_on_chain_schema (1082			origin,1083			collection_id: CollectionId,1084			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1085		) -> DispatchResult {1086			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1087			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1088			target_collection.check_is_owner_or_admin(&sender)?;10891090			target_collection.variable_on_chain_schema = schema;10911092			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1093				collection_id1094			));10951096			target_collection.save()1097		}10981099		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1100		#[transactional]1101		pub fn set_collection_limits(1102			origin,1103			collection_id: CollectionId,1104			new_limit: CollectionLimits,1105		) -> DispatchResult {1106			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1107			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1108			target_collection.check_is_owner(&sender)?;1109			let old_limit = &target_collection.limits;11101111			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11121113			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1114				collection_id1115			));11161117			target_collection.save()1118		}1119	}1120}
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::{57	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,58	CommonWeightInfo,59};60use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};61use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};62use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};6364#[cfg(test)]65mod mock;6667#[cfg(test)]68mod tests;6970mod eth;71mod sponsorship;72pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};73pub use eth::sponsoring::UniqueEthSponsorshipHandler;7475pub use eth::UniqueErcSupport;7677pub mod common;78use common::CommonWeights;79pub mod dispatch;80use dispatch::dispatch_call;8182#[cfg(feature = "runtime-benchmarks")]83mod benchmarking;84pub mod weights;85use weights::WeightInfo;8687pub trait SponsorshipPredict<T: Config> {88	fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>89	where90		u64: From<<T as frame_system::Config>::BlockNumber>;91}9293decl_error! {94	/// Error for non-fungible-token module.95	pub enum Error for Module<T: Config> {96		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.97		CollectionDecimalPointLimitExceeded,98		/// This address is not set as sponsor, use setCollectionSponsor first.99		ConfirmUnsetSponsorFail,100		/// Length of items properties must be greater than 0.101		EmptyArgument,102	}103}104105pub trait Config:106	system::Config107	+ pallet_evm_coder_substrate::Config108	+ pallet_common::Config109	+ pallet_nonfungible::Config110	+ pallet_refungible::Config111	+ pallet_fungible::Config112	+ Sized113	+ TypeInfo114{115	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;116117	/// Weight information for extrinsics in this pallet.118	type WeightInfo: WeightInfo;119}120121decl_event! {122	pub enum Event<T>123	where124		<T as frame_system::Config>::AccountId,125		<T as pallet_common::Config>::CrossAccountId,126	{127		/// Collection sponsor was removed128		///129		/// # Arguments130		///131		/// * collection_id: Globally unique collection identifier.132		CollectionSponsorRemoved(CollectionId),133134		/// Collection admin was added135		///136		/// # Arguments137		///138		/// * collection_id: Globally unique collection identifier.139		///140		/// * admin:  Admin address.141		CollectionAdminAdded(CollectionId, CrossAccountId),142143		/// Collection owned was change144		///145		/// # Arguments146		///147		/// * collection_id: Globally unique collection identifier.148		///149		/// * owner:  New owner address.150		CollectionOwnedChanged(CollectionId, AccountId),151152		/// Collection sponsor was set153		///154		/// # Arguments155		///156		/// * collection_id: Globally unique collection identifier.157		///158		/// * owner:  New sponsor address.159		CollectionSponsorSet(CollectionId, AccountId),160161		/// const on chain schema was set162		///163		/// # Arguments164		///165		/// * collection_id: Globally unique collection identifier.166		ConstOnChainSchemaSet(CollectionId),167168		/// New sponsor was confirm169		///170		/// # Arguments171		///172		/// * collection_id: Globally unique collection identifier.173		///174		/// * sponsor:  New sponsor address.175		SponsorshipConfirmed(CollectionId, AccountId),176177		/// Collection admin was removed178		///179		/// # Arguments180		///181		/// * collection_id: Globally unique collection identifier.182		///183		/// * admin:  Admin address.184		CollectionAdminRemoved(CollectionId, CrossAccountId),185186		/// Address was remove from allow list187		///188		/// # Arguments189		///190		/// * collection_id: Globally unique collection identifier.191		///192		/// * user:  Address.193		AllowListAddressRemoved(CollectionId, CrossAccountId),194195		/// Address was add to allow list196		///197		/// # Arguments198		///199		/// * collection_id: Globally unique collection identifier.200		///201		/// * user:  Address.202		AllowListAddressAdded(CollectionId, CrossAccountId),203204		/// Collection limits was set205		///206		/// # Arguments207		///208		/// * collection_id: Globally unique collection identifier.209		CollectionLimitSet(CollectionId),210211		/// Mint permission	was set212		///213		/// # Arguments214		///215		/// * collection_id: Globally unique collection identifier.216		MintPermissionSet(CollectionId),217218		/// Offchain schema was set219		///220		/// # Arguments221		///222		/// * collection_id: Globally unique collection identifier.223		OffchainSchemaSet(CollectionId),224225		/// Public access mode was set226		///227		/// # Arguments228		///229		/// * collection_id: Globally unique collection identifier.230		///231		/// * mode: New access state.232		PublicAccessModeSet(CollectionId, AccessMode),233234		/// Schema version was set235		///236		/// # Arguments237		///238		/// * collection_id: Globally unique collection identifier.239		SchemaVersionSet(CollectionId),240241		/// Variable on chain schema was set242		///243		/// # Arguments244		///245		/// * collection_id: Globally unique collection identifier.246		VariableOnChainSchemaSet(CollectionId),247	}248}249250type SelfWeightOf<T> = <T as Config>::WeightInfo;251252// # Used definitions253//254// ## User control levels255//256// chain-controlled - key is uncontrolled by user257//                    i.e autoincrementing index258//                    can use non-cryptographic hash259// real - key is controlled by user260//        but it is hard to generate enough colliding values, i.e owner of signed txs261//        can use non-cryptographic hash262// controlled - key is completly controlled by users263//              i.e maps with mutable keys264//              should use cryptographic hash265//266// ## User control level downgrade reasons267//268// ?1 - chain-controlled -> controlled269//      collections/tokens can be destroyed, resulting in massive holes270// ?2 - chain-controlled -> controlled271//      same as ?1, but can be only added, resulting in easier exploitation272// ?3 - real -> controlled273//      no confirmation required, so addresses can be easily generated274decl_storage! {275	trait Store for Module<T: Config> as Unique {276277		//#region Private members278		/// Used for migrations279		ChainVersion: u64;280		//#endregion281282		//#region Tokens transfer rate limit baskets283		/// (Collection id (controlled?2), who created (real))284		/// TODO: Off chain worker should remove from this map when collection gets removed285		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;286		/// Collection id (controlled?2), token id (controlled?2)287		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;288		/// Collection id (controlled?2), owning user (real)289		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;290		/// Collection id (controlled?2), token id (controlled?2)291		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>;292		//#endregion293294		/// Variable metadata sponsoring295		/// Collection id (controlled?2), token id (controlled?2)296		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;297		/// Approval sponsoring298		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;299		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;300		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>;301	}302}303304decl_module! {305	pub struct Module<T: Config> for enum Call306	where307		origin: T::Origin308	{309		type Error = Error<T>;310311		fn deposit_event() = default;312313		fn on_initialize(_now: T::BlockNumber) -> Weight {314			0315		}316317		/// 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.318		///319		/// # Permissions320		///321		/// * Anyone.322		///323		/// # Arguments324		///325		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.326		///327		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.328		///329		/// * token_prefix: UTF-8 string with token prefix.330		///331		/// * mode: [CollectionMode] collection type and type dependent data.332		// returns collection ID333		#[weight = <SelfWeightOf<T>>::create_collection()]334		#[transactional]335		#[deprecated]336		pub fn create_collection(origin,337								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,338								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,339								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,340								 mode: CollectionMode) -> DispatchResult  {341			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {342				name: collection_name,343				description: collection_description,344				token_prefix,345				mode,346				..Default::default()347			};348			Self::create_collection_ex(origin, data)349		}350351		/// This method creates a collection352		///353		/// Prefer it to deprecated [`created_collection`] method354		#[weight = <SelfWeightOf<T>>::create_collection()]355		#[transactional]356		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {357			let owner = ensure_signed(origin)?;358359			let _id = match data.mode {360				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},361				CollectionMode::Fungible(decimal_points) => {362					// check params363					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);364					<PalletFungible<T>>::init_collection(owner, data)?365				}366				CollectionMode::ReFungible => {367					<PalletRefungible<T>>::init_collection(owner, data)?368				}369			};370371			Ok(())372		}373374		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.375		///376		/// # Permissions377		///378		/// * Collection Owner.379		///380		/// # Arguments381		///382		/// * collection_id: collection to destroy.383		#[weight = <SelfWeightOf<T>>::destroy_collection()]384		#[transactional]385		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {386			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);387388			let collection = <CollectionHandle<T>>::try_get(collection_id)?;389			collection.check_is_owner(&sender)?;390391			// =========392393			match collection.mode {394				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,395				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,396				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,397			}398399			<NftTransferBasket<T>>::remove_prefix(collection_id, None);400			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);401			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);402403			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);404			<NftApproveBasket<T>>::remove_prefix(collection_id, None);405			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);406			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);407408			Ok(())409		}410411		/// Add an address to allow list.412		///413		/// # Permissions414		///415		/// * Collection Owner416		/// * Collection Admin417		///418		/// # Arguments419		///420		/// * collection_id.421		///422		/// * address.423		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]424		#[transactional]425		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{426427			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);428			let collection = <CollectionHandle<T>>::try_get(collection_id)?;429430			<PalletCommon<T>>::toggle_allowlist(431				&collection,432				&sender,433				&address,434				true,435			)?;436437			Self::deposit_event(Event::<T>::AllowListAddressAdded(438				collection_id,439				address440			));441442			Ok(())443		}444445		/// Remove an address from allow list.446		///447		/// # Permissions448		///449		/// * Collection Owner450		/// * Collection Admin451		///452		/// # Arguments453		///454		/// * collection_id.455		///456		/// * address.457		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]458		#[transactional]459		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{460461			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);462			let collection = <CollectionHandle<T>>::try_get(collection_id)?;463464			<PalletCommon<T>>::toggle_allowlist(465				&collection,466				&sender,467				&address,468				false,469			)?;470471			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(472				collection_id,473				address474			));475476			Ok(())477		}478479		/// Toggle between normal and allow list access for the methods with access for `Anyone`.480		///481		/// # Permissions482		///483		/// * Collection Owner.484		///485		/// # Arguments486		///487		/// * collection_id.488		///489		/// * mode: [AccessMode]490		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]491		#[transactional]492		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult493		{494			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);495496			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;497			target_collection.check_is_owner(&sender)?;498499			target_collection.access = mode.clone();500501			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(502				collection_id,503				mode504			));505506			target_collection.save()507		}508509		/// Allows Anyone to create tokens if:510		/// * Allow List is enabled, and511		/// * Address is added to allow list, and512		/// * This method was called with True parameter513		///514		/// # Permissions515		/// * Collection Owner516		///517		/// # Arguments518		///519		/// * collection_id.520		///521		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.522		#[weight = <SelfWeightOf<T>>::set_mint_permission()]523		#[transactional]524		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult525		{526			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;529			target_collection.check_is_owner(&sender)?;530531			target_collection.mint_mode = mint_permission;532533			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(534				collection_id535			));536537			target_collection.save()538		}539540		/// Change the owner of the collection.541		///542		/// # Permissions543		///544		/// * Collection Owner.545		///546		/// # Arguments547		///548		/// * collection_id.549		///550		/// * new_owner.551		#[weight = <SelfWeightOf<T>>::change_collection_owner()]552		#[transactional]553		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {554555			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);556557			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;558			target_collection.check_is_owner(&sender)?;559560			target_collection.owner = new_owner.clone();561			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(562				collection_id,563				new_owner564			));565566			target_collection.save()567		}568569		/// Adds an admin of the Collection.570		/// 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.571		///572		/// # Permissions573		///574		/// * Collection Owner.575		/// * Collection Admin.576		///577		/// # Arguments578		///579		/// * collection_id: ID of the Collection to add admin for.580		///581		/// * new_admin_id: Address of new admin to add.582		#[weight = <SelfWeightOf<T>>::add_collection_admin()]583		#[transactional]584		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {585			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);586			let collection = <CollectionHandle<T>>::try_get(collection_id)?;587588			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(589				collection_id,590				new_admin_id.clone()591			));592593			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)594		}595596		/// 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.597		///598		/// # Permissions599		///600		/// * Collection Owner.601		/// * Collection Admin.602		///603		/// # Arguments604		///605		/// * collection_id: ID of the Collection to remove admin for.606		///607		/// * account_id: Address of admin to remove.608		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]609		#[transactional]610		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {611			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);612			let collection = <CollectionHandle<T>>::try_get(collection_id)?;613614			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(615				collection_id,616				account_id.clone()617			));618619			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)620		}621622		/// # Permissions623		///624		/// * Collection Owner625		///626		/// # Arguments627		///628		/// * collection_id.629		///630		/// * new_sponsor.631		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]632		#[transactional]633		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {634			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635636			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;637			target_collection.check_is_owner(&sender)?;638639			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());640641			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(642				collection_id,643				new_sponsor644			));645646			target_collection.save()647		}648649		/// # Permissions650		///651		/// * Sponsor.652		///653		/// # Arguments654		///655		/// * collection_id.656		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]657		#[transactional]658		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {659			let sender = ensure_signed(origin)?;660661			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;662			ensure!(663				target_collection.sponsorship.pending_sponsor() == Some(&sender),664				Error::<T>::ConfirmUnsetSponsorFail665			);666667			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());668669			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(670				collection_id,671				sender672			));673674			target_collection.save()675		}676677		/// Switch back to pay-per-own-transaction model.678		///679		/// # Permissions680		///681		/// * Collection owner.682		///683		/// # Arguments684		///685		/// * collection_id.686		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]687		#[transactional]688		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {689			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);690691			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;692			target_collection.check_is_owner(&sender)?;693694			target_collection.sponsorship = SponsorshipState::Disabled;695696			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(697				collection_id698			));699			target_collection.save()700		}701702		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.703		///704		/// # Permissions705		///706		/// * Collection Owner.707		/// * Collection Admin.708		/// * Anyone if709		///     * Allow List is enabled, and710		///     * Address is added to allow list, and711		///     * MintPermission is enabled (see SetMintPermission method)712		///713		/// # Arguments714		///715		/// * collection_id: ID of the collection.716		///717		/// * owner: Address, initial owner of the NFT.718		///719		/// * data: Token data to store on chain.720		#[weight = <CommonWeights<T>>::create_item()]721		#[transactional]722		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {723			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))726		}727728		/// This method creates multiple items in a collection created with CreateCollection method.729		///730		/// # Permissions731		///732		/// * Collection Owner.733		/// * Collection Admin.734		/// * Anyone if735		///     * Allow List is enabled, and736		///     * Address is added to allow list, and737		///     * MintPermission is enabled (see SetMintPermission method)738		///739		/// # Arguments740		///741		/// * collection_id: ID of the collection.742		///743		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].744		///745		/// * owner: Address, initial owner of the NFT.746		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]747		#[transactional]748		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {749			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);750			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751752			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))753		}754755		#[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]756		#[transactional]757		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {758			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);759760			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))761		}762763		// TODO! transaction weight764765		/// Set transfers_enabled value for particular collection766		///767		/// # Permissions768		///769		/// * Collection Owner.770		///771		/// # Arguments772		///773		/// * collection_id: ID of the collection.774		///775		/// * value: New flag value.776		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]777		#[transactional]778		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {779			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);780			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;781			target_collection.check_is_owner(&sender)?;782783			// =========784785			target_collection.limits.transfers_enabled = Some(value);786			target_collection.save()787		}788789		/// Destroys a concrete instance of NFT.790		///791		/// # Permissions792		///793		/// * Collection Owner.794		/// * Collection Admin.795		/// * Current NFT Owner.796		///797		/// # Arguments798		///799		/// * collection_id: ID of the collection.800		///801		/// * item_id: ID of NFT to burn.802		#[weight = <CommonWeights<T>>::burn_item()]803		#[transactional]804		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {805			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);806807			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;808			if value == 1 {809				<NftTransferBasket<T>>::remove(collection_id, item_id);810				<NftApproveBasket<T>>::remove(collection_id, item_id);811			}812			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?813			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());814			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));815			Ok(post_info)816		}817818		/// Destroys a concrete instance of NFT on behalf of the owner819		/// See also: [`approve`]820		///821		/// # Permissions822		///823		/// * Collection Owner.824		/// * Collection Admin.825		/// * Current NFT Owner.826		///827		/// # Arguments828		///829		/// * collection_id: ID of the collection.830		///831		/// * item_id: ID of NFT to burn.832		///833		/// * from: owner of item834		#[weight = <CommonWeights<T>>::burn_from()]835		#[transactional]836		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {837			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);838839			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))840		}841842		/// Change ownership of the token.843		///844		/// # Permissions845		///846		/// * Collection Owner847		/// * Collection Admin848		/// * Current NFT owner849		///850		/// # Arguments851		///852		/// * recipient: Address of token recipient.853		///854		/// * collection_id.855		///856		/// * item_id: ID of the item857		///     * Non-Fungible Mode: Required.858		///     * Fungible Mode: Ignored.859		///     * Re-Fungible Mode: Required.860		///861		/// * value: Amount to transfer.862		///     * Non-Fungible Mode: Ignored863		///     * Fungible Mode: Must specify transferred amount864		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)865		#[weight = <CommonWeights<T>>::transfer()]866		#[transactional]867		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {868			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869870			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))871		}872873		/// Set, change, or remove approved address to transfer the ownership of the NFT.874		///875		/// # Permissions876		///877		/// * Collection Owner878		/// * Collection Admin879		/// * Current NFT owner880		///881		/// # Arguments882		///883		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).884		///885		/// * collection_id.886		///887		/// * item_id: ID of the item.888		#[weight = <CommonWeights<T>>::approve()]889		#[transactional]890		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {891			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);892893			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))894		}895896		/// 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.897		///898		/// # Permissions899		/// * Collection Owner900		/// * Collection Admin901		/// * Current NFT owner902		/// * Address approved by current NFT owner903		///904		/// # Arguments905		///906		/// * from: Address that owns token.907		///908		/// * recipient: Address of token recipient.909		///910		/// * collection_id.911		///912		/// * item_id: ID of the item.913		///914		/// * value: Amount to transfer.915		#[weight = <CommonWeights<T>>::transfer_from()]916		#[transactional]917		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {918			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);919920			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))921		}922923		/// Set off-chain data schema.924		///925		/// # Permissions926		///927		/// * Collection Owner928		/// * Collection Admin929		///930		/// # Arguments931		///932		/// * collection_id.933		///934		/// * schema: String representing the offchain data schema.935		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]936		#[transactional]937		pub fn set_variable_meta_data (938			origin,939			collection_id: CollectionId,940			item_id: TokenId,941			data: BoundedVec<u8, CustomDataLimit>,942		) -> DispatchResultWithPostInfo {943			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);944945			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))946		}947948		/// Set meta_update_permission value for particular collection949		///950		/// # Permissions951		///952		/// * Collection Owner.953		///954		/// # Arguments955		///956		/// * collection_id: ID of the collection.957		///958		/// * value: New flag value.959		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]960		#[transactional]961		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {962			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);963			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;964965			ensure!(966				target_collection.meta_update_permission != MetaUpdatePermission::None,967				<CommonError<T>>::MetadataFlagFrozen,968			);969			target_collection.check_is_owner(&sender)?;970971			target_collection.meta_update_permission = value;972973			target_collection.save()974		}975976		/// Set schema standard977		/// ImageURL978		/// Unique979		///980		/// # Permissions981		///982		/// * Collection Owner983		/// * Collection Admin984		///985		/// # Arguments986		///987		/// * collection_id.988		///989		/// * schema: SchemaVersion: enum990		#[weight = <SelfWeightOf<T>>::set_schema_version()]991		#[transactional]992		pub fn set_schema_version(993			origin,994			collection_id: CollectionId,995			version: SchemaVersion996		) -> DispatchResult {997			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);998			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;999			target_collection.check_is_owner_or_admin(&sender)?;1000			target_collection.schema_version = version;10011002			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(1003				collection_id1004			));10051006			target_collection.save()1007		}10081009		/// Set off-chain data schema.1010		///1011		/// # Permissions1012		///1013		/// * Collection Owner1014		/// * Collection Admin1015		///1016		/// # Arguments1017		///1018		/// * collection_id.1019		///1020		/// * schema: String representing the offchain data schema.1021		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1022		#[transactional]1023		pub fn set_offchain_schema(1024			origin,1025			collection_id: CollectionId,1026			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1027		) -> DispatchResult {1028			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1029			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1030			target_collection.check_is_owner_or_admin(&sender)?;10311032			target_collection.offchain_schema = schema;10331034			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1035				collection_id1036			));10371038			target_collection.save()1039		}10401041		/// Set const on-chain data schema.1042		///1043		/// # Permissions1044		///1045		/// * Collection Owner1046		/// * Collection Admin1047		///1048		/// # Arguments1049		///1050		/// * collection_id.1051		///1052		/// * schema: String representing the const on-chain data schema.1053		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1054		#[transactional]1055		pub fn set_const_on_chain_schema (1056			origin,1057			collection_id: CollectionId,1058			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1059		) -> DispatchResult {1060			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1061			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1062			target_collection.check_is_owner_or_admin(&sender)?;10631064			target_collection.const_on_chain_schema = schema;10651066			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1067				collection_id1068			));10691070			target_collection.save()1071		}10721073		/// Set variable on-chain data schema.1074		///1075		/// # Permissions1076		///1077		/// * Collection Owner1078		/// * Collection Admin1079		///1080		/// # Arguments1081		///1082		/// * collection_id.1083		///1084		/// * schema: String representing the variable on-chain data schema.1085		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1086		#[transactional]1087		pub fn set_variable_on_chain_schema (1088			origin,1089			collection_id: CollectionId,1090			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1091		) -> DispatchResult {1092			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1093			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1094			target_collection.check_is_owner_or_admin(&sender)?;10951096			target_collection.variable_on_chain_schema = schema;10971098			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1099				collection_id1100			));11011102			target_collection.save()1103		}11041105		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1106		#[transactional]1107		pub fn set_collection_limits(1108			origin,1109			collection_id: CollectionId,1110			new_limit: CollectionLimits,1111		) -> DispatchResult {1112			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1113			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1114			target_collection.check_is_owner(&sender)?;1115			let old_limit = &target_collection.limits;11161117			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11181119			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1120				collection_id1121			));11221123			target_collection.save()1124		}1125	}1126}
modifiedpallets/unique/src/sponsorship.rsdiffbeforeafterboth
--- a/pallets/unique/src/sponsorship.rs
+++ b/pallets/unique/src/sponsorship.rs
@@ -301,3 +301,80 @@
 		}
 	}
 }
+
+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()?;
+
+		// preliminary sponsoring correctness check
+		match collection.sponsorship {
+			SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => return None,
+			_ => (),
+		}
+
+		// 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 {
+			let timeout = last_tx_block + limit.into();
+			if block_number < timeout {
+				return Some((timeout - block_number).into());
+			}
+			return Some(0);
+		}
+
+		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
+		}
+
+		// // existance check
+		// 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()))
+		// 	}
+		// };
+	}
+}
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -59,5 +59,6 @@
 		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>>;
 	}
 }
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))
+                }
             }
 
             impl sp_api::Core<Block> for Runtime {
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -619,6 +619,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: PalletCommonAccountBasicCrossAccountIdRepr | { 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,5 +55,6 @@
     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('Check if user is allowed to use collection', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),
   },
 };