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

difftreelog

feat add ApproveForAll to Eth and Sub

Grigoriy Simonov2022-12-06parent: #e7f81f3.patch.diff
in: master

39 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -246,6 +246,16 @@
 		token_id: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<String>>;
+
+	/// Get whether an operator is approved by a given owner.
+	#[method(name = "unique_isApprovedForAll")]
+	fn is_approved_for_all(
+		&self,
+		collection: CollectionId,
+		owner: CrossAccountId,
+		operator: CrossAccountId,
+		at: Option<BlockHash>,
+	) -> Result<bool>;
 }
 
 mod app_promotion_unique_rpc {
@@ -569,6 +579,7 @@
 	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);
 	pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<String> => |o| o.map(|number| number.to_string()) , unique_api);
 	pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
+	pass_method!(is_approved_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> bool, unique_api);
 }
 
 impl<C, Block, BlockNumber, CrossAccountId, AccountId>
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -472,6 +472,18 @@
 			u128,
 		),
 
+		/// Amount pieces of token owned by `sender` was approved for `spender`.
+		ApprovedForAll(
+			/// Id of collection to which item is belong.
+			CollectionId,
+			/// Owner of a wallet.
+			T::CrossAccountId,
+			/// Id for which operator status was granted or rewoked.
+			T::CrossAccountId,
+			/// Is operator status was granted or rewoked.
+			bool,
+		),
+
 		/// The colletion property has been added or edited.
 		CollectionPropertySet(
 			/// Id of collection to which property has been set.
@@ -1521,6 +1533,9 @@
 
 	/// The price of retrieving token owner
 	fn token_owner() -> Weight;
+
+	/// The price of setting approval for all
+	fn set_approval_for_all() -> Weight;
 }
 
 /// Weight info extension trait for refungible pallet.
@@ -1828,6 +1843,20 @@
 
 	/// Get extension for RFT collection.
 	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;
+
+	/// An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// * `owner` - Token owner
+	/// * `operator` - Operator
+	/// * `approve` - Is operator enabled or disabled
+	fn set_approval_for_all(
+		&self,
+		owner: T::CrossAccountId,
+		operator: T::CrossAccountId,
+		approve: bool,
+	) -> DispatchResultWithPostInfo;
+
+	/// Tells whether an operator is approved by a given owner.
+	fn is_approved_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;
 }
 
 /// Extension for RFT collection.
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -107,6 +107,10 @@
 	fn token_owner() -> Weight {
 		Weight::zero()
 	}
+
+	fn set_approval_for_all() -> Weight {
+		Weight::zero()
+	}
 }
 
 /// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
@@ -424,4 +428,17 @@
 		}
 		<TotalSupply<T>>::try_get(self.id).ok()
 	}
+
+	fn set_approval_for_all(
+		&self,
+		_owner: T::CrossAccountId,
+		_operator: T::CrossAccountId,
+		_approve: bool,
+	) -> DispatchResultWithPostInfo {
+		fail!(<Error<T>>::SettingApprovalForAllNotAllowed)
+	}
+
+	fn is_approved_for_all(&self, _owner: T::CrossAccountId, _operator: T::CrossAccountId) -> bool {
+		false
+	}
 }
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -127,6 +127,8 @@
 		FungibleDisallowsNesting,
 		/// Setting item properties is not allowed.
 		SettingPropertiesNotAllowed,
+		/// Setting approval for all is not allowed.
+		SettingApprovalForAllNotAllowed,
 	}
 
 	#[pallet::config]
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -222,4 +222,18 @@
 		let item = create_max_item(&collection, &owner, owner.clone())?;
 
 	}: {collection.token_owner(item)}
+
+	set_approval_for_all {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			operator: cross_from_sub(owner); owner: cross_sub;
+		};
+	}: {<Pallet<T>>::set_approval_for_all(&collection, &owner, &operator, true)}
+
+	is_approved_for_all {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			operator: cross_from_sub(owner); owner: cross_sub;
+		};
+	}: {<Pallet<T>>::is_approved_for_all(&collection, &owner, &operator)}
 }
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -122,6 +122,10 @@
 	fn token_owner() -> Weight {
 		<SelfWeightOf<T>>::token_owner()
 	}
+
+	fn set_approval_for_all() -> Weight {
+		<SelfWeightOf<T>>::set_approval_for_all()
+	}
 }
 
 fn map_create_data<T: Config>(
@@ -512,4 +516,20 @@
 			None
 		}
 	}
+
+	fn set_approval_for_all(
+		&self,
+		owner: T::CrossAccountId,
+		operator: T::CrossAccountId,
+		approve: bool,
+	) -> DispatchResultWithPostInfo {
+		with_weight(
+			<Pallet<T>>::set_approval_for_all(self, &owner, &operator, approve),
+			<CommonWeights<T>>::set_approval_for_all(),
+		)
+	}
+
+	fn is_approved_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool {
+		<Pallet<T>>::is_approved_for_all(self, &owner, &operator)
+	}
 }
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -469,15 +469,23 @@
 		Ok(())
 	}
 
-	/// @dev Not implemented
+	/// @notice Sets or unsets the approval of a given operator.
+	///  An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// @param operator Operator
+	/// @param approved Is operator enabled or disabled
+	#[weight(<SelfWeightOf<T>>::set_approval_for_all())]
 	fn set_approval_for_all(
 		&mut self,
-		_caller: caller,
-		_operator: address,
-		_approved: bool,
+		caller: caller,
+		operator: address,
+		approved: bool,
 	) -> Result<void> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
+		let caller = T::CrossAccountId::from_eth(caller);
+		let operator = T::CrossAccountId::from_eth(operator);
+
+		<Pallet<T>>::set_approval_for_all(self, &caller, &operator, approved)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
 	}
 
 	/// @dev Not implemented
@@ -486,10 +494,13 @@
 		Err("not implemented".into())
 	}
 
-	/// @dev Not implemented
-	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
+	/// @notice Tells whether an operator is approved by a given owner.
+	#[weight(<SelfWeightOf<T>>::is_approved_for_all())]
+	fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {
+		let owner = T::CrossAccountId::from_eth(owner);
+		let operator = T::CrossAccountId::from_eth(operator);
+
+		Ok(<Pallet<T>>::is_approved_for_all(self, &owner, &operator))
 	}
 
 	/// @notice Returns collection helper contract address
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -272,6 +272,18 @@
 		QueryKind = OptionQuery,
 	>;
 
+	/// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+	#[pallet::storage]
+	pub type WalletOperator<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Blake2_128Concat, T::CrossAccountId>,
+			Key<Blake2_128Concat, T::CrossAccountId>,
+		),
+		Value = bool,
+		QueryKind = OptionQuery,
+	>;
+
 	/// Upgrade from the old schema to properties.
 	#[pallet::hooks]
 	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
@@ -438,6 +450,7 @@
 		<TokensBurnt<T>>::remove(id);
 		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);
 		let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);
+		let _ = <WalletOperator<T>>::clear_prefix((id,), u32::MAX, None);
 		Ok(())
 	}
 
@@ -1193,6 +1206,9 @@
 		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
 			return Ok(());
 		}
+		if <WalletOperator<T>>::get((collection.id, from, spender)) == Some(true) {
+			return Ok(());
+		}
 		ensure!(
 			collection.ignores_allowance(spender),
 			<CommonError<T>>::ApprovedValueTooLow
@@ -1326,4 +1342,52 @@
 	) -> DispatchResult {
 		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)
 	}
+
+	/// Sets or unsets the approval of a given operator.
+	///
+	/// An operator is allowed to transfer all token pieces of the sender on their behalf.
+	/// - `owner`: Token owner
+	/// - `operator`: Operator
+	/// - `approve`: Is operator enabled or disabled
+	pub fn set_approval_for_all(
+		collection: &NonfungibleHandle<T>,
+		owner: &T::CrossAccountId,
+		operator: &T::CrossAccountId,
+		approve: bool,
+	) -> DispatchResult {
+		if collection.permissions.access() == AccessMode::AllowList {
+			collection.check_allowlist(owner)?;
+			collection.check_allowlist(operator)?;
+		}
+
+		<PalletCommon<T>>::ensure_correct_receiver(operator)?;
+
+		// =========
+
+		<WalletOperator<T>>::insert((collection.id, owner, operator), approve);
+		<PalletEvm<T>>::deposit_log(
+			ERC721Events::ApprovalForAll {
+				owner: *owner.as_eth(),
+				operator: *operator.as_eth(),
+				approved: approve,
+			}
+			.to_log(collection_id_to_address(collection.id)),
+		);
+		<PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(
+			collection.id,
+			owner.clone(),
+			operator.clone(),
+			approve,
+		));
+		Ok(())
+	}
+
+	/// Tells whether an operator is approved by a given owner.
+	pub fn is_approved_for_all(
+		collection: &NonfungibleHandle<T>,
+		owner: &T::CrossAccountId,
+		operator: &T::CrossAccountId,
+	) -> bool {
+		<WalletOperator<T>>::get((collection.id, owner, operator)).unwrap_or(false)
+	}
 }
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -1020,7 +1020,10 @@
 		dummy = 0;
 	}
 
-	/// @dev Not implemented
+	/// @notice Sets or unsets the approval of a given operator.
+	///  An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// @param operator Operator
+	/// @param approved Is operator enabled or disabled
 	/// @dev EVM selector for this function is: 0xa22cb465,
 	///  or in textual repr: setApprovalForAll(address,bool)
 	function setApprovalForAll(address operator, bool approved) public {
@@ -1040,15 +1043,15 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	/// @dev Not implemented
+	/// @notice Tells whether an operator is approved by a given owner.
 	/// @dev EVM selector for this function is: 0xe985e9c5,
 	///  or in textual repr: isApprovedForAll(address,address)
-	function isApprovedForAll(address owner, address operator) public view returns (address) {
+	function isApprovedForAll(address owner, address operator) public view returns (bool) {
 		require(false, stub_error);
 		owner;
 		operator;
 		dummy;
-		return 0x0000000000000000000000000000000000000000;
+		return false;
 	}
 
 	/// @notice Returns collection helper contract address
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -26,6 +26,7 @@
 #![cfg_attr(rustfmt, rustfmt_skip)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
+#![allow(missing_docs)]
 #![allow(clippy::unnecessary_cast)]
 
 use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
@@ -47,6 +48,8 @@
 	fn set_token_properties(b: u32, ) -> Weight;
 	fn delete_token_properties(b: u32, ) -> Weight;
 	fn token_owner() -> Weight;
+	fn set_approval_for_all() -> Weight;
+	fn is_approved_for_all() -> Weight;
 }
 
 /// Weights for pallet_nonfungible using the Substrate node and recommended hardware.
@@ -195,6 +198,16 @@
 		Weight::from_ref_time(4_366_000)
 			.saturating_add(T::DbWeight::get().reads(1 as u64))
 	}
+	// Storage: Nonfungible WalletOperator (r:0 w:1)
+	fn set_approval_for_all() -> Weight {
+		Weight::from_ref_time(16_231_000 as u64)
+			.saturating_add(T::DbWeight::get().writes(1 as u64))
+	}
+	// Storage: Nonfungible WalletOperator (r:1 w:0)
+	fn is_approved_for_all() -> Weight {
+		Weight::from_ref_time(6_161_000 as u64)
+			.saturating_add(T::DbWeight::get().reads(1 as u64))
+	}
 }
 
 // For backwards compatibility and tests
@@ -342,4 +355,14 @@
 		Weight::from_ref_time(4_366_000)
 			.saturating_add(RocksDbWeight::get().reads(1 as u64))
 	}
+	// Storage: Nonfungible WalletOperator (r:0 w:1)
+	fn set_approval_for_all() -> Weight {
+		Weight::from_ref_time(16_231_000 as u64)
+			.saturating_add(RocksDbWeight::get().writes(1 as u64))
+	}
+	// Storage: Nonfungible WalletOperator (r:1 w:0)
+	fn is_approved_for_all() -> Weight {
+		Weight::from_ref_time(6_161_000 as u64)
+			.saturating_add(RocksDbWeight::get().reads(1 as u64))
+	}
 }
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -290,4 +290,18 @@
 		};
 		let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?;
 	}: {<Pallet<T>>::token_owner(collection.id, item)}
+
+	set_approval_for_all {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			operator: cross_from_sub(owner); owner: cross_sub;
+		};
+	}: {<Pallet<T>>::set_approval_for_all(&collection, &owner, &operator, true)}
+
+	is_approved_for_all {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			operator: cross_from_sub(owner); owner: cross_sub;
+		};
+	}: {<Pallet<T>>::is_approved_for_all(&collection, &owner, &operator)}
 }
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -152,6 +152,10 @@
 	fn token_owner() -> Weight {
 		<SelfWeightOf<T>>::token_owner()
 	}
+
+	fn set_approval_for_all() -> Weight {
+		<SelfWeightOf<T>>::set_approval_for_all()
+	}
 }
 
 fn map_create_data<T: Config>(
@@ -516,6 +520,22 @@
 	fn total_pieces(&self, token: TokenId) -> Option<u128> {
 		<Pallet<T>>::total_pieces(self.id, token)
 	}
+
+	fn set_approval_for_all(
+		&self,
+		owner: T::CrossAccountId,
+		operator: T::CrossAccountId,
+		approve: bool,
+	) -> DispatchResultWithPostInfo {
+		with_weight(
+			<Pallet<T>>::set_approval_for_all(self, &owner, &operator, approve),
+			<CommonWeights<T>>::set_approval_for_all(),
+		)
+	}
+
+	fn is_approved_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool {
+		<Pallet<T>>::is_approved_for_all(self, &owner, &operator)
+	}
 }
 
 impl<T: Config> RefungibleExtensions<T> for RefungibleHandle<T> {
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -461,15 +461,23 @@
 		Err("not implemented".into())
 	}
 
-	/// @dev Not implemented
+	/// @notice Sets or unsets the approval of a given operator.
+	///  An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// @param operator Operator
+	/// @param approved Is operator enabled or disabled
+	#[weight(<SelfWeightOf<T>>::set_approval_for_all())]
 	fn set_approval_for_all(
 		&mut self,
-		_caller: caller,
-		_operator: address,
-		_approved: bool,
+		caller: caller,
+		operator: address,
+		approved: bool,
 	) -> Result<void> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
+		let caller = T::CrossAccountId::from_eth(caller);
+		let operator = T::CrossAccountId::from_eth(operator);
+
+		<Pallet<T>>::set_approval_for_all(self, &caller, &operator, approved)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
 	}
 
 	/// @dev Not implemented
@@ -478,10 +486,13 @@
 		Err("not implemented".into())
 	}
 
-	/// @dev Not implemented
-	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
+	/// @notice Tells whether an operator is approved by a given owner.
+	#[weight(<SelfWeightOf<T>>::is_approved_for_all())]
+	fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {
+		let owner = T::CrossAccountId::from_eth(owner);
+		let operator = T::CrossAccountId::from_eth(operator);
+
+		Ok(<Pallet<T>>::is_approved_for_all(self, &owner, &operator))
 	}
 
 	/// @notice Returns collection helper contract address
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -273,6 +273,18 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+	#[pallet::storage]
+	pub type WalletOperator<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Blake2_128Concat, T::CrossAccountId>,
+			Key<Blake2_128Concat, T::CrossAccountId>,
+		),
+		Value = bool,
+		QueryKind = OptionQuery,
+	>;
+
 	#[pallet::hooks]
 	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
 		fn on_runtime_upgrade() -> Weight {
@@ -1161,6 +1173,12 @@
 		}
 		let allowance =
 			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);
+
+		// Allowance if any would be reduced if spender is also wallet operator
+		if <WalletOperator<T>>::get((collection.id, from, spender)) == Some(true) {
+			return Ok(allowance);
+		}
+
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
@@ -1387,4 +1405,52 @@
 			Some(res)
 		}
 	}
+
+	/// Sets or unsets the approval of a given operator.
+	///
+	/// An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// - `owner`: Token owner
+	/// - `operator`: Operator
+	/// - `approve`: Is operator enabled or disabled
+	pub fn set_approval_for_all(
+		collection: &RefungibleHandle<T>,
+		owner: &T::CrossAccountId,
+		operator: &T::CrossAccountId,
+		approve: bool,
+	) -> DispatchResult {
+		if collection.permissions.access() == AccessMode::AllowList {
+			collection.check_allowlist(owner)?;
+			collection.check_allowlist(operator)?;
+		}
+
+		<PalletCommon<T>>::ensure_correct_receiver(operator)?;
+
+		// =========
+
+		<WalletOperator<T>>::insert((collection.id, owner, operator), approve);
+		<PalletEvm<T>>::deposit_log(
+			ERC721Events::ApprovalForAll {
+				owner: *owner.as_eth(),
+				operator: *operator.as_eth(),
+				approved: approve,
+			}
+			.to_log(collection_id_to_address(collection.id)),
+		);
+		<PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(
+			collection.id,
+			owner.clone(),
+			operator.clone(),
+			approve,
+		));
+		Ok(())
+	}
+
+	/// Tells whether an operator is approved by a given owner.
+	pub fn is_approved_for_all(
+		collection: &RefungibleHandle<T>,
+		owner: &T::CrossAccountId,
+		operator: &T::CrossAccountId,
+	) -> bool {
+		<WalletOperator<T>>::get((collection.id, owner, operator)).unwrap_or(false)
+	}
 }
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -1017,7 +1017,10 @@
 		dummy = 0;
 	}
 
-	/// @dev Not implemented
+	/// @notice Sets or unsets the approval of a given operator.
+	///  An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// @param operator Operator
+	/// @param approved Is operator enabled or disabled
 	/// @dev EVM selector for this function is: 0xa22cb465,
 	///  or in textual repr: setApprovalForAll(address,bool)
 	function setApprovalForAll(address operator, bool approved) public {
@@ -1037,15 +1040,15 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	/// @dev Not implemented
+	/// @notice Tells whether an operator is approved by a given owner.
 	/// @dev EVM selector for this function is: 0xe985e9c5,
 	///  or in textual repr: isApprovedForAll(address,address)
-	function isApprovedForAll(address owner, address operator) public view returns (address) {
+	function isApprovedForAll(address owner, address operator) public view returns (bool) {
 		require(false, stub_error);
 		owner;
 		operator;
 		dummy;
-		return 0x0000000000000000000000000000000000000000;
+		return false;
 	}
 
 	/// @notice Returns collection helper contract address
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,7 +3,7 @@
 //! Autogenerated weights for pallet_refungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-08-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-11-25, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -26,6 +26,7 @@
 #![cfg_attr(rustfmt, rustfmt_skip)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
+#![allow(missing_docs)]
 #![allow(clippy::unnecessary_cast)]
 
 use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
@@ -54,6 +55,8 @@
 	fn delete_token_properties(b: u32, ) -> Weight;
 	fn repartition_item() -> Weight;
 	fn token_owner() -> Weight;
+	fn set_approval_for_all() -> Weight;
+	fn is_approved_for_all() -> Weight;
 }
 
 /// Weights for pallet_refungible using the Substrate node and recommended hardware.
@@ -259,6 +262,16 @@
 		Weight::from_ref_time(9_431_000)
 			.saturating_add(T::DbWeight::get().reads(2 as u64))
 	}
+	// Storage: Refungible WalletOperator (r:0 w:1)
+	fn set_approval_for_all() -> Weight {
+		Weight::from_ref_time(16_150_000 as u64)
+			.saturating_add(T::DbWeight::get().writes(1 as u64))
+	}
+	// Storage: Refungible WalletOperator (r:1 w:0)
+	fn is_approved_for_all() -> Weight {
+		Weight::from_ref_time(5_901_000 as u64)
+			.saturating_add(T::DbWeight::get().reads(1 as u64))
+	}
 }
 
 // For backwards compatibility and tests
@@ -463,4 +476,14 @@
 		Weight::from_ref_time(9_431_000)
 			.saturating_add(RocksDbWeight::get().reads(2 as u64))
 	}
+	// Storage: Refungible WalletOperator (r:0 w:1)
+	fn set_approval_for_all() -> Weight {
+		Weight::from_ref_time(16_150_000 as u64)
+			.saturating_add(RocksDbWeight::get().writes(1 as u64))
+	}
+	// Storage: Refungible WalletOperator (r:1 w:0)
+	fn is_approved_for_all() -> Weight {
+		Weight::from_ref_time(5_901_000 as u64)
+			.saturating_add(RocksDbWeight::get().reads(1 as 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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77	decl_module, decl_storage, decl_error, decl_event,78	dispatch::DispatchResult,79	ensure, fail,80	weights::{Weight},81	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82	BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_std::{vec, vec::Vec};87use up_data_structs::{88	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89	MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,90	MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,91	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,92	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,93	PropertyKeyPermission,94};95use pallet_evm::account::CrossAccountId;96use pallet_common::{97	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,98	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,99};100pub mod eth;101102#[cfg(feature = "runtime-benchmarks")]103pub mod benchmarking;104pub mod weights;105use weights::WeightInfo;106107/// A maximum number of levels of depth in the token nesting tree.108pub const NESTING_BUDGET: u32 = 5;109110decl_error! {111	/// Errors for the common Unique transactions.112	pub enum Error for Module<T: Config> {113		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].114		CollectionDecimalPointLimitExceeded,115		/// This address is not set as sponsor, use setCollectionSponsor first.116		ConfirmUnsetSponsorFail,117		/// Length of items properties must be greater than 0.118		EmptyArgument,119		/// Repertition is only supported by refungible collection.120		RepartitionCalledOnNonRefungibleCollection,121	}122}123124/// Configuration trait of this pallet.125pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {126	/// Overarching event type.127	type RuntimeEvent: From<Event<Self>> + Into<<Self as frame_system::Config>::RuntimeEvent>;128129	/// Weight information for extrinsics in this pallet.130	type WeightInfo: WeightInfo;131132	/// Weight information for common pallet operations.133	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;134135	/// Weight info information for extra refungible pallet operations.136	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;137}138139decl_event! {140	pub enum Event<T>141	where142		<T as frame_system::Config>::AccountId,143		<T as pallet_evm::Config>::CrossAccountId,144	{145		/// Collection sponsor was removed146		///147		/// # Arguments148		/// * collection_id: ID of the affected collection.149		CollectionSponsorRemoved(CollectionId),150151		/// Collection admin was added152		///153		/// # Arguments154		/// * collection_id: ID of the affected collection.155		/// * admin: Admin address.156		CollectionAdminAdded(CollectionId, CrossAccountId),157158		/// Collection owned was changed159		///160		/// # Arguments161		/// * collection_id: ID of the affected collection.162		/// * owner: New owner address.163		CollectionOwnedChanged(CollectionId, AccountId),164165		/// Collection sponsor was set166		///167		/// # Arguments168		/// * collection_id: ID of the affected collection.169		/// * owner: New sponsor address.170		CollectionSponsorSet(CollectionId, AccountId),171172		/// New sponsor was confirm173		///174		/// # Arguments175		/// * collection_id: ID of the affected collection.176		/// * sponsor: New sponsor address.177		SponsorshipConfirmed(CollectionId, AccountId),178179		/// Collection admin was removed180		///181		/// # Arguments182		/// * collection_id: ID of the affected collection.183		/// * admin: Removed admin address.184		CollectionAdminRemoved(CollectionId, CrossAccountId),185186		/// Address was removed from the allow list187		///188		/// # Arguments189		/// * collection_id: ID of the affected collection.190		/// * user: Address of the removed account.191		AllowListAddressRemoved(CollectionId, CrossAccountId),192193		/// Address was added to the allow list194		///195		/// # Arguments196		/// * collection_id: ID of the affected collection.197		/// * user: Address of the added account.198		AllowListAddressAdded(CollectionId, CrossAccountId),199200		/// Collection limits were set201		///202		/// # Arguments203		/// * collection_id: ID of the affected collection.204		CollectionLimitSet(CollectionId),205206		/// Collection permissions were set207		///208		/// # Arguments209		/// * collection_id: ID of the affected collection.210		CollectionPermissionSet(CollectionId),211	}212}213214type SelfWeightOf<T> = <T as Config>::WeightInfo;215216// # Used definitions217//218// ## User control levels219//220// chain-controlled - key is uncontrolled by user221//                    i.e autoincrementing index222//                    can use non-cryptographic hash223// real - key is controlled by user224//        but it is hard to generate enough colliding values, i.e owner of signed txs225//        can use non-cryptographic hash226// controlled - key is completly controlled by users227//              i.e maps with mutable keys228//              should use cryptographic hash229//230// ## User control level downgrade reasons231//232// ?1 - chain-controlled -> controlled233//      collections/tokens can be destroyed, resulting in massive holes234// ?2 - chain-controlled -> controlled235//      same as ?1, but can be only added, resulting in easier exploitation236// ?3 - real -> controlled237//      no confirmation required, so addresses can be easily generated238decl_storage! {239	trait Store for Module<T: Config> as Unique {240241		//#region Private members242		/// Used for migrations243		ChainVersion: u64;244		//#endregion245246		//#region Tokens transfer sponosoring rate limit baskets247		/// (Collection id (controlled?2), who created (real))248		/// TODO: Off chain worker should remove from this map when collection gets removed249		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;250		/// Collection id (controlled?2), token id (controlled?2)251		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;252		/// Collection id (controlled?2), owning user (real)253		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		/// Collection id (controlled?2), token id (controlled?2)255		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>;256		//#endregion257258		/// Variable metadata sponsoring259		/// Collection id (controlled?2), token id (controlled?2)260		#[deprecated]261		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262		/// Last sponsoring of token property setting // todo:doc rephrase this and the following263		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;264265		/// Last sponsoring of NFT approval in a collection266		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;267		/// Last sponsoring of fungible tokens approval in a collection268		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269		/// Last sponsoring of RFT approval in a collection270		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>;271	}272}273274decl_module! {275	/// Type alias to Pallet, to be used by construct_runtime.276	pub struct Module<T: Config> for enum Call277	where278		origin: T::RuntimeOrigin279	{280		type Error = Error<T>;281282		#[doc = "A maximum number of levels of depth in the token nesting tree."]283		const NESTING_BUDGET: u32 = NESTING_BUDGET;284285		#[doc = "Maximal length of a collection name."]286		const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;287288		#[doc = "Maximal length of a collection description."]289		const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;290291		#[doc = "Maximal length of a token prefix."]292		const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;293294		#[doc = "Maximum admins per collection."]295		const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;296297		#[doc = "Maximal length of a property key."]298		const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;299300		#[doc = "Maximal length of a property value."]301		const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;302303		#[doc = "A maximum number of token properties."]304		const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;305306		#[doc = "Maximum size for all collection properties."]307		const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;308309		#[doc = "Maximum size of all token properties."]310		const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;311312		#[doc = "Default NFT collection limit."]313		const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT);314315		#[doc = "Default RFT collection limit."]316		const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible);317318		#[doc = "Default FT collection limit."]319		const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0));320321322		pub fn deposit_event() = default;323324		fn on_initialize(_now: T::BlockNumber) -> Weight {325			Weight::zero()326		}327328		fn on_runtime_upgrade() -> Weight {329			Weight::zero()330		}331332		/// Create a collection of tokens.333		///334		/// Each Token may have multiple properties encoded as an array of bytes335		/// of certain length. The initial owner of the collection is set336		/// to the address that signed the transaction and can be changed later.337		///338		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.339		///340		/// # Permissions341		///342		/// * Anyone - becomes the owner of the new collection.343		///344		/// # Arguments345		///346		/// * `collection_name`: Wide-character string with collection name347		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).348		/// * `collection_description`: Wide-character string with collection description349		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).350		/// * `token_prefix`: Byte string containing the token prefix to mark a collection351		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).352		/// * `mode`: Type of items stored in the collection and type dependent data.353		// returns collection ID354		#[weight = <SelfWeightOf<T>>::create_collection()]355		#[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]356		pub fn create_collection(357			origin,358			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,359			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,360			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,361			mode: CollectionMode362		) -> DispatchResult {363			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {364				name: collection_name,365				description: collection_description,366				token_prefix,367				mode,368				..Default::default()369			};370			Self::create_collection_ex(origin, data)371		}372373		/// Create a collection with explicit parameters.374		///375		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.376		///377		/// # Permissions378		///379		/// * Anyone - becomes the owner of the new collection.380		///381		/// # Arguments382		///383		/// * `data`: Explicit data of a collection used for its creation.384		#[weight = <SelfWeightOf<T>>::create_collection()]385		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {386			let sender = ensure_signed(origin)?;387388			// =========389			let sender = T::CrossAccountId::from_sub(sender);390			let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;391392			Ok(())393		}394395		/// Destroy a collection if no tokens exist within.396		///397		/// # Permissions398		///399		/// * Collection owner400		///401		/// # Arguments402		///403		/// * `collection_id`: Collection to destroy.404		#[weight = <SelfWeightOf<T>>::destroy_collection()]405		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407408			Self::destroy_collection_internal(sender, collection_id)409		}410411		/// Add an address to allow list.412		///413		/// # Permissions414		///415		/// * Collection owner416		/// * Collection admin417		///418		/// # Arguments419		///420		/// * `collection_id`: ID of the modified collection.421		/// * `address`: ID of the address to be added to the allowlist.422		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]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)?;427			collection.check_is_internal()?;428429			<PalletCommon<T>>::toggle_allowlist(430				&collection,431				&sender,432				&address,433				true,434			)?;435436			Self::deposit_event(Event::<T>::AllowListAddressAdded(437				collection_id,438				address439			));440441			Ok(())442		}443444		/// Remove an address from allow list.445		///446		/// # Permissions447		///448		/// * Collection owner449		/// * Collection admin450		///451		/// # Arguments452		///453		/// * `collection_id`: ID of the modified collection.454		/// * `address`: ID of the address to be removed from the allowlist.455		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]456		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{457458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459			let collection = <CollectionHandle<T>>::try_get(collection_id)?;460			collection.check_is_internal()?;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		/// Change the owner of the collection.478		///479		/// # Permissions480		///481		/// * Collection owner482		///483		/// # Arguments484		///485		/// * `collection_id`: ID of the modified collection.486		/// * `new_owner`: ID of the account that will become the owner.487		#[weight = <SelfWeightOf<T>>::change_collection_owner()]488		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {489490			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);491492			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;493			target_collection.check_is_internal()?;494			target_collection.check_is_owner(&sender)?;495496			target_collection.owner = new_owner.clone();497			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(498				collection_id,499				new_owner500			));501502			target_collection.save()503		}504505		/// Add an admin to a collection.506		///507		/// NFT Collection can be controlled by multiple admin addresses508		/// (some which can also be servers, for example). Admins can issue509		/// and burn NFTs, as well as add and remove other admins,510		/// but cannot change NFT or Collection ownership.511		///512		/// # Permissions513		///514		/// * Collection owner515		/// * Collection admin516		///517		/// # Arguments518		///519		/// * `collection_id`: ID of the Collection to add an admin for.520		/// * `new_admin`: Address of new admin to add.521		#[weight = <SelfWeightOf<T>>::add_collection_admin()]522		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {523			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);524			let collection = <CollectionHandle<T>>::try_get(collection_id)?;525			collection.check_is_internal()?;526527			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(528				collection_id,529				new_admin_id.clone()530			));531532			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)533		}534535		/// Remove admin of a collection.536		///537		/// An admin address can remove itself. List of admins may become empty,538		/// in which case only Collection Owner will be able to add an Admin.539		///540		/// # Permissions541		///542		/// * Collection owner543		/// * Collection admin544		///545		/// # Arguments546		///547		/// * `collection_id`: ID of the collection to remove the admin for.548		/// * `account_id`: Address of the admin to remove.549		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]550		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552			let collection = <CollectionHandle<T>>::try_get(collection_id)?;553			collection.check_is_internal()?;554555			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(556				collection_id,557				account_id.clone()558			));559560			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)561		}562563		/// Set (invite) a new collection sponsor.564		///565		/// If successful, confirmation from the sponsor-to-be will be pending.566		///567		/// # Permissions568		///569		/// * Collection owner570		/// * Collection admin571		///572		/// # Arguments573		///574		/// * `collection_id`: ID of the modified collection.575		/// * `new_sponsor`: ID of the account of the sponsor-to-be.576		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]577		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {578			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);579580			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581			target_collection.check_is_owner_or_admin(&sender)?;582			target_collection.check_is_internal()?;583584			target_collection.set_sponsor(new_sponsor.clone())?;585586			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(587				collection_id,588				new_sponsor589			));590591			target_collection.save()592		}593594		/// Confirm own sponsorship of a collection, becoming the sponsor.595		///596		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].597		/// Sponsor can pay the fees of a transaction instead of the sender,598		/// but only within specified limits.599		///600		/// # Permissions601		///602		/// * Sponsor-to-be603		///604		/// # Arguments605		///606		/// * `collection_id`: ID of the collection with the pending sponsor.607		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]608		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {609			let sender = ensure_signed(origin)?;610611			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;612			target_collection.check_is_internal()?;613			ensure!(614				target_collection.confirm_sponsorship(&sender)?,615				Error::<T>::ConfirmUnsetSponsorFail616			);617618			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(619				collection_id,620				sender621			));622623			target_collection.save()624		}625626		/// Remove a collection's a sponsor, making everyone pay for their own transactions.627		///628		/// # Permissions629		///630		/// * Collection owner631		///632		/// # Arguments633		///634		/// * `collection_id`: ID of the collection with the sponsor to remove.635		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]636		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638639			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;640			target_collection.check_is_internal()?;641			target_collection.check_is_owner(&sender)?;642643			target_collection.sponsorship = SponsorshipState::Disabled;644645			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(646				collection_id647			));648			target_collection.save()649		}650651		/// Mint an item within a collection.652		///653		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].654		///655		/// # Permissions656		///657		/// * Collection owner658		/// * Collection admin659		/// * Anyone if660		///     * Allow List is enabled, and661		///     * Address is added to allow list, and662		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])663		///664		/// # Arguments665		///666		/// * `collection_id`: ID of the collection to which an item would belong.667		/// * `owner`: Address of the initial owner of the item.668		/// * `data`: Token data describing the item to store on chain.669		#[weight = T::CommonWeightInfo::create_item()]670		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {671			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672			let budget = budget::Value::new(NESTING_BUDGET);673674			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))675		}676677		/// Create multiple items within a collection.678		///679		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].680		///681		/// # Permissions682		///683		/// * Collection owner684		/// * Collection admin685		/// * Anyone if686		///     * Allow List is enabled, and687		///     * Address is added to the allow list, and688		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])689		///690		/// # Arguments691		///692		/// * `collection_id`: ID of the collection to which the tokens would belong.693		/// * `owner`: Address of the initial owner of the tokens.694		/// * `items_data`: Vector of data describing each item to be created.695		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]696		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {697			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);698			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699			let budget = budget::Value::new(NESTING_BUDGET);700701			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))702		}703704		/// Add or change collection properties.705		///706		/// # Permissions707		///708		/// * Collection owner709		/// * Collection admin710		///711		/// # Arguments712		///713		/// * `collection_id`: ID of the modified collection.714		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.715		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.716		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]717		pub fn set_collection_properties(718			origin,719			collection_id: CollectionId,720			properties: Vec<Property>721		) -> DispatchResultWithPostInfo {722			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);723724			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))727		}728729		/// Delete specified collection properties.730		///731		/// # Permissions732		///733		/// * Collection Owner734		/// * Collection Admin735		///736		/// # Arguments737		///738		/// * `collection_id`: ID of the modified collection.739		/// * `property_keys`: Vector of keys of the properties to be deleted.740		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.741		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]742		pub fn delete_collection_properties(743			origin,744			collection_id: CollectionId,745			property_keys: Vec<PropertyKey>,746		) -> DispatchResultWithPostInfo {747			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);748749			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);750751			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))752		}753754		/// Add or change token properties according to collection's permissions.755		/// Currently properties only work with NFTs.756		///757		/// # Permissions758		///759		/// * Depends on collection's token property permissions and specified property mutability:760		/// 	* Collection owner761		/// 	* Collection admin762		/// 	* Token owner763		///764		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].765		///766		/// # Arguments767		///768		/// * `collection_id: ID of the collection to which the token belongs.769		/// * `token_id`: ID of the modified token.770		/// * `properties`: Vector of key-value pairs stored as the token's metadata.771		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.772		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]773		pub fn set_token_properties(774			origin,775			collection_id: CollectionId,776			token_id: TokenId,777			properties: Vec<Property>778		) -> DispatchResultWithPostInfo {779			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);780781			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782			let budget = budget::Value::new(NESTING_BUDGET);783784			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))785		}786787		/// Delete specified token properties. Currently properties only work with NFTs.788		///789		/// # Permissions790		///791		/// * Depends on collection's token property permissions and specified property mutability:792		/// 	* Collection owner793		/// 	* Collection admin794		/// 	* Token owner795		///796		/// # Arguments797		///798		/// * `collection_id`: ID of the collection to which the token belongs.799		/// * `token_id`: ID of the modified token.800		/// * `property_keys`: Vector of keys of the properties to be deleted.801		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.802		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]803		pub fn delete_token_properties(804			origin,805			collection_id: CollectionId,806			token_id: TokenId,807			property_keys: Vec<PropertyKey>808		) -> DispatchResultWithPostInfo {809			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);810811			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);812			let budget = budget::Value::new(NESTING_BUDGET);813814			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))815		}816817		/// Add or change token property permissions of a collection.818		///819		/// Without a permission for a particular key, a property with that key820		/// cannot be created in a token.821		///822		/// # Permissions823		///824		/// * Collection owner825		/// * Collection admin826		///827		/// # Arguments828		///829		/// * `collection_id`: ID of the modified collection.830		/// * `property_permissions`: Vector of permissions for property keys.831		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.832		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]833		pub fn set_token_property_permissions(834			origin,835			collection_id: CollectionId,836			property_permissions: Vec<PropertyKeyPermission>,837		) -> DispatchResultWithPostInfo {838			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);839840			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841842			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))843		}844845		/// Create multiple items within a collection with explicitly specified initial parameters.846		///847		/// # Permissions848		///849		/// * Collection owner850		/// * Collection admin851		/// * Anyone if852		///     * Allow List is enabled, and853		///     * Address is added to allow list, and854		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])855		///856		/// # Arguments857		///858		/// * `collection_id`: ID of the collection to which the tokens would belong.859		/// * `data`: Explicit item creation data.860		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]861		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {862			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);863			let budget = budget::Value::new(NESTING_BUDGET);864865			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))866		}867868		/// Completely allow or disallow transfers for a particular collection.869		///870		/// # Permissions871		///872		/// * Collection owner873		///874		/// # Arguments875		///876		/// * `collection_id`: ID of the collection.877		/// * `value`: New value of the flag, are transfers allowed?878		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]879		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;882			target_collection.check_is_internal()?;883			target_collection.check_is_owner(&sender)?;884885			// =========886887			target_collection.limits.transfers_enabled = Some(value);888			target_collection.save()889		}890891		/// Destroy an item.892		///893		/// # Permissions894		///895		/// * Collection owner896		/// * Collection admin897		/// * Current item owner898		///899		/// # Arguments900		///901		/// * `collection_id`: ID of the collection to which the item belongs.902		/// * `item_id`: ID of item to burn.903		/// * `value`: Number of pieces of the item to destroy.904		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.905		///     * Fungible Mode: The desired number of pieces to burn.906		///     * Re-Fungible Mode: The desired number of pieces to burn.907		#[weight = T::CommonWeightInfo::burn_item()]908		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {909			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);910911			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;912			if value == 1 {913				<NftTransferBasket<T>>::remove(collection_id, item_id);914				<NftApproveBasket<T>>::remove(collection_id, item_id);915			}916			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?917			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());918			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));919			Ok(post_info)920		}921922		/// Destroy a token on behalf of the owner as a non-owner account.923		///924		/// See also: [`approve`][`Pallet::approve`].925		///926		/// After this method executes, one approval is removed from the total so that927		/// the approved address will not be able to transfer this item again from this owner.928		///929		/// # Permissions930		///931		/// * Collection owner932		/// * Collection admin933		/// * Current token owner934		/// * Address approved by current item owner935		///936		/// # Arguments937		///938		/// * `from`: The owner of the burning item.939		/// * `collection_id`: ID of the collection to which the item belongs.940		/// * `item_id`: ID of item to burn.941		/// * `value`: Number of pieces to burn.942		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.943		///     * Fungible Mode: The desired number of pieces to burn.944		///     * Re-Fungible Mode: The desired number of pieces to burn.945		#[weight = T::CommonWeightInfo::burn_from()]946		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {947			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948			let budget = budget::Value::new(NESTING_BUDGET);949950			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))951		}952953		/// Change ownership of the token.954		///955		/// # Permissions956		///957		/// * Collection owner958		/// * Collection admin959		/// * Current token owner960		///961		/// # Arguments962		///963		/// * `recipient`: Address of token recipient.964		/// * `collection_id`: ID of the collection the item belongs to.965		/// * `item_id`: ID of the item.966		///     * Non-Fungible Mode: Required.967		///     * Fungible Mode: Ignored.968		///     * Re-Fungible Mode: Required.969		///970		/// * `value`: Amount to transfer.971		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.972		///     * Fungible Mode: The desired number of pieces to transfer.973		///     * Re-Fungible Mode: The desired number of pieces to transfer.974		#[weight = T::CommonWeightInfo::transfer()]975		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {976			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);977			let budget = budget::Value::new(NESTING_BUDGET);978979			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))980		}981982		/// Allow a non-permissioned address to transfer or burn an item.983		///984		/// # Permissions985		///986		/// * Collection owner987		/// * Collection admin988		/// * Current item owner989		///990		/// # Arguments991		///992		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.993		/// * `collection_id`: ID of the collection the item belongs to.994		/// * `item_id`: ID of the item transactions on which are now approved.995		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).996		/// Set to 0 to revoke the approval.997		#[weight = T::CommonWeightInfo::approve()]998		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {999			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10001001			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1002		}10031004		/// Change ownership of an item on behalf of the owner as a non-owner account.1005		///1006		/// See the [`approve`][`Pallet::approve`] method for additional information.1007		///1008		/// After this method executes, one approval is removed from the total so that1009		/// the approved address will not be able to transfer this item again from this owner.1010		///1011		/// # Permissions1012		///1013		/// * Collection owner1014		/// * Collection admin1015		/// * Current item owner1016		/// * Address approved by current item owner1017		///1018		/// # Arguments1019		///1020		/// * `from`: Address that currently owns the token.1021		/// * `recipient`: Address of the new token-owner-to-be.1022		/// * `collection_id`: ID of the collection the item.1023		/// * `item_id`: ID of the item to be transferred.1024		/// * `value`: Amount to transfer.1025		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1026		///     * Fungible Mode: The desired number of pieces to transfer.1027		///     * Re-Fungible Mode: The desired number of pieces to transfer.1028		#[weight = T::CommonWeightInfo::transfer_from()]1029		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1030			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1031			let budget = budget::Value::new(NESTING_BUDGET);10321033			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1034		}10351036		/// Set specific limits of a collection. Empty, or None fields mean chain default.1037		///1038		/// # Permissions1039		///1040		/// * Collection owner1041		/// * Collection admin1042		///1043		/// # Arguments1044		///1045		/// * `collection_id`: ID of the modified collection.1046		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1047		/// will not overwrite the old ones.1048		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1049		pub fn set_collection_limits(1050			origin,1051			collection_id: CollectionId,1052			new_limit: CollectionLimits,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_internal()?;1057			target_collection.check_is_owner_or_admin(&sender)?;1058			let old_limit = &target_collection.limits;10591060			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10611062			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1063				collection_id1064			));10651066			target_collection.save()1067		}10681069		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1070		///1071		/// # Permissions1072		///1073		/// * Collection owner1074		/// * Collection admin1075		///1076		/// # Arguments1077		///1078		/// * `collection_id`: ID of the modified collection.1079		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1080		/// will not overwrite the old ones.1081		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1082		pub fn set_collection_permissions(1083			origin,1084			collection_id: CollectionId,1085			new_permission: CollectionPermissions,1086		) -> DispatchResult {1087			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1088			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1089			target_collection.check_is_internal()?;1090			target_collection.check_is_owner_or_admin(&sender)?;1091			let old_limit = &target_collection.permissions;10921093			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10941095			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1096				collection_id1097			));10981099			target_collection.save()1100		}11011102		/// Re-partition a refungible token, while owning all of its parts/pieces.1103		///1104		/// # Permissions1105		///1106		/// * Token owner (must own every part)1107		///1108		/// # Arguments1109		///1110		/// * `collection_id`: ID of the collection the RFT belongs to.1111		/// * `token_id`: ID of the RFT.1112		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1113		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1114		pub fn repartition(1115			origin,1116			collection_id: CollectionId,1117			token_id: TokenId,1118			amount: u128,1119		) -> DispatchResultWithPostInfo {1120			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1121			dispatch_tx::<T, _>(collection_id, |d| {1122				if let Some(refungible_extensions) = d.refungible_extensions() {1123					refungible_extensions.repartition(&sender, token_id, amount)1124				} else {1125					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1126				}1127			})1128		}1129	}1130}11311132impl<T: Config> Pallet<T> {1133	/// Force set `sponsor` for `collection`.1134	///1135	/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1136	/// from the `sponsor` is not required.1137	///1138	/// # Arguments1139	///1140	/// * `sponsor`: ID of the account of the sponsor-to-be.1141	/// * `collection_id`: ID of the modified collection.1142	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1143		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1144		target_collection.check_is_internal()?;1145		target_collection.set_sponsor(sponsor.clone())?;11461147		Self::deposit_event(Event::<T>::CollectionSponsorSet(1148			collection_id,1149			sponsor.clone(),1150		));11511152		ensure!(1153			target_collection.confirm_sponsorship(&sponsor)?,1154			Error::<T>::ConfirmUnsetSponsorFail1155		);11561157		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11581159		target_collection.save()1160	}11611162	/// Force remove `sponsor` for `collection`.1163	///1164	/// Differs from `remove_sponsor` in that1165	/// it doesn't require consent from the `owner` of the collection.1166	///1167	/// # Arguments1168	///1169	/// * `collection_id`: ID of the modified collection.1170	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1171		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1172		target_collection.check_is_internal()?;1173		target_collection.sponsorship = SponsorshipState::Disabled;11741175		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11761177		target_collection.save()1178	}11791180	#[inline(always)]1181	pub(crate) fn destroy_collection_internal(1182		sender: T::CrossAccountId,1183		collection_id: CollectionId,1184	) -> DispatchResult {1185		let collection = <CollectionHandle<T>>::try_get(collection_id)?;1186		collection.check_is_internal()?;11871188		T::CollectionDispatch::destroy(sender, collection)?;11891190		// TODO: basket cleanup should be moved elsewhere1191		// Maybe runtime dispatch.rs should perform it?11921193		let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1194		let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1195		let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);11961197		let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1198		let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1199		let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);12001201		Ok(())1202	}1203}
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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77	decl_module, decl_storage, decl_error, decl_event,78	dispatch::DispatchResult,79	ensure, fail,80	weights::{Weight},81	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82	BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_std::{vec, vec::Vec};87use up_data_structs::{88	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89	MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,90	MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,91	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,92	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,93	PropertyKeyPermission,94};95use pallet_evm::account::CrossAccountId;96use pallet_common::{97	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,98	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,99};100pub mod eth;101102#[cfg(feature = "runtime-benchmarks")]103pub mod benchmarking;104pub mod weights;105use weights::WeightInfo;106107/// A maximum number of levels of depth in the token nesting tree.108pub const NESTING_BUDGET: u32 = 5;109110decl_error! {111	/// Errors for the common Unique transactions.112	pub enum Error for Module<T: Config> {113		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].114		CollectionDecimalPointLimitExceeded,115		/// This address is not set as sponsor, use setCollectionSponsor first.116		ConfirmUnsetSponsorFail,117		/// Length of items properties must be greater than 0.118		EmptyArgument,119		/// Repertition is only supported by refungible collection.120		RepartitionCalledOnNonRefungibleCollection,121	}122}123124/// Configuration trait of this pallet.125pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {126	/// Overarching event type.127	type RuntimeEvent: From<Event<Self>> + Into<<Self as frame_system::Config>::RuntimeEvent>;128129	/// Weight information for extrinsics in this pallet.130	type WeightInfo: WeightInfo;131132	/// Weight information for common pallet operations.133	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;134135	/// Weight info information for extra refungible pallet operations.136	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;137}138139decl_event! {140	pub enum Event<T>141	where142		<T as frame_system::Config>::AccountId,143		<T as pallet_evm::Config>::CrossAccountId,144	{145		/// Collection sponsor was removed146		///147		/// # Arguments148		/// * collection_id: ID of the affected collection.149		CollectionSponsorRemoved(CollectionId),150151		/// Collection admin was added152		///153		/// # Arguments154		/// * collection_id: ID of the affected collection.155		/// * admin: Admin address.156		CollectionAdminAdded(CollectionId, CrossAccountId),157158		/// Collection owned was changed159		///160		/// # Arguments161		/// * collection_id: ID of the affected collection.162		/// * owner: New owner address.163		CollectionOwnedChanged(CollectionId, AccountId),164165		/// Collection sponsor was set166		///167		/// # Arguments168		/// * collection_id: ID of the affected collection.169		/// * owner: New sponsor address.170		CollectionSponsorSet(CollectionId, AccountId),171172		/// New sponsor was confirm173		///174		/// # Arguments175		/// * collection_id: ID of the affected collection.176		/// * sponsor: New sponsor address.177		SponsorshipConfirmed(CollectionId, AccountId),178179		/// Collection admin was removed180		///181		/// # Arguments182		/// * collection_id: ID of the affected collection.183		/// * admin: Removed admin address.184		CollectionAdminRemoved(CollectionId, CrossAccountId),185186		/// Address was removed from the allow list187		///188		/// # Arguments189		/// * collection_id: ID of the affected collection.190		/// * user: Address of the removed account.191		AllowListAddressRemoved(CollectionId, CrossAccountId),192193		/// Address was added to the allow list194		///195		/// # Arguments196		/// * collection_id: ID of the affected collection.197		/// * user: Address of the added account.198		AllowListAddressAdded(CollectionId, CrossAccountId),199200		/// Collection limits were set201		///202		/// # Arguments203		/// * collection_id: ID of the affected collection.204		CollectionLimitSet(CollectionId),205206		/// Collection permissions were set207		///208		/// # Arguments209		/// * collection_id: ID of the affected collection.210		CollectionPermissionSet(CollectionId),211	}212}213214type SelfWeightOf<T> = <T as Config>::WeightInfo;215216// # Used definitions217//218// ## User control levels219//220// chain-controlled - key is uncontrolled by user221//                    i.e autoincrementing index222//                    can use non-cryptographic hash223// real - key is controlled by user224//        but it is hard to generate enough colliding values, i.e owner of signed txs225//        can use non-cryptographic hash226// controlled - key is completly controlled by users227//              i.e maps with mutable keys228//              should use cryptographic hash229//230// ## User control level downgrade reasons231//232// ?1 - chain-controlled -> controlled233//      collections/tokens can be destroyed, resulting in massive holes234// ?2 - chain-controlled -> controlled235//      same as ?1, but can be only added, resulting in easier exploitation236// ?3 - real -> controlled237//      no confirmation required, so addresses can be easily generated238decl_storage! {239	trait Store for Module<T: Config> as Unique {240241		//#region Private members242		/// Used for migrations243		ChainVersion: u64;244		//#endregion245246		//#region Tokens transfer sponosoring rate limit baskets247		/// (Collection id (controlled?2), who created (real))248		/// TODO: Off chain worker should remove from this map when collection gets removed249		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;250		/// Collection id (controlled?2), token id (controlled?2)251		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;252		/// Collection id (controlled?2), owning user (real)253		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		/// Collection id (controlled?2), token id (controlled?2)255		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>;256		//#endregion257258		/// Variable metadata sponsoring259		/// Collection id (controlled?2), token id (controlled?2)260		#[deprecated]261		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262		/// Last sponsoring of token property setting // todo:doc rephrase this and the following263		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;264265		/// Last sponsoring of NFT approval in a collection266		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;267		/// Last sponsoring of fungible tokens approval in a collection268		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269		/// Last sponsoring of RFT approval in a collection270		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>;271	}272}273274decl_module! {275	/// Type alias to Pallet, to be used by construct_runtime.276	pub struct Module<T: Config> for enum Call277	where278		origin: T::RuntimeOrigin279	{280		type Error = Error<T>;281282		#[doc = "A maximum number of levels of depth in the token nesting tree."]283		const NESTING_BUDGET: u32 = NESTING_BUDGET;284285		#[doc = "Maximal length of a collection name."]286		const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;287288		#[doc = "Maximal length of a collection description."]289		const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;290291		#[doc = "Maximal length of a token prefix."]292		const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;293294		#[doc = "Maximum admins per collection."]295		const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;296297		#[doc = "Maximal length of a property key."]298		const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;299300		#[doc = "Maximal length of a property value."]301		const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;302303		#[doc = "A maximum number of token properties."]304		const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;305306		#[doc = "Maximum size for all collection properties."]307		const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;308309		#[doc = "Maximum size of all token properties."]310		const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;311312		#[doc = "Default NFT collection limit."]313		const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT);314315		#[doc = "Default RFT collection limit."]316		const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible);317318		#[doc = "Default FT collection limit."]319		const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0));320321322		pub fn deposit_event() = default;323324		fn on_initialize(_now: T::BlockNumber) -> Weight {325			Weight::zero()326		}327328		fn on_runtime_upgrade() -> Weight {329			Weight::zero()330		}331332		/// Create a collection of tokens.333		///334		/// Each Token may have multiple properties encoded as an array of bytes335		/// of certain length. The initial owner of the collection is set336		/// to the address that signed the transaction and can be changed later.337		///338		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.339		///340		/// # Permissions341		///342		/// * Anyone - becomes the owner of the new collection.343		///344		/// # Arguments345		///346		/// * `collection_name`: Wide-character string with collection name347		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).348		/// * `collection_description`: Wide-character string with collection description349		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).350		/// * `token_prefix`: Byte string containing the token prefix to mark a collection351		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).352		/// * `mode`: Type of items stored in the collection and type dependent data.353		// returns collection ID354		#[weight = <SelfWeightOf<T>>::create_collection()]355		#[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]356		pub fn create_collection(357			origin,358			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,359			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,360			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,361			mode: CollectionMode362		) -> DispatchResult {363			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {364				name: collection_name,365				description: collection_description,366				token_prefix,367				mode,368				..Default::default()369			};370			Self::create_collection_ex(origin, data)371		}372373		/// Create a collection with explicit parameters.374		///375		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.376		///377		/// # Permissions378		///379		/// * Anyone - becomes the owner of the new collection.380		///381		/// # Arguments382		///383		/// * `data`: Explicit data of a collection used for its creation.384		#[weight = <SelfWeightOf<T>>::create_collection()]385		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {386			let sender = ensure_signed(origin)?;387388			// =========389			let sender = T::CrossAccountId::from_sub(sender);390			let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;391392			Ok(())393		}394395		/// Destroy a collection if no tokens exist within.396		///397		/// # Permissions398		///399		/// * Collection owner400		///401		/// # Arguments402		///403		/// * `collection_id`: Collection to destroy.404		#[weight = <SelfWeightOf<T>>::destroy_collection()]405		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407408			Self::destroy_collection_internal(sender, collection_id)409		}410411		/// Add an address to allow list.412		///413		/// # Permissions414		///415		/// * Collection owner416		/// * Collection admin417		///418		/// # Arguments419		///420		/// * `collection_id`: ID of the modified collection.421		/// * `address`: ID of the address to be added to the allowlist.422		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]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)?;427			collection.check_is_internal()?;428429			<PalletCommon<T>>::toggle_allowlist(430				&collection,431				&sender,432				&address,433				true,434			)?;435436			Self::deposit_event(Event::<T>::AllowListAddressAdded(437				collection_id,438				address439			));440441			Ok(())442		}443444		/// Remove an address from allow list.445		///446		/// # Permissions447		///448		/// * Collection owner449		/// * Collection admin450		///451		/// # Arguments452		///453		/// * `collection_id`: ID of the modified collection.454		/// * `address`: ID of the address to be removed from the allowlist.455		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]456		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{457458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459			let collection = <CollectionHandle<T>>::try_get(collection_id)?;460			collection.check_is_internal()?;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		/// Change the owner of the collection.478		///479		/// # Permissions480		///481		/// * Collection owner482		///483		/// # Arguments484		///485		/// * `collection_id`: ID of the modified collection.486		/// * `new_owner`: ID of the account that will become the owner.487		#[weight = <SelfWeightOf<T>>::change_collection_owner()]488		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {489490			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);491492			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;493			target_collection.check_is_internal()?;494			target_collection.check_is_owner(&sender)?;495496			target_collection.owner = new_owner.clone();497			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(498				collection_id,499				new_owner500			));501502			target_collection.save()503		}504505		/// Add an admin to a collection.506		///507		/// NFT Collection can be controlled by multiple admin addresses508		/// (some which can also be servers, for example). Admins can issue509		/// and burn NFTs, as well as add and remove other admins,510		/// but cannot change NFT or Collection ownership.511		///512		/// # Permissions513		///514		/// * Collection owner515		/// * Collection admin516		///517		/// # Arguments518		///519		/// * `collection_id`: ID of the Collection to add an admin for.520		/// * `new_admin`: Address of new admin to add.521		#[weight = <SelfWeightOf<T>>::add_collection_admin()]522		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {523			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);524			let collection = <CollectionHandle<T>>::try_get(collection_id)?;525			collection.check_is_internal()?;526527			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(528				collection_id,529				new_admin_id.clone()530			));531532			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)533		}534535		/// Remove admin of a collection.536		///537		/// An admin address can remove itself. List of admins may become empty,538		/// in which case only Collection Owner will be able to add an Admin.539		///540		/// # Permissions541		///542		/// * Collection owner543		/// * Collection admin544		///545		/// # Arguments546		///547		/// * `collection_id`: ID of the collection to remove the admin for.548		/// * `account_id`: Address of the admin to remove.549		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]550		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552			let collection = <CollectionHandle<T>>::try_get(collection_id)?;553			collection.check_is_internal()?;554555			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(556				collection_id,557				account_id.clone()558			));559560			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)561		}562563		/// Set (invite) a new collection sponsor.564		///565		/// If successful, confirmation from the sponsor-to-be will be pending.566		///567		/// # Permissions568		///569		/// * Collection owner570		/// * Collection admin571		///572		/// # Arguments573		///574		/// * `collection_id`: ID of the modified collection.575		/// * `new_sponsor`: ID of the account of the sponsor-to-be.576		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]577		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {578			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);579580			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581			target_collection.check_is_owner_or_admin(&sender)?;582			target_collection.check_is_internal()?;583584			target_collection.set_sponsor(new_sponsor.clone())?;585586			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(587				collection_id,588				new_sponsor589			));590591			target_collection.save()592		}593594		/// Confirm own sponsorship of a collection, becoming the sponsor.595		///596		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].597		/// Sponsor can pay the fees of a transaction instead of the sender,598		/// but only within specified limits.599		///600		/// # Permissions601		///602		/// * Sponsor-to-be603		///604		/// # Arguments605		///606		/// * `collection_id`: ID of the collection with the pending sponsor.607		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]608		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {609			let sender = ensure_signed(origin)?;610611			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;612			target_collection.check_is_internal()?;613			ensure!(614				target_collection.confirm_sponsorship(&sender)?,615				Error::<T>::ConfirmUnsetSponsorFail616			);617618			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(619				collection_id,620				sender621			));622623			target_collection.save()624		}625626		/// Remove a collection's a sponsor, making everyone pay for their own transactions.627		///628		/// # Permissions629		///630		/// * Collection owner631		///632		/// # Arguments633		///634		/// * `collection_id`: ID of the collection with the sponsor to remove.635		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]636		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638639			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;640			target_collection.check_is_internal()?;641			target_collection.check_is_owner(&sender)?;642643			target_collection.sponsorship = SponsorshipState::Disabled;644645			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(646				collection_id647			));648			target_collection.save()649		}650651		/// Mint an item within a collection.652		///653		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].654		///655		/// # Permissions656		///657		/// * Collection owner658		/// * Collection admin659		/// * Anyone if660		///     * Allow List is enabled, and661		///     * Address is added to allow list, and662		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])663		///664		/// # Arguments665		///666		/// * `collection_id`: ID of the collection to which an item would belong.667		/// * `owner`: Address of the initial owner of the item.668		/// * `data`: Token data describing the item to store on chain.669		#[weight = T::CommonWeightInfo::create_item()]670		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {671			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672			let budget = budget::Value::new(NESTING_BUDGET);673674			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))675		}676677		/// Create multiple items within a collection.678		///679		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].680		///681		/// # Permissions682		///683		/// * Collection owner684		/// * Collection admin685		/// * Anyone if686		///     * Allow List is enabled, and687		///     * Address is added to the allow list, and688		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])689		///690		/// # Arguments691		///692		/// * `collection_id`: ID of the collection to which the tokens would belong.693		/// * `owner`: Address of the initial owner of the tokens.694		/// * `items_data`: Vector of data describing each item to be created.695		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]696		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {697			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);698			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699			let budget = budget::Value::new(NESTING_BUDGET);700701			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))702		}703704		/// Add or change collection properties.705		///706		/// # Permissions707		///708		/// * Collection owner709		/// * Collection admin710		///711		/// # Arguments712		///713		/// * `collection_id`: ID of the modified collection.714		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.715		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.716		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]717		pub fn set_collection_properties(718			origin,719			collection_id: CollectionId,720			properties: Vec<Property>721		) -> DispatchResultWithPostInfo {722			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);723724			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))727		}728729		/// Delete specified collection properties.730		///731		/// # Permissions732		///733		/// * Collection Owner734		/// * Collection Admin735		///736		/// # Arguments737		///738		/// * `collection_id`: ID of the modified collection.739		/// * `property_keys`: Vector of keys of the properties to be deleted.740		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.741		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]742		pub fn delete_collection_properties(743			origin,744			collection_id: CollectionId,745			property_keys: Vec<PropertyKey>,746		) -> DispatchResultWithPostInfo {747			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);748749			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);750751			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))752		}753754		/// Add or change token properties according to collection's permissions.755		/// Currently properties only work with NFTs.756		///757		/// # Permissions758		///759		/// * Depends on collection's token property permissions and specified property mutability:760		/// 	* Collection owner761		/// 	* Collection admin762		/// 	* Token owner763		///764		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].765		///766		/// # Arguments767		///768		/// * `collection_id: ID of the collection to which the token belongs.769		/// * `token_id`: ID of the modified token.770		/// * `properties`: Vector of key-value pairs stored as the token's metadata.771		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.772		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]773		pub fn set_token_properties(774			origin,775			collection_id: CollectionId,776			token_id: TokenId,777			properties: Vec<Property>778		) -> DispatchResultWithPostInfo {779			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);780781			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782			let budget = budget::Value::new(NESTING_BUDGET);783784			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))785		}786787		/// Delete specified token properties. Currently properties only work with NFTs.788		///789		/// # Permissions790		///791		/// * Depends on collection's token property permissions and specified property mutability:792		/// 	* Collection owner793		/// 	* Collection admin794		/// 	* Token owner795		///796		/// # Arguments797		///798		/// * `collection_id`: ID of the collection to which the token belongs.799		/// * `token_id`: ID of the modified token.800		/// * `property_keys`: Vector of keys of the properties to be deleted.801		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.802		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]803		pub fn delete_token_properties(804			origin,805			collection_id: CollectionId,806			token_id: TokenId,807			property_keys: Vec<PropertyKey>808		) -> DispatchResultWithPostInfo {809			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);810811			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);812			let budget = budget::Value::new(NESTING_BUDGET);813814			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))815		}816817		/// Add or change token property permissions of a collection.818		///819		/// Without a permission for a particular key, a property with that key820		/// cannot be created in a token.821		///822		/// # Permissions823		///824		/// * Collection owner825		/// * Collection admin826		///827		/// # Arguments828		///829		/// * `collection_id`: ID of the modified collection.830		/// * `property_permissions`: Vector of permissions for property keys.831		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.832		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]833		pub fn set_token_property_permissions(834			origin,835			collection_id: CollectionId,836			property_permissions: Vec<PropertyKeyPermission>,837		) -> DispatchResultWithPostInfo {838			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);839840			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841842			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))843		}844845		/// Create multiple items within a collection with explicitly specified initial parameters.846		///847		/// # Permissions848		///849		/// * Collection owner850		/// * Collection admin851		/// * Anyone if852		///     * Allow List is enabled, and853		///     * Address is added to allow list, and854		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])855		///856		/// # Arguments857		///858		/// * `collection_id`: ID of the collection to which the tokens would belong.859		/// * `data`: Explicit item creation data.860		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]861		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {862			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);863			let budget = budget::Value::new(NESTING_BUDGET);864865			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))866		}867868		/// Completely allow or disallow transfers for a particular collection.869		///870		/// # Permissions871		///872		/// * Collection owner873		///874		/// # Arguments875		///876		/// * `collection_id`: ID of the collection.877		/// * `value`: New value of the flag, are transfers allowed?878		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]879		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;882			target_collection.check_is_internal()?;883			target_collection.check_is_owner(&sender)?;884885			// =========886887			target_collection.limits.transfers_enabled = Some(value);888			target_collection.save()889		}890891		/// Destroy an item.892		///893		/// # Permissions894		///895		/// * Collection owner896		/// * Collection admin897		/// * Current item owner898		///899		/// # Arguments900		///901		/// * `collection_id`: ID of the collection to which the item belongs.902		/// * `item_id`: ID of item to burn.903		/// * `value`: Number of pieces of the item to destroy.904		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.905		///     * Fungible Mode: The desired number of pieces to burn.906		///     * Re-Fungible Mode: The desired number of pieces to burn.907		#[weight = T::CommonWeightInfo::burn_item()]908		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {909			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);910911			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;912			if value == 1 {913				<NftTransferBasket<T>>::remove(collection_id, item_id);914				<NftApproveBasket<T>>::remove(collection_id, item_id);915			}916			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?917			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());918			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));919			Ok(post_info)920		}921922		/// Destroy a token on behalf of the owner as a non-owner account.923		///924		/// See also: [`approve`][`Pallet::approve`].925		///926		/// After this method executes, one approval is removed from the total so that927		/// the approved address will not be able to transfer this item again from this owner.928		///929		/// # Permissions930		///931		/// * Collection owner932		/// * Collection admin933		/// * Current token owner934		/// * Address approved by current item owner935		///936		/// # Arguments937		///938		/// * `from`: The owner of the burning item.939		/// * `collection_id`: ID of the collection to which the item belongs.940		/// * `item_id`: ID of item to burn.941		/// * `value`: Number of pieces to burn.942		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.943		///     * Fungible Mode: The desired number of pieces to burn.944		///     * Re-Fungible Mode: The desired number of pieces to burn.945		#[weight = T::CommonWeightInfo::burn_from()]946		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {947			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948			let budget = budget::Value::new(NESTING_BUDGET);949950			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))951		}952953		/// Change ownership of the token.954		///955		/// # Permissions956		///957		/// * Collection owner958		/// * Collection admin959		/// * Current token owner960		///961		/// # Arguments962		///963		/// * `recipient`: Address of token recipient.964		/// * `collection_id`: ID of the collection the item belongs to.965		/// * `item_id`: ID of the item.966		///     * Non-Fungible Mode: Required.967		///     * Fungible Mode: Ignored.968		///     * Re-Fungible Mode: Required.969		///970		/// * `value`: Amount to transfer.971		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.972		///     * Fungible Mode: The desired number of pieces to transfer.973		///     * Re-Fungible Mode: The desired number of pieces to transfer.974		#[weight = T::CommonWeightInfo::transfer()]975		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {976			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);977			let budget = budget::Value::new(NESTING_BUDGET);978979			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))980		}981982		/// Allow a non-permissioned address to transfer or burn an item.983		///984		/// # Permissions985		///986		/// * Collection owner987		/// * Collection admin988		/// * Current item owner989		///990		/// # Arguments991		///992		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.993		/// * `collection_id`: ID of the collection the item belongs to.994		/// * `item_id`: ID of the item transactions on which are now approved.995		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).996		/// Set to 0 to revoke the approval.997		#[weight = T::CommonWeightInfo::approve()]998		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {999			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10001001			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1002		}10031004		/// Change ownership of an item on behalf of the owner as a non-owner account.1005		///1006		/// See the [`approve`][`Pallet::approve`] method for additional information.1007		///1008		/// After this method executes, one approval is removed from the total so that1009		/// the approved address will not be able to transfer this item again from this owner.1010		///1011		/// # Permissions1012		///1013		/// * Collection owner1014		/// * Collection admin1015		/// * Current item owner1016		/// * Address approved by current item owner1017		///1018		/// # Arguments1019		///1020		/// * `from`: Address that currently owns the token.1021		/// * `recipient`: Address of the new token-owner-to-be.1022		/// * `collection_id`: ID of the collection the item.1023		/// * `item_id`: ID of the item to be transferred.1024		/// * `value`: Amount to transfer.1025		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1026		///     * Fungible Mode: The desired number of pieces to transfer.1027		///     * Re-Fungible Mode: The desired number of pieces to transfer.1028		#[weight = T::CommonWeightInfo::transfer_from()]1029		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1030			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1031			let budget = budget::Value::new(NESTING_BUDGET);10321033			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1034		}10351036		/// Set specific limits of a collection. Empty, or None fields mean chain default.1037		///1038		/// # Permissions1039		///1040		/// * Collection owner1041		/// * Collection admin1042		///1043		/// # Arguments1044		///1045		/// * `collection_id`: ID of the modified collection.1046		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1047		/// will not overwrite the old ones.1048		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1049		pub fn set_collection_limits(1050			origin,1051			collection_id: CollectionId,1052			new_limit: CollectionLimits,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_internal()?;1057			target_collection.check_is_owner_or_admin(&sender)?;1058			let old_limit = &target_collection.limits;10591060			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10611062			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1063				collection_id1064			));10651066			target_collection.save()1067		}10681069		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1070		///1071		/// # Permissions1072		///1073		/// * Collection owner1074		/// * Collection admin1075		///1076		/// # Arguments1077		///1078		/// * `collection_id`: ID of the modified collection.1079		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1080		/// will not overwrite the old ones.1081		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1082		pub fn set_collection_permissions(1083			origin,1084			collection_id: CollectionId,1085			new_permission: CollectionPermissions,1086		) -> DispatchResult {1087			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1088			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1089			target_collection.check_is_internal()?;1090			target_collection.check_is_owner_or_admin(&sender)?;1091			let old_limit = &target_collection.permissions;10921093			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10941095			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1096				collection_id1097			));10981099			target_collection.save()1100		}11011102		/// Re-partition a refungible token, while owning all of its parts/pieces.1103		///1104		/// # Permissions1105		///1106		/// * Token owner (must own every part)1107		///1108		/// # Arguments1109		///1110		/// * `collection_id`: ID of the collection the RFT belongs to.1111		/// * `token_id`: ID of the RFT.1112		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1113		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1114		pub fn repartition(1115			origin,1116			collection_id: CollectionId,1117			token_id: TokenId,1118			amount: u128,1119		) -> DispatchResultWithPostInfo {1120			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1121			dispatch_tx::<T, _>(collection_id, |d| {1122				if let Some(refungible_extensions) = d.refungible_extensions() {1123					refungible_extensions.repartition(&sender, token_id, amount)1124				} else {1125					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1126				}1127			})1128		}11291130		/// Sets or unsets the approval of a given operator.1131		///1132		/// An operator is allowed to transfer all tokens of the sender on their behalf.1133		///1134		/// # Arguments1135		///1136		/// * `owner`: Token owner1137		/// * `operator`: Operator1138		/// * `approve`: Is operator enabled or disabled1139		#[weight = T::CommonWeightInfo::set_approval_for_all()]1140		pub fn set_approval_for_all(1141			origin,1142			collection_id: CollectionId,1143			operator: T::CrossAccountId,1144			approve: bool,1145		) -> DispatchResultWithPostInfo {1146			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1147			dispatch_tx::<T, _>(collection_id, |d| {1148				d.set_approval_for_all(sender, operator, approve)1149			})1150		}1151	}1152}11531154impl<T: Config> Pallet<T> {1155	/// Force set `sponsor` for `collection`.1156	///1157	/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1158	/// from the `sponsor` is not required.1159	///1160	/// # Arguments1161	///1162	/// * `sponsor`: ID of the account of the sponsor-to-be.1163	/// * `collection_id`: ID of the modified collection.1164	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1165		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1166		target_collection.check_is_internal()?;1167		target_collection.set_sponsor(sponsor.clone())?;11681169		Self::deposit_event(Event::<T>::CollectionSponsorSet(1170			collection_id,1171			sponsor.clone(),1172		));11731174		ensure!(1175			target_collection.confirm_sponsorship(&sponsor)?,1176			Error::<T>::ConfirmUnsetSponsorFail1177		);11781179		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11801181		target_collection.save()1182	}11831184	/// Force remove `sponsor` for `collection`.1185	///1186	/// Differs from `remove_sponsor` in that1187	/// it doesn't require consent from the `owner` of the collection.1188	///1189	/// # Arguments1190	///1191	/// * `collection_id`: ID of the modified collection.1192	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1193		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1194		target_collection.check_is_internal()?;1195		target_collection.sponsorship = SponsorshipState::Disabled;11961197		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11981199		target_collection.save()1200	}12011202	#[inline(always)]1203	pub(crate) fn destroy_collection_internal(1204		sender: T::CrossAccountId,1205		collection_id: CollectionId,1206	) -> DispatchResult {1207		let collection = <CollectionHandle<T>>::try_get(collection_id)?;1208		collection.check_is_internal()?;12091210		T::CollectionDispatch::destroy(sender, collection)?;12111212		// TODO: basket cleanup should be moved elsewhere1213		// Maybe runtime dispatch.rs should perform it?12141215		let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1216		let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1217		let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);12181219		let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1220		let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1221		let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);12221223		Ok(())1224	}1225}
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -132,5 +132,8 @@
 		fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result<Option<u128>>;
 
 		fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec<CrossAccountId>>;
+
+		/// Get whether an operator is approved by a given owner.
+		fn is_approved_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> Result<bool>;
 	}
 }
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -187,6 +187,10 @@
                 fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
                     dispatch_unique_runtime!(collection.total_pieces(token_id))
                 }
+
+		        fn is_approved_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> Result<bool, DispatchError> {
+                    dispatch_unique_runtime!(collection.is_approved_for_all(owner, operator))
+                }
             }
 
             impl app_promotion_rpc::AppPromotionApi<Block, BlockNumber, CrossAccountId, AccountId> for Runtime {
modifiedruntime/common/weights.rsdiffbeforeafterboth
--- a/runtime/common/weights.rs
+++ b/runtime/common/weights.rs
@@ -120,6 +120,10 @@
 	fn token_owner() -> Weight {
 		max_weight_of!(token_owner())
 	}
+
+	fn set_approval_for_all() -> Weight {
+		max_weight_of!(set_approval_for_all())
+	}
 }
 
 #[cfg(feature = "refungible")]
modifiedtests/src/approve.test.tsdiffbeforeafterboth
--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -603,3 +603,40 @@
     await expect(approveTx()).to.be.rejected;
   });
 });
+
+describe('Normal user can approve other users to be wallet operator:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const donor = await privateKey({filename: __filename});
+      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+    });
+  });
+
+  itSub('[nft] Enable and disable approval', async ({helper}) => {
+    const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+    await helper.nft.mintToken(alice, {collectionId: collectionId, owner: alice.address});
+    const checkBeforeApprovalTx = () => helper.nft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+    expect(await checkBeforeApprovalTx()).to.be.false;
+    await helper.nft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, true);
+    const checkAfterApprovalTx = () => helper.nft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+    expect(await checkAfterApprovalTx()).to.be.true;
+    await helper.nft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, false);
+    const checkAfterDisapprovalTx = () => helper.nft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+    expect(await checkAfterDisapprovalTx()).to.be.false;
+  });
+
+  itSub.ifWithPallets('[rft] Enable and disable approval', [Pallets.ReFungible], async ({helper}) => {
+    const {collectionId} = await helper.rft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+    const checkBeforeApprovalTx = () => helper.rft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+    expect(await checkBeforeApprovalTx()).to.be.false;
+    await helper.rft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, true);
+    const checkAfterApprovalTx = () => helper.rft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+    expect(await checkAfterApprovalTx()).to.be.true;
+    await helper.rft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, false);
+    const checkAfterDisapprovalTx = () => helper.rft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+    expect(await checkAfterDisapprovalTx()).to.be.false;
+  });
+});
modifiedtests/src/eth/abi/nonFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -384,7 +384,7 @@
       { "internalType": "address", "name": "operator", "type": "address" }
     ],
     "name": "isApprovedForAll",
-    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
     "stateMutability": "view",
     "type": "function"
   },
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -366,7 +366,7 @@
       { "internalType": "address", "name": "operator", "type": "address" }
     ],
     "name": "isApprovedForAll",
-    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
     "stateMutability": "view",
     "type": "function"
   },
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -671,7 +671,10 @@
 	///  or in textual repr: approve(address,uint256)
 	function approve(address approved, uint256 tokenId) external;
 
-	/// @dev Not implemented
+	/// @notice Sets or unsets the approval of a given operator.
+	///  An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// @param operator Operator
+	/// @param approved Is operator enabled or disabled
 	/// @dev EVM selector for this function is: 0xa22cb465,
 	///  or in textual repr: setApprovalForAll(address,bool)
 	function setApprovalForAll(address operator, bool approved) external;
@@ -681,10 +684,10 @@
 	///  or in textual repr: getApproved(uint256)
 	function getApproved(uint256 tokenId) external view returns (address);
 
-	/// @dev Not implemented
+	/// @notice Tells whether an operator is approved by a given owner.
 	/// @dev EVM selector for this function is: 0xe985e9c5,
 	///  or in textual repr: isApprovedForAll(address,address)
-	function isApprovedForAll(address owner, address operator) external view returns (address);
+	function isApprovedForAll(address owner, address operator) external view returns (bool);
 
 	/// @notice Returns collection helper contract address
 	/// @dev EVM selector for this function is: 0x1896cce6,
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -668,7 +668,10 @@
 	///  or in textual repr: approve(address,uint256)
 	function approve(address approved, uint256 tokenId) external;
 
-	/// @dev Not implemented
+	/// @notice Sets or unsets the approval of a given operator.
+	///  An operator is allowed to transfer all tokens of the sender on their behalf.
+	/// @param operator Operator
+	/// @param approved Is operator enabled or disabled
 	/// @dev EVM selector for this function is: 0xa22cb465,
 	///  or in textual repr: setApprovalForAll(address,bool)
 	function setApprovalForAll(address operator, bool approved) external;
@@ -678,10 +681,10 @@
 	///  or in textual repr: getApproved(uint256)
 	function getApproved(uint256 tokenId) external view returns (address);
 
-	/// @dev Not implemented
+	/// @notice Tells whether an operator is approved by a given owner.
 	/// @dev EVM selector for this function is: 0xe985e9c5,
 	///  or in textual repr: isApprovedForAll(address,address)
-	function isApprovedForAll(address owner, address operator) external view returns (address);
+	function isApprovedForAll(address owner, address operator) external view returns (bool);
 
 	/// @notice Returns collection helper contract address
 	/// @dev EVM selector for this function is: 0x1896cce6,
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -249,6 +249,114 @@
     }
   });
 
+  itEth('Can perform setApprovalForAll()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = helper.eth.createAccount();
+
+    const collection = await helper.nft.mintCollection(minter, {});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+
+    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();
+    expect(approvedBefore).to.be.equal(false);
+
+    {
+      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+
+      expect(result.events.ApprovalForAll).to.be.like({
+        address: collectionAddress,
+        event: 'ApprovalForAll',
+        returnValues: {
+          owner,
+          operator,
+          approved: true,
+        },
+      });
+
+      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+      expect(approvedAfter).to.be.equal(true);
+    }
+
+    {
+      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});
+
+      expect(result.events.ApprovalForAll).to.be.like({
+        address: collectionAddress,
+        event: 'ApprovalForAll',
+        returnValues: {
+          owner,
+          operator,
+          approved: false,
+        },
+      });
+
+      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+      expect(approvedAfter).to.be.equal(false);
+    }
+  });
+
+  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    {
+      await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+      const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});
+      const events = result.events.Transfer;
+
+      expect(events).to.be.like({
+        address,
+        event: 'Transfer',
+        returnValues: {
+          from: owner,
+          to: '0x0000000000000000000000000000000000000000',
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+  });
+  
+  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = await helper.eth.createAccountWithBalance(donor);
+    const receiver = charlie;
+
+    const token = await collection.mintToken(minter, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    {
+      await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+      const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: owner,
+          to: helper.address.substrateToEth(receiver.address),
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});
+  });
+
   itEth('Can perform burnFromCross()', async ({helper}) => {
     const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
     const ownerSub = bob;
@@ -822,3 +930,53 @@
     expect(symbol).to.equal('CHANGE');
   });
 });
+
+describe('Negative tests', () => {
+  let donor: IKeyringPair;
+  let minter: IKeyringPair;
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+      [minter, alice, bob] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+    });
+  });
+
+  itEth('[negative] Cant perform burn without approval', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = bob;
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;
+    }
+  });
+
+  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+    const owner = bob;
+    const receiver = alice;
+
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;
+    }
+  });
+});
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -169,6 +169,136 @@
     }
   });
 
+  itEth('Can perform setApprovalForAll()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = helper.eth.createAccount();
+
+    const collection = await helper.rft.mintCollection(minter, {});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
+
+    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();
+    expect(approvedBefore).to.be.equal(false);
+
+    {
+      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+
+      expect(result.events.ApprovalForAll).to.be.like({
+        address: collectionAddress,
+        event: 'ApprovalForAll',
+        returnValues: {
+          owner,
+          operator,
+          approved: true,
+        },
+      });
+
+      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+      expect(approvedAfter).to.be.equal(true);
+    }
+
+    {
+      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});
+
+      expect(result.events.ApprovalForAll).to.be.like({
+        address: collectionAddress,
+        event: 'ApprovalForAll',
+        returnValues: {
+          owner,
+          operator,
+          approved: false,
+        },
+      });
+
+      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+      expect(approvedAfter).to.be.equal(false);
+    }
+  });
+
+  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    {
+      await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+      const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});
+      const events = result.events.Transfer;
+
+      expect(events).to.be.like({
+        address,
+        event: 'Transfer',
+        returnValues: {
+          from: owner,
+          to: '0x0000000000000000000000000000000000000000',
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+  });
+
+  itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    const rftToken = helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner);
+
+    {
+      await rftToken.methods.approve(operator, 15n).send({from: owner});
+      await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+      await rftToken.methods.burnFrom(owner, 10n).send({from: operator});
+      const allowance = await rftToken.methods.allowance(owner, operator).call();
+      expect(allowance).to.be.equal('5');
+    }
+  });
+  
+  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const operator = await helper.eth.createAccountWithBalance(donor);
+    const receiver = charlie;
+
+    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    {
+      await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+      const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: owner,
+          to: helper.address.substrateToEth(receiver.address),
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);
+  });
+
   itEth('Can perform burn()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');
@@ -594,3 +724,52 @@
     expect(symbol).to.equal('12');
   });
 });
+
+describe('Negative tests', () => {
+  let donor: IKeyringPair;
+  let minter: IKeyringPair;
+  let alice: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);
+    });
+  });
+
+  itEth('[negative] Cant perform burn without approval', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+      await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;
+    }
+  });
+
+  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+    const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+    const receiver = alice;
+
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;
+    }
+  });
+});
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -386,6 +386,10 @@
        **/
       NotFungibleDataUsedToMintFungibleCollectionToken: AugmentedError<ApiType>;
       /**
+       * Setting approval for all is not allowed.
+       **/
+      SettingApprovalForAllNotAllowed: AugmentedError<ApiType>;
+      /**
        * Setting item properties is not allowed.
        **/
       SettingPropertiesNotAllowed: AugmentedError<ApiType>;
modifiedtests/src/interfaces/augment-api-events.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-events.ts
+++ b/tests/src/interfaces/augment-api-events.ts
@@ -107,6 +107,10 @@
        **/
       Approved: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
       /**
+       * Amount pieces of token owned by `sender` was approved for `spender`.
+       **/
+      ApprovedForAll: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
+      /**
        * New collection was created
        **/
       CollectionCreated: AugmentedEvent<ApiType, [u32, u8, AccountId32]>;
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -442,6 +442,10 @@
        **/
       tokensMinted: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
+       * Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+       **/
+      walletOperator: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<Option<bool>>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]>;
+      /**
        * Generic query
        **/
       [key: string]: QueryableStorageEntry<ApiType>;
@@ -645,6 +649,10 @@
        **/
       totalSupply: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
       /**
+       * Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+       **/
+      walletOperator: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<Option<bool>>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]>;
+      /**
        * Generic query
        **/
       [key: string]: QueryableStorageEntry<ApiType>;
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -720,6 +720,10 @@
        **/
       effectiveCollectionLimits: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<UpDataStructsCollectionLimits>>>;
       /**
+       * Tells whether an operator is approved by a given owner.
+       **/
+      isApprovedForAll: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, owner: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, operator: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<bool>>>;
+      /**
        * Get the last token ID created in a collection
        **/
       lastTokenId: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u32>>;
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -1545,6 +1545,18 @@
        **/
       repartition: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, u32, u128]>;
       /**
+       * Sets or unsets the approval of a given operator.
+       * 
+       * An operator is allowed to transfer all tokens of the sender on their behalf.
+       * 
+       * # Arguments
+       * 
+       * * `owner`: Token owner
+       * * `operator`: Operator
+       * * `approve`: Is operator enabled or disabled
+       **/
+      setApprovalForAll: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, operator: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, approve: bool | boolean | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
+      /**
        * Set specific limits of a collection. Empty, or None fields mean chain default.
        * 
        * # Permissions
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -1286,6 +1286,8 @@
   readonly asTransfer: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
   readonly isApproved: boolean;
   readonly asApproved: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
+  readonly isApprovedForAll: boolean;
+  readonly asApprovedForAll: ITuple<[u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
   readonly isCollectionPropertySet: boolean;
   readonly asCollectionPropertySet: ITuple<[u32, Bytes]>;
   readonly isCollectionPropertyDeleted: boolean;
@@ -1296,7 +1298,7 @@
   readonly asTokenPropertyDeleted: ITuple<[u32, u32, Bytes]>;
   readonly isPropertyPermissionSet: boolean;
   readonly asPropertyPermissionSet: ITuple<[u32, Bytes]>;
-  readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
+  readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'ApprovedForAll' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
 }
 
 /** @name PalletConfigurationCall */
@@ -1603,7 +1605,8 @@
   readonly isFungibleItemsDontHaveData: boolean;
   readonly isFungibleDisallowsNesting: boolean;
   readonly isSettingPropertiesNotAllowed: boolean;
-  readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed';
+  readonly isSettingApprovalForAllNotAllowed: boolean;
+  readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed' | 'SettingApprovalForAllNotAllowed';
 }
 
 /** @name PalletInflationCall */
@@ -2309,7 +2312,13 @@
     readonly tokenId: u32;
     readonly amount: u128;
   } & Struct;
-  readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition';
+  readonly isSetApprovalForAll: boolean;
+  readonly asSetApprovalForAll: {
+    readonly collectionId: u32;
+    readonly operator: PalletEvmAccountBasicCrossAccountIdRepr;
+    readonly approve: bool;
+  } & Struct;
+  readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition' | 'SetApprovalForAll';
 }
 
 /** @name PalletUniqueError */
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1046,6 +1046,7 @@
       ItemDestroyed: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,u128)',
       Transfer: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,u128)',
       Approved: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,u128)',
+      ApprovedForAll: '(u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,bool)',
       CollectionPropertySet: '(u32,Bytes)',
       CollectionPropertyDeleted: '(u32,Bytes)',
       TokenPropertySet: '(u32,u32,Bytes)',
@@ -1054,7 +1055,7 @@
     }
   },
   /**
-   * Lookup99: pallet_structure::pallet::Event<T>
+   * Lookup100: pallet_structure::pallet::Event<T>
    **/
   PalletStructureEvent: {
     _enum: {
@@ -1062,7 +1063,7 @@
     }
   },
   /**
-   * Lookup100: pallet_rmrk_core::pallet::Event<T>
+   * Lookup101: pallet_rmrk_core::pallet::Event<T>
    **/
   PalletRmrkCoreEvent: {
     _enum: {
@@ -1139,7 +1140,7 @@
     }
   },
   /**
-   * Lookup101: rmrk_traits::nft::AccountIdOrCollectionNftTuple<sp_core::crypto::AccountId32>
+   * Lookup102: rmrk_traits::nft::AccountIdOrCollectionNftTuple<sp_core::crypto::AccountId32>
    **/
   RmrkTraitsNftAccountIdOrCollectionNftTuple: {
     _enum: {
@@ -2302,7 +2303,12 @@
       repartition: {
         collectionId: 'u32',
         tokenId: 'u32',
-        amount: 'u128'
+        amount: 'u128',
+      },
+      set_approval_for_all: {
+        collectionId: 'u32',
+        operator: 'PalletEvmAccountBasicCrossAccountIdRepr',
+        approve: 'bool'
       }
     }
   },
@@ -3445,7 +3451,7 @@
    * Lookup430: pallet_fungible::pallet::Error<T>
    **/
   PalletFungibleError: {
-    _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed']
+    _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed', 'SettingApprovalForAllNotAllowed']
   },
   /**
    * Lookup431: pallet_refungible::ItemData
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1182,6 +1182,8 @@
     readonly asTransfer: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
     readonly isApproved: boolean;
     readonly asApproved: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
+    readonly isApprovedForAll: boolean;
+    readonly asApprovedForAll: ITuple<[u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
     readonly isCollectionPropertySet: boolean;
     readonly asCollectionPropertySet: ITuple<[u32, Bytes]>;
     readonly isCollectionPropertyDeleted: boolean;
@@ -1192,17 +1194,17 @@
     readonly asTokenPropertyDeleted: ITuple<[u32, u32, Bytes]>;
     readonly isPropertyPermissionSet: boolean;
     readonly asPropertyPermissionSet: ITuple<[u32, Bytes]>;
-    readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
+    readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'ApprovedForAll' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
   }
 
-  /** @name PalletStructureEvent (99) */
+  /** @name PalletStructureEvent (100) */
   interface PalletStructureEvent extends Enum {
     readonly isExecuted: boolean;
     readonly asExecuted: Result<Null, SpRuntimeDispatchError>;
     readonly type: 'Executed';
   }
 
-  /** @name PalletRmrkCoreEvent (100) */
+  /** @name PalletRmrkCoreEvent (101) */
   interface PalletRmrkCoreEvent extends Enum {
     readonly isCollectionCreated: boolean;
     readonly asCollectionCreated: {
@@ -1292,7 +1294,7 @@
     readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'IssuerChanged' | 'CollectionLocked' | 'NftMinted' | 'NftBurned' | 'NftSent' | 'NftAccepted' | 'NftRejected' | 'PropertySet' | 'ResourceAdded' | 'ResourceRemoval' | 'ResourceAccepted' | 'ResourceRemovalAccepted' | 'PrioritySet';
   }
 
-  /** @name RmrkTraitsNftAccountIdOrCollectionNftTuple (101) */
+  /** @name RmrkTraitsNftAccountIdOrCollectionNftTuple (102) */
   interface RmrkTraitsNftAccountIdOrCollectionNftTuple extends Enum {
     readonly isAccountId: boolean;
     readonly asAccountId: AccountId32;
@@ -2539,7 +2541,13 @@
       readonly tokenId: u32;
       readonly amount: u128;
     } & Struct;
-    readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition';
+    readonly isSetApprovalForAll: boolean;
+    readonly asSetApprovalForAll: {
+      readonly collectionId: u32;
+      readonly operator: PalletEvmAccountBasicCrossAccountIdRepr;
+      readonly approve: bool;
+    } & Struct;
+    readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition' | 'SetApprovalForAll';
   }
 
   /** @name UpDataStructsCollectionMode (240) */
@@ -3655,7 +3663,8 @@
     readonly isFungibleItemsDontHaveData: boolean;
     readonly isFungibleDisallowsNesting: boolean;
     readonly isSettingPropertiesNotAllowed: boolean;
-    readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed';
+    readonly isSettingApprovalForAllNotAllowed: boolean;
+    readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed' | 'SettingApprovalForAllNotAllowed';
   }
 
   /** @name PalletRefungibleItemData (431) */
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -175,5 +175,10 @@
       [collectionParam, tokenParam], 
       'Option<u128>',
     ),
+    isApprovedForAll: fun(
+      'Tells whether an operator is approved by a given owner.', 
+      [collectionParam, crossAccountParam('owner'), crossAccountParam('operator')], 
+      'Option<bool>',
+    ),
   },
 };
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -1413,6 +1413,32 @@
   getTokenObject(_collectionId: number, _tokenId: number): any {
     return null;
   }
+
+  /**
+   * Tells whether an operator is approved by a given owner.
+   * @param collectionId ID of collection
+   * @param owner owner address
+	 * @param operator operator addrees
+   * @returns true if operator is enabled
+   */
+  async isApprovedForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {
+    return (await this.helper.callRpc('api.rpc.unique.isApprovedForAll', [collectionId, owner, operator])).toJSON();
+  }
+
+  /** Sets or unsets the approval of a given operator.
+	 *  An operator is allowed to transfer all tokens of the sender on their behalf.
+	 *  @param operator Operator
+	 *  @param approved Is operator enabled or disabled
+   *  @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async setApprovalForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setApprovalForAll', [collectionId, operator, approved],
+      true,
+    );
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');
+  }
 }