git.delta.rocks / unique-network / refs/commits / 9836c2113849

difftreelog

Merge branch 'develop' into feature/internal-audit

Igor Kozyrev2021-11-23parents: #6a168a1 #bba462e.patch.diff
in: master

57 files changed

modified.devcontainer/devcontainer.jsondiffbeforeafterboth
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,8 +1,8 @@
 {
 	"name": "Rust",
     "dockerComposeFile": "./docker-compose.yml",
-    "service": "nft_private",
-    "workspaceFolder": "/workspaces/nft_private",
+    "service": "unique-chain",
+    "workspaceFolder": "/workspaces/unique-chain",
 	"settings": { 
 		"terminal.integrated.shell.linux": "/bin/bash",
 		"lldb.executable": "/usr/bin/lldb",
modified.devcontainer/docker-compose.ymldiffbeforeafterboth
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -1,13 +1,13 @@
 version: '3'
 services:
-  nft_private:
+  unique-chain:
     build: 
         context: .
     environment:
       - JAEGER_AGENT_HOST=jaeger
       - JAEGER_AGENT_PORT=6831
     volumes:
-      - ..:/workspaces/nft_private:cached
+      - ..:/workspaces/unique-chain:cached
       - ../../polkadot:/workspaces/polkadot:cached
       - ../../polkadot-launch:/workspaces/polkadot-launch:cached
       #- ../../frontier:/workspaces/frontier
modified.github/workflows/node_build_test.ymldiffbeforeafterboth
--- a/.github/workflows/node_build_test.yml
+++ b/.github/workflows/node_build_test.yml
@@ -35,10 +35,10 @@
           script: |
             eval $(ssh-agent -s)
             ssh-add /home/devops/.ssh/git_hub
-            git clone git@github.com:UniqueNetwork/nft_private.git
-            cd nft_private
+            git clone git@github.com:UniqueNetwork/unique-chain.git
+            cd unique-chain
             git checkout develop
             # git pull --all
             chmod +x ci_node.sh
             ./ci_node.sh
-            rm -rf /home/polkadot/nft_private
+            rm -rf /home/polkadot/unique-chain
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5097,6 +5097,7 @@
  "frame-system-rpc-runtime-api",
  "hex-literal",
  "nft-data-structs",
+ "orml-vesting",
  "pallet-aura",
  "pallet-balances",
  "pallet-common",
@@ -5109,7 +5110,6 @@
  "pallet-fungible",
  "pallet-inflation",
  "pallet-nft",
- "pallet-nft-transaction-payment",
  "pallet-nonfungible",
  "pallet-randomness-collective-flip",
  "pallet-refungible",
@@ -5120,7 +5120,6 @@
  "pallet-transaction-payment-rpc-runtime-api",
  "pallet-treasury",
  "pallet-unq-scheduler",
- "pallet-vesting",
  "pallet-xcm",
  "parachain-info",
  "parity-scale-codec",
@@ -5142,6 +5141,7 @@
  "sp-transaction-pool",
  "sp-version",
  "substrate-wasm-builder",
+ "up-evm-mapping",
  "up-rpc",
  "xcm",
  "xcm-builder",
@@ -5308,6 +5308,21 @@
 ]
 
 [[package]]
+name = "orml-vesting"
+version = "0.4.1-dev"
+source = "git+https://github.com/UniqueNetwork/open-runtime-module-library#d69f226e332ae29b7b33d53d2f06f309d2986ea0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "scale-info",
+ "serde",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
 name = "owning_ref"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5575,6 +5590,7 @@
  "sp-core",
  "sp-runtime",
  "sp-std",
+ "up-evm-mapping",
 ]
 
 [[package]]
@@ -5824,6 +5840,7 @@
  "sp-io",
  "sp-runtime",
  "sp-std",
+ "up-evm-mapping",
  "up-sponsorship",
 ]
 
@@ -6072,24 +6089,7 @@
  "sp-io",
  "sp-runtime",
  "sp-std",
- "up-sponsorship",
-]
-
-[[package]]
-name = "pallet-nft-transaction-payment"
-version = "3.0.0"
-dependencies = [
- "frame-benchmarking",
- "frame-support",
- "frame-system",
- "pallet-transaction-payment",
- "parity-scale-codec",
- "scale-info",
- "serde",
- "sp-core",
- "sp-io",
- "sp-runtime",
- "sp-std",
+ "up-evm-mapping",
  "up-sponsorship",
 ]
 
@@ -11832,6 +11832,14 @@
 checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
+name = "up-evm-mapping"
+version = "0.1.0"
+dependencies = [
+ "frame-support",
+ "sp-core",
+]
+
+[[package]]
 name = "up-rpc"
 version = "0.1.0"
 dependencies = [
modifiedDockerfile-parachaindiffbeforeafterboth
--- a/Dockerfile-parachain
+++ b/Dockerfile-parachain
@@ -98,7 +98,7 @@
     npm install --global yarn && \
     yarn
 
-COPY --from=builder /nft_parachain/target/$PROFILE/nft /nft_private/target/$PROFILE/
+COPY --from=builder /nft_parachain/target/$PROFILE/nft /unique-chain/target/$PROFILE/
 COPY --from=builder-polkadot /nft_parachain/polkadot/target/$PROFILE/polkadot /polkadot/target/$PROFILE/
 
 CMD export NVM_DIR="$HOME/.nvm" && \
modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -3,7 +3,7 @@
 use codec::Decode;
 use jsonrpc_core::{Error as RpcError, ErrorCode, Result};
 use jsonrpc_derive::rpc;
-use nft_data_structs::{CollectionId, TokenId};
+use nft_data_structs::{Collection, CollectionId, CollectionStats, TokenId};
 use sp_api::{BlockId, BlockT, ProvideRuntimeApi};
 use sp_blockchain::HeaderBackend;
 use up_rpc::NftApi as NftRuntimeApi;
@@ -86,8 +86,23 @@
 		collection: CollectionId,
 		at: Option<BlockHash>,
 	) -> Result<Vec<CrossAccountId>>;
+	#[rpc(name = "nft_allowed")]
+	fn allowed(
+		&self,
+		collection: CollectionId,
+		user: CrossAccountId,
+		at: Option<BlockHash>,
+	) -> Result<bool>;
 	#[rpc(name = "nft_lastTokenId")]
 	fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;
+	#[rpc(name = "nft_collectionById")]
+	fn collection_by_id(
+		&self,
+		collection: CollectionId,
+		at: Option<BlockHash>,
+	) -> Result<Option<Collection<AccountId>>>;
+	#[rpc(name = "nft_collectionStats")]
+	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
 }
 
 pub struct Nft<C, P> {
@@ -160,5 +175,8 @@
 
 	pass_method!(adminlist(collection: CollectionId) -> Vec<CrossAccountId>);
 	pass_method!(allowlist(collection: CollectionId) -> Vec<CrossAccountId>);
+	pass_method!(allowed(collection: CollectionId, user: CrossAccountId) -> bool);
 	pass_method!(last_token_id(collection: CollectionId) -> TokenId);
+	pass_method!(collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>>);
+	pass_method!(collection_stats() -> CollectionStats);
 }
modifiedlaunch-config.jsondiffbeforeafterboth
--- a/launch-config.json
+++ b/launch-config.json
@@ -55,7 +55,7 @@
     },
     "parachains": [
         {
-            "bin": "../nft_private/target/release/nft",
+            "bin": "../unique-chain/target/release/nft",
             "id": "2000",
             "balance": "1000000000000000000000",
             "nodes": [
modifiedpallets/common/Cargo.tomldiffbeforeafterboth
--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -15,10 +15,10 @@
 sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
 sp-std = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
 sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
+up-evm-mapping = { default-features = false, path = '../../primitives/evm-mapping' }
 nft-data-structs = { default-features = false, path = '../../primitives/nft' }
 pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
 evm-coder = { default-features = false, path = '../../crates/evm-coder' }
-
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.12" }
 serde = { version = "1.0.130", default-features = false }
 scale-info = { version = "1.0.0", default-features = false, features = [
@@ -32,6 +32,7 @@
     "frame-system/std",
     "sp-runtime/std",
     "sp-std/std",
+    "up-evm-mapping/std",
     "nft-data-structs/std",
     "pallet-evm/std",
 ]
modifiedpallets/common/src/account.rsdiffbeforeafterboth
--- a/pallets/common/src/account.rs
+++ b/pallets/common/src/account.rs
@@ -2,12 +2,12 @@
 use codec::{Encode, EncodeLike, Decode};
 use sp_core::H160;
 use scale_info::{Type, TypeInfo};
-use sp_core::crypto::AccountId32;
 use core::cmp::Ordering;
 use serde::{Serialize, Deserialize};
 use pallet_evm::AddressMapping;
 use sp_std::vec::Vec;
 use sp_std::clone::Clone;
+use up_evm_mapping::EvmBackwardsAddressMapping;
 
 pub trait CrossAccountId<AccountId>:
 	Encode + EncodeLike + Decode + TypeInfo + Clone + PartialEq + Ord + core::fmt::Debug + Default
@@ -174,19 +174,5 @@
 		} else {
 			BasicCrossAccountIdRepr::Substrate(v.as_sub().clone())
 		}
-	}
-}
-
-pub trait EvmBackwardsAddressMapping<AccountId> {
-	fn from_account_id(account_id: AccountId) -> H160;
-}
-
-/// Should have same mapping as EnsureAddressTruncated
-pub struct MapBackwardsAddressTruncated;
-impl EvmBackwardsAddressMapping<AccountId32> for MapBackwardsAddressTruncated {
-	fn from_account_id(account_id: AccountId32) -> H160 {
-		let mut out = [0; 20];
-		out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]);
-		H160(out)
 	}
 }
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -19,7 +19,7 @@
 pub fn create_collection_raw<T: Config, R>(
 	owner: T::AccountId,
 	mode: CollectionMode,
-	handler: impl FnOnce(Collection<T>) -> Result<CollectionId, DispatchError>,
+	handler: impl FnOnce(Collection<T::AccountId>) -> Result<CollectionId, DispatchError>,
 	cast: impl FnOnce(CollectionHandle<T>) -> R,
 ) -> Result<R, DispatchError> {
 	T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -12,7 +12,7 @@
 	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
 	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
 	COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,
-	WithdrawReasons,
+	WithdrawReasons, CollectionStats,
 };
 pub use pallet::*;
 use sp_core::H160;
@@ -26,7 +26,7 @@
 #[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]
 pub struct CollectionHandle<T: Config> {
 	pub id: CollectionId,
-	collection: Collection<T>,
+	collection: Collection<T::AccountId>,
 	pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,
 }
 impl<T: Config> CollectionHandle<T> {
@@ -78,7 +78,7 @@
 	}
 }
 impl<T: Config> Deref for CollectionHandle<T> {
-	type Target = Collection<T>;
+	type Target = Collection<T::AccountId>;
 
 	fn deref(&self) -> &Self::Target {
 		&self.collection
@@ -141,7 +141,7 @@
 pub mod pallet {
 	use super::*;
 	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};
-	use account::{EvmBackwardsAddressMapping, CrossAccountId};
+	use account::CrossAccountId;
 	use frame_support::traits::Currency;
 	use nft_data_structs::TokenId;
 	use scale_info::TypeInfo;
@@ -153,7 +153,7 @@
 		type CrossAccountId: CrossAccountId<Self::AccountId>;
 
 		type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;
-		type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;
+		type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;
 
 		type Currency: Currency<Self::AccountId>;
 		type CollectionCreationPrice: Get<
@@ -311,7 +311,7 @@
 	pub type CollectionById<T> = StorageMap<
 		Hasher = Blake2_128Concat,
 		Key = CollectionId,
-		Value = Collection<T>,
+		Value = Collection<<T as frame_system::Config>::AccountId>,
 		QueryKind = OptionQuery,
 	>;
 
@@ -344,6 +344,11 @@
 		Value = bool,
 		QueryKind = ValueQuery,
 	>;
+
+	/// Not used by code, exists only to provide some types to metadata
+	#[pallet::storage]
+	pub type DummyStorageValue<T> =
+		StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;
 }
 
 impl<T: Config> Pallet<T> {
@@ -355,10 +360,32 @@
 		);
 		Ok(())
 	}
+	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {
+		<IsAdmin<T>>::iter_prefix((collection,))
+			.map(|(a, _)| a)
+			.collect()
+	}
+	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {
+		<Allowlist<T>>::iter_prefix((collection,))
+			.map(|(a, _)| a)
+			.collect()
+	}
+	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {
+		<Allowlist<T>>::get((collection, user))
+	}
+	pub fn collection_stats() -> CollectionStats {
+		let created = <CreatedCollectionCount<T>>::get();
+		let destroyed = <DestroyedCollectionCount<T>>::get();
+		CollectionStats {
+			created: created.0,
+			destroyed: destroyed.0,
+			alive: created.0 - destroyed.0,
+		}
+	}
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {
+	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
 				data.name.len() <= MAX_COLLECTION_NAME_LENGTH,
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -59,7 +59,8 @@
 
 	fn allowed(&self, contract_address: address, user: address) -> Result<bool> {
 		self.0.consume_sload()?;
-		Ok(<Pallet<T>>::allowed(contract_address, user, true))
+		Ok(<Pallet<T>>::allowed(contract_address, user)
+			|| !<AllowlistEnabled<T>>::get(contract_address))
 	}
 
 	fn allowlist_enabled(&self, contract_address: address) -> Result<bool> {
@@ -113,7 +114,7 @@
 		value: sp_core::U256,
 	) -> Option<PrecompileOutput> {
 		// TODO: Extract to another OnMethodCall handler
-		if !<Pallet<T>>::allowed(*target, *source, true) {
+		if <AllowlistEnabled<T>>::get(target) && !<Pallet<T>>::allowed(*target, *source) {
 			return Some(PrecompileOutput {
 				exit_status: ExitReason::Revert(ExitRevert::Reverted),
 				cost: 0,
@@ -151,22 +152,26 @@
 pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);
 impl<T: Config> SponsorshipHandler<H160, (H160, Vec<u8>)> for HelpersContractSponsoring<T> {
 	fn get_sponsor(who: &H160, call: &(H160, Vec<u8>)) -> Option<H160> {
-		if <SelfSponsoring<T>>::get(&call.0) && <Pallet<T>>::allowed(call.0, *who, false) {
-			let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-			if let Some(last_tx_block) = <SponsorBasket<T>>::get(&call.0, who) {
-				let rate_limit = <SponsoringRateLimit<T>>::get(&call.0);
-				let limit_time = last_tx_block + rate_limit;
+		if !<SelfSponsoring<T>>::get(&call.0) {
+			return None;
+		}
+		if !<Pallet<T>>::allowed(call.0, *who) {
+			return None;
+		}
+		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 
-				if block_number > limit_time {
-					<SponsorBasket<T>>::insert(&call.0, who, block_number);
-					return Some(call.0);
-				}
-			} else {
-				<SponsorBasket<T>>::insert(&call.0, who, block_number);
-				return Some(call.0);
+		if let Some(last_tx_block) = <SponsorBasket<T>>::get(&call.0, who) {
+			let limit = <SponsoringRateLimit<T>>::get(&call.0);
+
+			let timeout = last_tx_block + limit.into();
+			if block_number < timeout {
+				return None;
 			}
 		}
-		None
+
+		<SponsorBasket<T>>::insert(&call.0, who, block_number);
+
+		Some(call.0)
 	}
 }
 
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -76,11 +76,7 @@
 			<SponsoringRateLimit<T>>::insert(contract, rate_limit);
 		}
 
-		/// Default is returned if allowlist is disabled
-		pub fn allowed(contract: H160, user: H160, default: bool) -> bool {
-			if !<AllowlistEnabled<T>>::get(contract) {
-				return default;
-			}
+		pub fn allowed(contract: H160, user: H160) -> bool {
 			<Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user
 		}
 
modifiedpallets/evm-transaction-payment/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-transaction-payment/Cargo.toml
+++ b/pallets/evm-transaction-payment/Cargo.toml
@@ -15,6 +15,7 @@
 fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.12" }
 pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.12" }
 up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring" } 
+up-evm-mapping = { default-features = false, path = "../../primitives/evm-mapping" }
 
 [dependencies.codec]
 default-features = false
@@ -35,4 +36,5 @@
     "pallet-ethereum/std",
     "fp-evm/std",
     "up-sponsorship/std",
+    "up-evm-mapping/std",
 ]
modifiedpallets/evm-transaction-payment/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-transaction-payment/src/lib.rs
+++ b/pallets/evm-transaction-payment/src/lib.rs
@@ -1,98 +1,140 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
+use core::marker::PhantomData;
+use fp_evm::WithdrawReason;
+use frame_support::traits::{Currency, IsSubType};
 pub use pallet::*;
+use pallet_evm::{EVMCurrencyAdapter, EnsureAddressOrigin};
+use sp_core::{H160, U256};
+use sp_runtime::TransactionOutcome;
+use up_sponsorship::SponsorshipHandler;
+use up_evm_mapping::EvmBackwardsAddressMapping;
+use pallet_evm::AddressMapping;
 
 #[frame_support::pallet]
 pub mod pallet {
-	use core::marker::PhantomData;
+	use super::*;
+
 	use frame_support::traits::Currency;
-	use pallet_evm::EVMCurrencyAdapter;
-	use fp_evm::WithdrawReason;
-	use sp_core::{H160, U256};
-	use sp_runtime::TransactionOutcome;
-	use up_sponsorship::SponsorshipHandler;
 	use sp_std::vec::Vec;
 
-	type NegativeImbalanceOf<C, T> =
-		<C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
-
 	#[pallet::config]
 	pub trait Config: frame_system::Config {
-		type SponsorshipHandler: SponsorshipHandler<H160, (H160, Vec<u8>)>;
+		type EvmSponsorshipHandler: SponsorshipHandler<H160, (H160, Vec<u8>)>;
 		type Currency: Currency<Self::AccountId>;
+		type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;
+		type EvmAddressMapping: AddressMapping<Self::AccountId>;
 	}
 
 	#[pallet::pallet]
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
+}
 
-	pub struct ChargeEvmLiquidityInfo<T>
-	where
-		T: Config,
-		T: pallet_evm::Config,
-	{
-		who: H160,
-		negative_imbalance: NegativeImbalanceOf<<T as Config>::Currency, T>,
-	}
+type NegativeImbalanceOf<C, T> =
+	<C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
 
-	pub struct TransactionValidityHack<T: Config>(PhantomData<*const T>);
-	impl<T: Config> fp_evm::TransactionValidityHack for TransactionValidityHack<T> {
-		fn who_pays_fee(origin: H160, reason: &WithdrawReason) -> Option<H160> {
-			match reason {
-				WithdrawReason::Call { target, input } => {
-					// This method is only used for checking, we shouldn't touch storage in it
-					frame_support::storage::with_transaction(|| {
-						TransactionOutcome::Rollback(T::SponsorshipHandler::get_sponsor(
-							&origin,
-							&(*target, input.clone()),
-						))
-					})
-				}
-				_ => None,
+pub struct ChargeEvmLiquidityInfo<T>
+where
+	T: Config,
+	T: pallet_evm::Config,
+{
+	who: H160,
+	negative_imbalance: NegativeImbalanceOf<<T as Config>::Currency, T>,
+}
+
+pub struct TransactionValidityHack<T: Config>(PhantomData<*const T>);
+impl<T: Config> fp_evm::TransactionValidityHack for TransactionValidityHack<T> {
+	fn who_pays_fee(origin: H160, reason: &WithdrawReason) -> Option<H160> {
+		match reason {
+			WithdrawReason::Call { target, input } => {
+				// This method is only used for checking, we shouldn't touch storage in it
+				frame_support::storage::with_transaction(|| {
+					TransactionOutcome::Rollback(T::EvmSponsorshipHandler::get_sponsor(
+						&origin,
+						&(*target, input.clone()),
+					))
+				})
 			}
+			_ => None,
+		}
+	}
+}
+pub struct OnChargeTransaction<T: Config>(PhantomData<*const T>);
+impl<T> pallet_evm::OnChargeEVMTransaction<T> for OnChargeTransaction<T>
+where
+	T: Config,
+	T: pallet_evm::Config,
+{
+	type LiquidityInfo = Option<ChargeEvmLiquidityInfo<T>>;
+
+	fn withdraw_fee(
+		who: &H160,
+		reason: WithdrawReason,
+		fee: U256,
+	) -> core::result::Result<Self::LiquidityInfo, pallet_evm::Error<T>> {
+		let mut who_pays_fee = *who;
+		if let WithdrawReason::Call { target, input } = &reason {
+			who_pays_fee = T::EvmSponsorshipHandler::get_sponsor(who, &(*target, input.clone()))
+				.unwrap_or(who_pays_fee);
 		}
+		let negative_imbalance = EVMCurrencyAdapter::<<T as Config>::Currency, ()>::withdraw_fee(
+			&who_pays_fee,
+			reason,
+			fee,
+		)?;
+		Ok(negative_imbalance.map(|i| ChargeEvmLiquidityInfo {
+			who: who_pays_fee,
+			negative_imbalance: i,
+		}))
 	}
 
-	pub struct OnChargeTransaction<T: Config>(PhantomData<*const T>);
-	impl<T> pallet_evm::OnChargeEVMTransaction<T> for OnChargeTransaction<T>
-	where
-		T: Config,
-		T: pallet_evm::Config,
-	{
-		type LiquidityInfo = Option<ChargeEvmLiquidityInfo<T>>;
+	fn correct_and_deposit_fee(
+		who: &H160,
+		corrected_fee: U256,
+		already_withdrawn: Self::LiquidityInfo,
+	) {
+		<EVMCurrencyAdapter<<T as Config>::Currency, ()> as pallet_evm::OnChargeEVMTransaction<T>>::correct_and_deposit_fee(
+			&already_withdrawn.as_ref().map(|e| e.who).unwrap_or(*who),
+			corrected_fee,
+			already_withdrawn.map(|e| e.negative_imbalance),
+		)
+	}
+}
 
-		fn withdraw_fee(
-			who: &H160,
-			reason: WithdrawReason,
-			fee: U256,
-		) -> core::result::Result<Self::LiquidityInfo, pallet_evm::Error<T>> {
-			let mut who_pays_fee = *who;
-			if let WithdrawReason::Call { target, input } = &reason {
-				who_pays_fee = T::SponsorshipHandler::get_sponsor(who, &(*target, input.clone()))
-					.unwrap_or(who_pays_fee);
+/// Implements sponsoring for evm calls performed from pallet-evm (via api.tx.ethereum.transact/api.tx.evm.call)
+pub struct BridgeSponsorshipHandler<T>(PhantomData<T>);
+impl<T, C> SponsorshipHandler<T::AccountId, C> for BridgeSponsorshipHandler<T>
+where
+	T: Config + pallet_evm::Config,
+	C: IsSubType<pallet_evm::Call<T>>,
+{
+	fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+		match call.is_sub_type()? {
+			pallet_evm::Call::call {
+				source,
+				target,
+				input,
+				..
+			} => {
+				let _ = T::CallOrigin::ensure_address_origin(
+					source,
+					<frame_system::RawOrigin<T::AccountId>>::Signed(who.clone()).into(),
+				)
+				.ok()?;
+				let who = T::EvmBackwardsAddressMapping::from_account_id(who.clone());
+				// Effects from EvmSponsorshipHandler are applied in OnChargeEvmTransaction by pallet_evm::runner
+				// TODO: Should we implement simulation mode (test, but do not apply effects) in `up-sponsorship`?
+				let sponsor = frame_support::storage::with_transaction(|| {
+					TransactionOutcome::Rollback(T::EvmSponsorshipHandler::get_sponsor(
+						&who,
+						&(target.clone(), input.clone()),
+					))
+				})?;
+				let sponsor = T::EvmAddressMapping::into_account_id(sponsor);
+				Some(sponsor)
 			}
-			let negative_imbalance =
-				EVMCurrencyAdapter::<<T as Config>::Currency, ()>::withdraw_fee(
-					&who_pays_fee,
-					reason,
-					fee,
-				)?;
-			Ok(negative_imbalance.map(|i| ChargeEvmLiquidityInfo {
-				who: who_pays_fee,
-				negative_imbalance: i,
-			}))
-		}
-
-		fn correct_and_deposit_fee(
-			who: &H160,
-			corrected_fee: U256,
-			already_withdrawn: Self::LiquidityInfo,
-		) {
-			<EVMCurrencyAdapter<<T as Config>::Currency, ()> as pallet_evm::OnChargeEVMTransaction<T>>::correct_and_deposit_fee(
-				&already_withdrawn.as_ref().map(|e| e.who).unwrap_or(*who),
-				corrected_fee,
-				already_withdrawn.map(|e| e.negative_imbalance),
-			)
+			_ => None,
 		}
 	}
 }
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -52,7 +52,7 @@
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;
 
 	#[pallet::storage]
-	pub(super) type Balance<T: Config> = StorageNMap<
+	pub type Balance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			Key<Blake2_128Concat, T::CrossAccountId>,
@@ -91,8 +91,8 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {
-		PalletCommon::init_collection(data)
+	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(data)
 	}
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
deletedpallets/nft-transaction-payment/Cargo.tomldiffbeforeafterboth
--- a/pallets/nft-transaction-payment/Cargo.toml
+++ /dev/null
@@ -1,51 +0,0 @@
-[package]
-authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
-description = 'Unqiue pallet nft specific transaction payment'
-edition = '2018'
-homepage = 'https://substrate.io'
-license = 'Unlicense'
-name = 'pallet-nft-transaction-payment'
-repository = 'https://github.com/usetech-llc/nft_private/'
-version = '3.0.0'
-
-[package.metadata.docs.rs]
-targets = ['x86_64-unknown-linux-gnu']
-
-# alias "parity-scale-code" to "codec"
-[dependencies.codec]
-default-features = false
-features = ['derive']
-package = 'parity-scale-codec'
-version = '2.3.0'
-
-[dependencies]
-scale-info = { version = "1.0.0", default-features = false, features = ["derive"] }
-serde = { version = "1.0.130", default-features = false }
-frame-support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-frame-system = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-pallet-transaction-payment = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-sp-std = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-frame-benchmarking = { default-features = false, optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-sp-io = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
-
-up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring" } 
-
-[features]
-default = ['std']
-std = [
-    'codec/std',
-    'serde/std',
-    'frame-support/std',
-    'frame-system/std',
-    'sp-core/std',
-    'sp-io/std',
-    'pallet-transaction-payment/std',
-    'sp-std/std',
-    'sp-runtime/std',
-    'frame-benchmarking/std',
-
-    'up-sponsorship/std',
-]
-runtime-benchmarks = ["frame-benchmarking"]
deletedpallets/nft-transaction-payment/README.mddiffbeforeafterboth
--- a/pallets/nft-transaction-payment/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Nft Transaction Payment
-
-## Overview
-
-A module containing the sponsoring logic for paying for sponsored collections
-
-**NOTE:** The scheduled calls will be dispatched with the default filter
-for the origin: namely `frame_system::Config::BaseCallFilter` for all origin
-except root which will get no filter. And not the filter contained in origin
-use to call `fn schedule`.
-
-If a call is scheduled using proxy or whatever mecanism which adds filter,
-then those filter will not be used when dispatching the schedule call.
deletedpallets/nft-transaction-payment/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft-transaction-payment/src/lib.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// This file is subject to the terms and conditions defined in
-// file 'LICENSE', which is part of this source code package.
-//
-
-#![cfg_attr(not(feature = "std"), no_std)]
-
-#[cfg(feature = "std")]
-pub use std::*;
-
-#[cfg(feature = "std")]
-pub use serde::*;
-
-use frame_support::{decl_module, decl_storage};
-use sp_std::prelude::*;
-use up_sponsorship::SponsorshipHandler;
-
-pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
-	type SponsorshipHandler: SponsorshipHandler<Self::AccountId, Self::Call>;
-}
-
-decl_storage! {
-	trait Store for Module<T: Config> as NftTransactionPayment{
-	}
-}
-
-decl_module! {
-	pub struct Module<T: Config> for enum Call
-	where
-		origin: T::Origin,
-	{
-	}
-}
-
-impl<T: Config> Module<T> {
-	pub fn withdraw_type(who: &T::AccountId, call: &T::Call) -> Option<T::AccountId> {
-		T::SponsorshipHandler::get_sponsor(who, call)
-	}
-}
modifiedpallets/nft/Cargo.tomldiffbeforeafterboth
--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -34,6 +34,7 @@
     'fp-evm/std',
     'nft-data-structs/std',
     'up-sponsorship/std',
+    'up-evm-mapping/std',
     'sp-std/std',
     'sp-api/std',
     'sp-runtime/std',
@@ -135,7 +136,7 @@
 sp-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.12" }
 
 up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring" } 
-
+up-evm-mapping = { default-features = false, path = "../../primitives/evm-mapping" }
 evm-coder = { default-features = false, path = "../../crates/evm-coder" }
 pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" }
 primitive-types = { version = "0.10.1", default-features = false, features = [
modifiedpallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -1,123 +1,74 @@
 //! Implements EVM sponsoring logic via OnChargeEVMTransaction
 
-use crate::{Collection, Config, FungibleTransferBasket, NftTransferBasket};
+use crate::{Config, sponsorship::*};
 use evm_coder::{Call, abi::AbiReader};
-use frame_support::{
-	storage::{StorageDoubleMap},
-};
-use pallet_common::eth::map_eth_to_id;
+use pallet_common::{CollectionHandle, eth::map_eth_to_id};
 use sp_core::H160;
 use sp_std::prelude::*;
 use up_sponsorship::SponsorshipHandler;
 use core::marker::PhantomData;
 use core::convert::TryInto;
-use nft_data_structs::{CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT};
-use pallet_common::{
-	CollectionById,
-	account::{CrossAccountId, EvmBackwardsAddressMapping},
-};
+use nft_data_structs::TokenId;
+use up_evm_mapping::EvmBackwardsAddressMapping;
+use pallet_common::account::CrossAccountId;
 
 use pallet_nonfungible::erc::{UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721Call};
 use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call};
 
-struct AnyError;
-
-fn try_sponsor<T: Config>(
-	caller: &H160,
-	collection_id: CollectionId,
-	collection: &Collection<T>,
-	call: &[u8],
-) -> Result<(), AnyError> {
-	let (method_id, mut reader) = AbiReader::new_call(call).map_err(|_| AnyError)?;
-	match &collection.mode {
-		crate::CollectionMode::NFT => {
-			let call: UniqueNFTCall = UniqueNFTCall::parse(method_id, &mut reader)
-				.map_err(|_| AnyError)?
-				.ok_or(AnyError)?;
-			match call {
-				UniqueNFTCall::ERC721UniqueExtensions(ERC721UniqueExtensionsCall::Transfer {
-					token_id,
-					..
-				})
-				| UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => {
-					let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?;
-					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-					let collection_limits = &collection.limits;
-					let limit =
-						collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
-
-					let mut sponsor = true;
-					if <NftTransferBasket<T>>::contains_key(collection_id, token_id) {
-						let last_tx_block = <NftTransferBasket<T>>::get(collection_id, token_id);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsor = false;
-						}
+pub struct NftEthSponsorshipHandler<T: Config>(PhantomData<*const T>);
+impl<T: Config> SponsorshipHandler<H160, (H160, Vec<u8>)> for NftEthSponsorshipHandler<T> {
+	fn get_sponsor(who: &H160, call: &(H160, Vec<u8>)) -> Option<H160> {
+		let collection_id = map_eth_to_id(&call.0)?;
+		let collection = <CollectionHandle<T>>::new(collection_id)?;
+		let sponsor = collection.sponsorship.sponsor()?.clone();
+		let sponsor =
+			<T as pallet_common::Config>::EvmBackwardsAddressMapping::from_account_id(sponsor);
+		let who = T::CrossAccountId::from_eth(*who);
+		let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?;
+		match &collection.mode {
+			crate::CollectionMode::NFT => {
+				let call = UniqueNFTCall::parse(method_id, &mut reader).ok()??;
+				match call {
+					UniqueNFTCall::ERC721UniqueExtensions(
+						ERC721UniqueExtensionsCall::Transfer { token_id, .. },
+					) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
 					}
-					if sponsor {
-						<NftTransferBasket<T>>::insert(collection_id, token_id, block_number);
-						return Ok(());
+					UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						let from = T::CrossAccountId::from_eth(from);
+						withdraw_transfer::<T>(&collection, &from, &token_id).map(|()| sponsor)
 					}
+					UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
+							.map(|()| sponsor)
+					}
+					_ => None,
 				}
-				_ => {}
 			}
-		}
-		crate::CollectionMode::Fungible(_) => {
-			let call: UniqueFungibleCall = UniqueFungibleCall::parse(method_id, &mut reader)
-				.map_err(|_| AnyError)?
-				.ok_or(AnyError)?;
-			#[allow(clippy::single_match)]
-			match call {
-				UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
-					let who = T::CrossAccountId::from_eth(*caller);
-					let collection_limits = &collection.limits;
-					let limit = collection_limits
-						.sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
-
-					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-					let mut sponsored = true;
-					if <FungibleTransferBasket<T>>::contains_key(collection_id, who.as_sub()) {
-						let last_tx_block =
-							<FungibleTransferBasket<T>>::get(collection_id, who.as_sub());
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
+			crate::CollectionMode::Fungible(_) => {
+				let call = UniqueFungibleCall::parse(method_id, &mut reader).ok()??;
+				#[allow(clippy::single_match)]
+				match call {
+					UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
+						withdraw_transfer::<T>(&collection, &who, &TokenId::default())
+							.map(|()| sponsor)
 					}
-					if sponsored {
-						<FungibleTransferBasket<T>>::insert(
-							collection_id,
-							who.as_sub(),
-							block_number,
-						);
-						return Ok(());
+					UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
+						let from = T::CrossAccountId::from_eth(from);
+						withdraw_transfer::<T>(&collection, &from, &TokenId::default())
+							.map(|()| sponsor)
 					}
-				}
-				_ => {}
-			}
-		}
-		_ => {}
-	}
-	Err(AnyError)
-}
-
-pub struct NftEthSponsorshipHandler<T: Config>(PhantomData<*const T>);
-impl<T: Config> SponsorshipHandler<H160, (H160, Vec<u8>)> for NftEthSponsorshipHandler<T> {
-	fn get_sponsor(who: &H160, call: &(H160, Vec<u8>)) -> Option<H160> {
-		if let Some(collection_id) = map_eth_to_id(&call.0) {
-			if let Some(collection) = <CollectionById<T>>::get(collection_id) {
-				if !collection.sponsorship.confirmed() {
-					return None;
+					UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+						withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
+							.map(|()| sponsor)
+					}
+					_ => None,
 				}
-				if try_sponsor(who, collection_id, &collection, &call.1).is_ok() {
-					return collection
-						.sponsorship
-						.sponsor()
-						.cloned()
-						.map(T::EvmBackwardsAddressMapping::from_account_id);
-				}
 			}
+			_ => None,
 		}
-		None
 	}
 }
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24		IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32	pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45	account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,46	Error as CommonError, CommonWeightInfo, Allowlist,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76	/// Error for non-fungible-token module.77	pub enum Error for Module<T: Config> {78		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79		CollectionDecimalPointLimitExceeded,80		/// This address is not set as sponsor, use setCollectionSponsor first.81		ConfirmUnsetSponsorFail,82		/// Length of items properties must be greater than 0.83		EmptyArgument,84		/// Collection limit bounds per collection exceeded85		CollectionLimitBoundsExceeded,86		/// Tried to enable permissions which are only permitted to be disabled87		OwnerPermissionsCantBeReverted,88	}89}90pub trait Config:91	system::Config92	+ pallet_evm_coder_substrate::Config93	+ pallet_common::Config94	+ pallet_nonfungible::Config95	+ pallet_refungible::Config96	+ pallet_fungible::Config97	+ Sized98	+ TypeInfo99{100	/// Weight information for extrinsics in this pallet.101	type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111//                    i.e autoincrementing index112//                    can use non-cryptographic hash113// real - key is controlled by user114//        but it is hard to generate enough colliding values, i.e owner of signed txs115//        can use non-cryptographic hash116// controlled - key is completly controlled by users117//              i.e maps with mutable keys118//              should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123//      collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125//      same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127//      no confirmation required, so addresses can be easily generated128decl_storage! {129	trait Store for Module<T: Config> as Nft {130131		//#region Private members132		/// Used for migrations133		ChainVersion: u64;134		//#endregion135136		//#region Tokens transfer rate limit baskets137		/// (Collection id (controlled?2), who created (real))138		/// TODO: Off chain worker should remove from this map when collection gets removed139		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;140		/// Collection id (controlled?2), token id (controlled?2)141		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;142		/// Collection id (controlled?2), owning user (real)143		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;144		/// Collection id (controlled?2), token id (controlled?2)145		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;146		//#endregion147148		/// Variable metadata sponsoring149		/// Collection id (controlled?2), token id (controlled?2)150		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;151	}152}153154decl_module! {155	pub struct Module<T: Config> for enum Call156	where157		origin: T::Origin158	{159		type Error = Error<T>;160161		fn on_initialize(_now: T::BlockNumber) -> Weight {162			0163		}164165		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.166		///167		/// # Permissions168		///169		/// * Anyone.170		///171		/// # Arguments172		///173		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.174		///175		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.176		///177		/// * token_prefix: UTF-8 string with token prefix.178		///179		/// * mode: [CollectionMode] collection type and type dependent data.180		// returns collection ID181		#[weight = <SelfWeightOf<T>>::create_collection()]182		#[transactional]183		pub fn create_collection(origin,184								 collection_name: Vec<u16>,185								 collection_description: Vec<u16>,186								 token_prefix: Vec<u8>,187								 mode: CollectionMode) -> DispatchResult {188189			// Anyone can create a collection190			let who = ensure_signed(origin)?;191192			// Create new collection193			let new_collection = Collection::<T> {194				owner: who.clone(),195				name: collection_name,196				mode: mode.clone(),197				mint_mode: false,198				access: AccessMode::Normal,199				description: collection_description,200				token_prefix,201				offchain_schema: Vec::new(),202				schema_version: SchemaVersion::ImageURL,203				sponsorship: SponsorshipState::Disabled,204				variable_on_chain_schema: Vec::new(),205				const_on_chain_schema: Vec::new(),206				limits: Default::default(),207				meta_update_permission: Default::default(),208			};209210			let _id = match mode {211				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},212				CollectionMode::Fungible(decimal_points) => {213					// check params214					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);215					PalletFungible::init_collection(new_collection)?216				}217				CollectionMode::ReFungible => {218					PalletRefungible::init_collection(new_collection)?219				}220			};221222			Ok(())223		}224225		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.226		///227		/// # Permissions228		///229		/// * Collection Owner.230		///231		/// # Arguments232		///233		/// * collection_id: collection to destroy.234		#[weight = <SelfWeightOf<T>>::destroy_collection()]235		#[transactional]236		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {237			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);238239			let collection = <CollectionHandle<T>>::try_get(collection_id)?;240			collection.check_is_owner(&sender)?;241242			// =========243244			match collection.mode {245				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,246				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,247				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,248			}249250			<NftTransferBasket<T>>::remove_prefix(collection_id, None);251			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);252			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);253254			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);255256			Ok(())257		}258259		/// Add an address to allow list.260		///261		/// # Permissions262		///263		/// * Collection Owner264		/// * Collection Admin265		///266		/// # Arguments267		///268		/// * collection_id.269		///270		/// * address.271		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]272		#[transactional]273		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{274275			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);276			let collection = <CollectionHandle<T>>::try_get(collection_id)?;277278			<PalletCommon<T>>::toggle_allowlist(279				&collection,280				&sender,281				&address,282				true,283			)?;284285			Ok(())286		}287288		/// Remove an address from allow list.289		///290		/// # Permissions291		///292		/// * Collection Owner293		/// * Collection Admin294		///295		/// # Arguments296		///297		/// * collection_id.298		///299		/// * address.300		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]301		#[transactional]302		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{303304			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);305			let collection = <CollectionHandle<T>>::try_get(collection_id)?;306307			<PalletCommon<T>>::toggle_allowlist(308				&collection,309				&sender,310				&address,311				false,312			)?;313314			Ok(())315		}316317		/// Toggle between normal and allow list access for the methods with access for `Anyone`.318		///319		/// # Permissions320		///321		/// * Collection Owner.322		///323		/// # Arguments324		///325		/// * collection_id.326		///327		/// * mode: [AccessMode]328		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]329		#[transactional]330		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult331		{332			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);333334			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;335			target_collection.check_is_owner(&sender)?;336337			target_collection.access = mode;338			target_collection.save()339		}340341		/// Allows Anyone to create tokens if:342		/// * Allow List is enabled, and343		/// * Address is added to allow list, and344		/// * This method was called with True parameter345		///346		/// # Permissions347		/// * Collection Owner348		///349		/// # Arguments350		///351		/// * collection_id.352		///353		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.354		#[weight = <SelfWeightOf<T>>::set_mint_permission()]355		#[transactional]356		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult357		{358			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);359360			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;361			target_collection.check_is_owner(&sender)?;362363			target_collection.mint_mode = mint_permission;364			target_collection.save()365		}366367		/// Change the owner of the collection.368		///369		/// # Permissions370		///371		/// * Collection Owner.372		///373		/// # Arguments374		///375		/// * collection_id.376		///377		/// * new_owner.378		#[weight = <SelfWeightOf<T>>::change_collection_owner()]379		#[transactional]380		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {381382			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);383384			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;385			target_collection.check_is_owner(&sender)?;386387			target_collection.owner = new_owner;388			target_collection.save()389		}390391		/// Adds an admin of the Collection.392		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.393		///394		/// # Permissions395		///396		/// * Collection Owner.397		/// * Collection Admin.398		///399		/// # Arguments400		///401		/// * collection_id: ID of the Collection to add admin for.402		///403		/// * new_admin_id: Address of new admin to add.404		#[weight = <SelfWeightOf<T>>::add_collection_admin()]405		#[transactional]406		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {407			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408			let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)411		}412413		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.414		///415		/// # Permissions416		///417		/// * Collection Owner.418		/// * Collection Admin.419		///420		/// # Arguments421		///422		/// * collection_id: ID of the Collection to remove admin for.423		///424		/// * account_id: Address of admin to remove.425		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]426		#[transactional]427		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {428			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);429			let collection = <CollectionHandle<T>>::try_get(collection_id)?;430431			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)432		}433434		/// # Permissions435		///436		/// * Collection Owner437		///438		/// # Arguments439		///440		/// * collection_id.441		///442		/// * new_sponsor.443		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]444		#[transactional]445		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {446			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);447448			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;449			target_collection.check_is_owner(&sender)?;450451			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);452			target_collection.save()453		}454455		/// # Permissions456		///457		/// * Sponsor.458		///459		/// # Arguments460		///461		/// * collection_id.462		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]463		#[transactional]464		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {465			let sender = ensure_signed(origin)?;466467			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;468			ensure!(469				target_collection.sponsorship.pending_sponsor() == Some(&sender),470				Error::<T>::ConfirmUnsetSponsorFail471			);472473			target_collection.sponsorship = SponsorshipState::Confirmed(sender);474			target_collection.save()475		}476477		/// Switch back to pay-per-own-transaction model.478		///479		/// # Permissions480		///481		/// * Collection owner.482		///483		/// # Arguments484		///485		/// * collection_id.486		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]487		#[transactional]488		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {489			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);490491			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;492			target_collection.check_is_owner(&sender)?;493494			target_collection.sponsorship = SponsorshipState::Disabled;495			target_collection.save()496		}497498		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.499		///500		/// # Permissions501		///502		/// * Collection Owner.503		/// * Collection Admin.504		/// * Anyone if505		///     * Allow List is enabled, and506		///     * Address is added to allow list, and507		///     * MintPermission is enabled (see SetMintPermission method)508		///509		/// # Arguments510		///511		/// * collection_id: ID of the collection.512		///513		/// * owner: Address, initial owner of the NFT.514		///515		/// * data: Token data to store on chain.516		#[weight = <CommonWeights<T>>::create_item()]517		#[transactional]518		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {519			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);520521			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))522		}523524		/// This method creates multiple items in a collection created with CreateCollection method.525		///526		/// # Permissions527		///528		/// * Collection Owner.529		/// * Collection Admin.530		/// * Anyone if531		///     * Allow List is enabled, and532		///     * Address is added to allow list, and533		///     * MintPermission is enabled (see SetMintPermission method)534		///535		/// # Arguments536		///537		/// * collection_id: ID of the collection.538		///539		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].540		///541		/// * owner: Address, initial owner of the NFT.542		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]543		#[transactional]544		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {545			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);546			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);547548			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))549		}550551		// TODO! transaction weight552553		/// Set transfers_enabled value for particular collection554		///555		/// # Permissions556		///557		/// * Collection Owner.558		///559		/// # Arguments560		///561		/// * collection_id: ID of the collection.562		///563		/// * value: New flag value.564		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]565		#[transactional]566		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {567			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);568			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;569			target_collection.check_is_owner(&sender)?;570571			// =========572573			target_collection.limits.transfers_enabled = Some(value);574			target_collection.save()575		}576577		/// Destroys a concrete instance of NFT.578		///579		/// # Permissions580		///581		/// * Collection Owner.582		/// * Collection Admin.583		/// * Current NFT Owner.584		///585		/// # Arguments586		///587		/// * collection_id: ID of the collection.588		///589		/// * item_id: ID of NFT to burn.590		#[weight = <CommonWeights<T>>::burn_item()]591		#[transactional]592		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {593			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);594595			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))596		}597598		/// Destroys a concrete instance of NFT on behalf of the owner599		/// See also: [`approve`]600		///601		/// # Permissions602		///603		/// * Collection Owner.604		/// * Collection Admin.605		/// * Current NFT Owner.606		///607		/// # Arguments608		///609		/// * collection_id: ID of the collection.610		///611		/// * item_id: ID of NFT to burn.612		///613		/// * from: owner of item614		#[weight = <CommonWeights<T>>::burn_from()]615		#[transactional]616		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {617			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);618619			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))620		}621622		/// Change ownership of the token.623		///624		/// # Permissions625		///626		/// * Collection Owner627		/// * Collection Admin628		/// * Current NFT owner629		///630		/// # Arguments631		///632		/// * recipient: Address of token recipient.633		///634		/// * collection_id.635		///636		/// * item_id: ID of the item637		///     * Non-Fungible Mode: Required.638		///     * Fungible Mode: Ignored.639		///     * Re-Fungible Mode: Required.640		///641		/// * value: Amount to transfer.642		///     * Non-Fungible Mode: Ignored643		///     * Fungible Mode: Must specify transferred amount644		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)645		#[weight = <CommonWeights<T>>::transfer()]646		#[transactional]647		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {648			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);649650			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))651		}652653		/// Set, change, or remove approved address to transfer the ownership of the NFT.654		///655		/// # Permissions656		///657		/// * Collection Owner658		/// * Collection Admin659		/// * Current NFT owner660		///661		/// # Arguments662		///663		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).664		///665		/// * collection_id.666		///667		/// * item_id: ID of the item.668		#[weight = <CommonWeights<T>>::approve()]669		#[transactional]670		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {671			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672673			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))674		}675676		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.677		///678		/// # Permissions679		/// * Collection Owner680		/// * Collection Admin681		/// * Current NFT owner682		/// * Address approved by current NFT owner683		///684		/// # Arguments685		///686		/// * from: Address that owns token.687		///688		/// * recipient: Address of token recipient.689		///690		/// * collection_id.691		///692		/// * item_id: ID of the item.693		///694		/// * value: Amount to transfer.695		#[weight = <CommonWeights<T>>::transfer_from()]696		#[transactional]697		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {698			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699700			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))701		}702703		/// Set off-chain data schema.704		///705		/// # Permissions706		///707		/// * Collection Owner708		/// * Collection Admin709		///710		/// # Arguments711		///712		/// * collection_id.713		///714		/// * schema: String representing the offchain data schema.715		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]716		#[transactional]717		pub fn set_variable_meta_data (718			origin,719			collection_id: CollectionId,720			item_id: TokenId,721			data: Vec<u8>722		) -> DispatchResultWithPostInfo {723			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))726		}727728		/// Set meta_update_permission value for particular collection729		///730		/// # Permissions731		///732		/// * Collection Owner.733		///734		/// # Arguments735		///736		/// * collection_id: ID of the collection.737		///738		/// * value: New flag value.739		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]740		#[transactional]741		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {742			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;744745			ensure!(746				target_collection.meta_update_permission != MetaUpdatePermission::None,747				<CommonError<T>>::MetadataFlagFrozen,748			);749			target_collection.check_is_owner(&sender)?;750751			target_collection.meta_update_permission = value;752753			target_collection.save()754		}755756		/// Set schema standard757		/// ImageURL758		/// Unique759		///760		/// # Permissions761		///762		/// * Collection Owner763		/// * Collection Admin764		///765		/// # Arguments766		///767		/// * collection_id.768		///769		/// * schema: SchemaVersion: enum770		#[weight = <SelfWeightOf<T>>::set_schema_version()]771		#[transactional]772		pub fn set_schema_version(773			origin,774			collection_id: CollectionId,775			version: SchemaVersion776		) -> DispatchResult {777			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;779			target_collection.check_is_owner_or_admin(&sender)?;780			target_collection.schema_version = version;781			target_collection.save()782		}783784		/// Set off-chain data schema.785		///786		/// # Permissions787		///788		/// * Collection Owner789		/// * Collection Admin790		///791		/// # Arguments792		///793		/// * collection_id.794		///795		/// * schema: String representing the offchain data schema.796		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]797		#[transactional]798		pub fn set_offchain_schema(799			origin,800			collection_id: CollectionId,801			schema: Vec<u8>802		) -> DispatchResult {803			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);804			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;805			target_collection.check_is_owner_or_admin(&sender)?;806807			// check schema limit808			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");809810			target_collection.offchain_schema = schema;811			target_collection.save()812		}813814		/// Set const on-chain data schema.815		///816		/// # Permissions817		///818		/// * Collection Owner819		/// * Collection Admin820		///821		/// # Arguments822		///823		/// * collection_id.824		///825		/// * schema: String representing the const on-chain data schema.826		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]827		#[transactional]828		pub fn set_const_on_chain_schema (829			origin,830			collection_id: CollectionId,831			schema: Vec<u8>832		) -> DispatchResult {833			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);834			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;835			target_collection.check_is_owner_or_admin(&sender)?;836837			// check schema limit838			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");839840			target_collection.const_on_chain_schema = schema;841			target_collection.save()842		}843844		/// Set variable on-chain data schema.845		///846		/// # Permissions847		///848		/// * Collection Owner849		/// * Collection Admin850		///851		/// # Arguments852		///853		/// * collection_id.854		///855		/// * schema: String representing the variable on-chain data schema.856		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]857		#[transactional]858		pub fn set_variable_on_chain_schema (859			origin,860			collection_id: CollectionId,861			schema: Vec<u8>862		) -> DispatchResult {863			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;865			target_collection.check_is_owner_or_admin(&sender)?;866867			// check schema limit868			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");869870			target_collection.variable_on_chain_schema = schema;871			target_collection.save()872		}873874		#[weight = <SelfWeightOf<T>>::set_collection_limits()]875		#[transactional]876		pub fn set_collection_limits(877			origin,878			collection_id: CollectionId,879			new_limit: CollectionLimits,880		) -> DispatchResult {881			let mut new_limit = new_limit;882			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);883			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;884			target_collection.check_is_owner(&sender)?;885			let old_limit = &target_collection.limits;886887			macro_rules! limit_default {888				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{889					$(890						if let Some($new) = $new.$field {891							let $old = $old.$field($($arg)?);892							let _ = $new;893							let _ = $old;894							$check895						} else {896							$new.$field = $old.$field897						}898					)*899				}};900			}901902			limit_default!(old_limit, new_limit,903				account_token_ownership_limit => ensure!(904					new_limit <= MAX_TOKEN_OWNERSHIP,905					<Error<T>>::CollectionLimitBoundsExceeded,906				),907				sponsor_transfer_timeout(match target_collection.mode {908					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,909					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,910					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,911				}) => ensure!(912					new_limit <= MAX_SPONSOR_TIMEOUT,913					<Error<T>>::CollectionLimitBoundsExceeded,914				),915				sponsored_data_size => ensure!(916					new_limit <= CUSTOM_DATA_LIMIT,917					<Error<T>>::CollectionLimitBoundsExceeded,918				),919				token_limit => ensure!(920					old_limit >= new_limit && new_limit > 0,921					<CommonError<T>>::CollectionTokenLimitExceeded922				),923				owner_can_transfer => ensure!(924					old_limit || !new_limit,925					<Error<T>>::OwnerPermissionsCantBeReverted,926				),927				owner_can_destroy => ensure!(928					old_limit || !new_limit,929					<Error<T>>::OwnerPermissionsCantBeReverted,930				),931				sponsored_data_rate_limit => {},932				transfers_enabled => {},933			);934935			target_collection.limits = new_limit;936937			target_collection.save()938		}939	}940}941942// TODO: limit returned entries?943impl<T: Config> Pallet<T> {944	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {945		<IsAdmin<T>>::iter_prefix((collection,))946			.map(|(a, _)| a)947			.collect()948	}949	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {950		<Allowlist<T>>::iter_prefix((collection,))951			.map(|(a, _)| a)952			.collect()953	}954}
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24		IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32	pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,46	CommonWeightInfo,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76	/// Error for non-fungible-token module.77	pub enum Error for Module<T: Config> {78		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79		CollectionDecimalPointLimitExceeded,80		/// This address is not set as sponsor, use setCollectionSponsor first.81		ConfirmUnsetSponsorFail,82		/// Length of items properties must be greater than 0.83		EmptyArgument,84		/// Collection limit bounds per collection exceeded85		CollectionLimitBoundsExceeded,86		/// Tried to enable permissions which are only permitted to be disabled87		OwnerPermissionsCantBeReverted,88	}89}90pub trait Config:91	system::Config92	+ pallet_evm_coder_substrate::Config93	+ pallet_common::Config94	+ pallet_nonfungible::Config95	+ pallet_refungible::Config96	+ pallet_fungible::Config97	+ Sized98	+ TypeInfo99{100	/// Weight information for extrinsics in this pallet.101	type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111//                    i.e autoincrementing index112//                    can use non-cryptographic hash113// real - key is controlled by user114//        but it is hard to generate enough colliding values, i.e owner of signed txs115//        can use non-cryptographic hash116// controlled - key is completly controlled by users117//              i.e maps with mutable keys118//              should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123//      collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125//      same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127//      no confirmation required, so addresses can be easily generated128decl_storage! {129	trait Store for Module<T: Config> as Nft {130131		//#region Private members132		/// Used for migrations133		ChainVersion: u64;134		//#endregion135136		//#region Tokens transfer rate limit baskets137		/// (Collection id (controlled?2), who created (real))138		/// TODO: Off chain worker should remove from this map when collection gets removed139		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;140		/// Collection id (controlled?2), token id (controlled?2)141		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;142		/// Collection id (controlled?2), owning user (real)143		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;144		/// Collection id (controlled?2), token id (controlled?2)145		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>;146		//#endregion147148		/// Variable metadata sponsoring149		/// Collection id (controlled?2), token id (controlled?2)150		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;151		/// Approval sponsoring152		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;153		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;154		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>;155	}156}157158decl_module! {159	pub struct Module<T: Config> for enum Call160	where161		origin: T::Origin162	{163		type Error = Error<T>;164165		fn on_initialize(_now: T::BlockNumber) -> Weight {166			0167		}168169		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.170		///171		/// # Permissions172		///173		/// * Anyone.174		///175		/// # Arguments176		///177		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.178		///179		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.180		///181		/// * token_prefix: UTF-8 string with token prefix.182		///183		/// * mode: [CollectionMode] collection type and type dependent data.184		// returns collection ID185		#[weight = <SelfWeightOf<T>>::create_collection()]186		#[transactional]187		pub fn create_collection(origin,188								 collection_name: Vec<u16>,189								 collection_description: Vec<u16>,190								 token_prefix: Vec<u8>,191								 mode: CollectionMode) -> DispatchResult {192193			// Anyone can create a collection194			let who = ensure_signed(origin)?;195196			// Create new collection197			let new_collection = Collection {198				owner: who.clone(),199				name: collection_name,200				mode: mode.clone(),201				mint_mode: false,202				access: AccessMode::Normal,203				description: collection_description,204				token_prefix,205				offchain_schema: Vec::new(),206				schema_version: SchemaVersion::ImageURL,207				sponsorship: SponsorshipState::Disabled,208				variable_on_chain_schema: Vec::new(),209				const_on_chain_schema: Vec::new(),210				limits: Default::default(),211				meta_update_permission: Default::default(),212			};213214			let _id = match mode {215				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},216				CollectionMode::Fungible(decimal_points) => {217					// check params218					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);219					<PalletFungible<T>>::init_collection(new_collection)?220				}221				CollectionMode::ReFungible => {222					<PalletRefungible<T>>::init_collection(new_collection)?223				}224			};225226			Ok(())227		}228229		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.230		///231		/// # Permissions232		///233		/// * Collection Owner.234		///235		/// # Arguments236		///237		/// * collection_id: collection to destroy.238		#[weight = <SelfWeightOf<T>>::destroy_collection()]239		#[transactional]240		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {241			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);242243			let collection = <CollectionHandle<T>>::try_get(collection_id)?;244			collection.check_is_owner(&sender)?;245246			// =========247248			match collection.mode {249				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,250				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,251				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,252			}253254			<NftTransferBasket<T>>::remove_prefix(collection_id, None);255			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);256			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);257258			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);259			<NftApproveBasket<T>>::remove_prefix(collection_id, None);260			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);261			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);262263			Ok(())264		}265266		/// Add an address to allow list.267		///268		/// # Permissions269		///270		/// * Collection Owner271		/// * Collection Admin272		///273		/// # Arguments274		///275		/// * collection_id.276		///277		/// * address.278		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]279		#[transactional]280		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{281282			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);283			let collection = <CollectionHandle<T>>::try_get(collection_id)?;284285			<PalletCommon<T>>::toggle_allowlist(286				&collection,287				&sender,288				&address,289				true,290			)?;291292			Ok(())293		}294295		/// Remove an address from allow list.296		///297		/// # Permissions298		///299		/// * Collection Owner300		/// * Collection Admin301		///302		/// # Arguments303		///304		/// * collection_id.305		///306		/// * address.307		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]308		#[transactional]309		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{310311			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);312			let collection = <CollectionHandle<T>>::try_get(collection_id)?;313314			<PalletCommon<T>>::toggle_allowlist(315				&collection,316				&sender,317				&address,318				false,319			)?;320321			Ok(())322		}323324		/// Toggle between normal and allow list access for the methods with access for `Anyone`.325		///326		/// # Permissions327		///328		/// * Collection Owner.329		///330		/// # Arguments331		///332		/// * collection_id.333		///334		/// * mode: [AccessMode]335		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]336		#[transactional]337		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult338		{339			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340341			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;342			target_collection.check_is_owner(&sender)?;343344			target_collection.access = mode;345			target_collection.save()346		}347348		/// Allows Anyone to create tokens if:349		/// * Allow List is enabled, and350		/// * Address is added to allow list, and351		/// * This method was called with True parameter352		///353		/// # Permissions354		/// * Collection Owner355		///356		/// # Arguments357		///358		/// * collection_id.359		///360		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.361		#[weight = <SelfWeightOf<T>>::set_mint_permission()]362		#[transactional]363		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult364		{365			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);366367			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;368			target_collection.check_is_owner(&sender)?;369370			target_collection.mint_mode = mint_permission;371			target_collection.save()372		}373374		/// Change the owner of the collection.375		///376		/// # Permissions377		///378		/// * Collection Owner.379		///380		/// # Arguments381		///382		/// * collection_id.383		///384		/// * new_owner.385		#[weight = <SelfWeightOf<T>>::change_collection_owner()]386		#[transactional]387		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {388389			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);390391			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;392			target_collection.check_is_owner(&sender)?;393394			target_collection.owner = new_owner;395			target_collection.save()396		}397398		/// Adds an admin of the Collection.399		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.400		///401		/// # Permissions402		///403		/// * Collection Owner.404		/// * Collection Admin.405		///406		/// # Arguments407		///408		/// * collection_id: ID of the Collection to add admin for.409		///410		/// * new_admin_id: Address of new admin to add.411		#[weight = <SelfWeightOf<T>>::add_collection_admin()]412		#[transactional]413		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {414			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);415			let collection = <CollectionHandle<T>>::try_get(collection_id)?;416417			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)418		}419420		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.421		///422		/// # Permissions423		///424		/// * Collection Owner.425		/// * Collection Admin.426		///427		/// # Arguments428		///429		/// * collection_id: ID of the Collection to remove admin for.430		///431		/// * account_id: Address of admin to remove.432		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]433		#[transactional]434		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {435			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436			let collection = <CollectionHandle<T>>::try_get(collection_id)?;437438			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)439		}440441		/// # Permissions442		///443		/// * Collection Owner444		///445		/// # Arguments446		///447		/// * collection_id.448		///449		/// * new_sponsor.450		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]451		#[transactional]452		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {453			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454455			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;456			target_collection.check_is_owner(&sender)?;457458			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);459			target_collection.save()460		}461462		/// # Permissions463		///464		/// * Sponsor.465		///466		/// # Arguments467		///468		/// * collection_id.469		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]470		#[transactional]471		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {472			let sender = ensure_signed(origin)?;473474			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475			ensure!(476				target_collection.sponsorship.pending_sponsor() == Some(&sender),477				Error::<T>::ConfirmUnsetSponsorFail478			);479480			target_collection.sponsorship = SponsorshipState::Confirmed(sender);481			target_collection.save()482		}483484		/// Switch back to pay-per-own-transaction model.485		///486		/// # Permissions487		///488		/// * Collection owner.489		///490		/// # Arguments491		///492		/// * collection_id.493		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]494		#[transactional]495		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {496			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499			target_collection.check_is_owner(&sender)?;500501			target_collection.sponsorship = SponsorshipState::Disabled;502			target_collection.save()503		}504505		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.506		///507		/// # Permissions508		///509		/// * Collection Owner.510		/// * Collection Admin.511		/// * Anyone if512		///     * Allow List is enabled, and513		///     * Address is added to allow list, and514		///     * MintPermission is enabled (see SetMintPermission method)515		///516		/// # Arguments517		///518		/// * collection_id: ID of the collection.519		///520		/// * owner: Address, initial owner of the NFT.521		///522		/// * data: Token data to store on chain.523		#[weight = <CommonWeights<T>>::create_item()]524		#[transactional]525		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {526			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))529		}530531		/// This method creates multiple items in a collection created with CreateCollection method.532		///533		/// # Permissions534		///535		/// * Collection Owner.536		/// * Collection Admin.537		/// * Anyone if538		///     * Allow List is enabled, and539		///     * Address is added to allow list, and540		///     * MintPermission is enabled (see SetMintPermission method)541		///542		/// # Arguments543		///544		/// * collection_id: ID of the collection.545		///546		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].547		///548		/// * owner: Address, initial owner of the NFT.549		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]550		#[transactional]551		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {552			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);553			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))556		}557558		// TODO! transaction weight559560		/// Set transfers_enabled value for particular collection561		///562		/// # Permissions563		///564		/// * Collection Owner.565		///566		/// # Arguments567		///568		/// * collection_id: ID of the collection.569		///570		/// * value: New flag value.571		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]572		#[transactional]573		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576			target_collection.check_is_owner(&sender)?;577578			// =========579580			target_collection.limits.transfers_enabled = Some(value);581			target_collection.save()582		}583584		/// Destroys a concrete instance of NFT.585		///586		/// # Permissions587		///588		/// * Collection Owner.589		/// * Collection Admin.590		/// * Current NFT Owner.591		///592		/// # Arguments593		///594		/// * collection_id: ID of the collection.595		///596		/// * item_id: ID of NFT to burn.597		#[weight = <CommonWeights<T>>::burn_item()]598		#[transactional]599		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {600			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601602			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;603			if value == 1 {604				<NftTransferBasket<T>>::remove(collection_id, item_id);605				<NftApproveBasket<T>>::remove(collection_id, item_id);606			}607			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?608			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());609			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));610			Ok(post_info)611		}612613		/// Destroys a concrete instance of NFT on behalf of the owner614		/// See also: [`approve`]615		///616		/// # Permissions617		///618		/// * Collection Owner.619		/// * Collection Admin.620		/// * Current NFT Owner.621		///622		/// # Arguments623		///624		/// * collection_id: ID of the collection.625		///626		/// * item_id: ID of NFT to burn.627		///628		/// * from: owner of item629		#[weight = <CommonWeights<T>>::burn_from()]630		#[transactional]631		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {632			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633634			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))635		}636637		/// Change ownership of the token.638		///639		/// # Permissions640		///641		/// * Collection Owner642		/// * Collection Admin643		/// * Current NFT owner644		///645		/// # Arguments646		///647		/// * recipient: Address of token recipient.648		///649		/// * collection_id.650		///651		/// * item_id: ID of the item652		///     * Non-Fungible Mode: Required.653		///     * Fungible Mode: Ignored.654		///     * Re-Fungible Mode: Required.655		///656		/// * value: Amount to transfer.657		///     * Non-Fungible Mode: Ignored658		///     * Fungible Mode: Must specify transferred amount659		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)660		#[weight = <CommonWeights<T>>::transfer()]661		#[transactional]662		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {663			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664665			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))666		}667668		/// Set, change, or remove approved address to transfer the ownership of the NFT.669		///670		/// # Permissions671		///672		/// * Collection Owner673		/// * Collection Admin674		/// * Current NFT owner675		///676		/// # Arguments677		///678		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).679		///680		/// * collection_id.681		///682		/// * item_id: ID of the item.683		#[weight = <CommonWeights<T>>::approve()]684		#[transactional]685		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {686			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);687688			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))689		}690691		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.692		///693		/// # Permissions694		/// * Collection Owner695		/// * Collection Admin696		/// * Current NFT owner697		/// * Address approved by current NFT owner698		///699		/// # Arguments700		///701		/// * from: Address that owns token.702		///703		/// * recipient: Address of token recipient.704		///705		/// * collection_id.706		///707		/// * item_id: ID of the item.708		///709		/// * value: Amount to transfer.710		#[weight = <CommonWeights<T>>::transfer_from()]711		#[transactional]712		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {713			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);714715			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))716		}717718		/// Set off-chain data schema.719		///720		/// # Permissions721		///722		/// * Collection Owner723		/// * Collection Admin724		///725		/// # Arguments726		///727		/// * collection_id.728		///729		/// * schema: String representing the offchain data schema.730		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]731		#[transactional]732		pub fn set_variable_meta_data (733			origin,734			collection_id: CollectionId,735			item_id: TokenId,736			data: Vec<u8>737		) -> DispatchResultWithPostInfo {738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))741		}742743		/// Set meta_update_permission value for particular collection744		///745		/// # Permissions746		///747		/// * Collection Owner.748		///749		/// # Arguments750		///751		/// * collection_id: ID of the collection.752		///753		/// * value: New flag value.754		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]755		#[transactional]756		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {757			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);758			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;759760			ensure!(761				target_collection.meta_update_permission != MetaUpdatePermission::None,762				<CommonError<T>>::MetadataFlagFrozen,763			);764			target_collection.check_is_owner(&sender)?;765766			target_collection.meta_update_permission = value;767768			target_collection.save()769		}770771		/// Set schema standard772		/// ImageURL773		/// Unique774		///775		/// # Permissions776		///777		/// * Collection Owner778		/// * Collection Admin779		///780		/// # Arguments781		///782		/// * collection_id.783		///784		/// * schema: SchemaVersion: enum785		#[weight = <SelfWeightOf<T>>::set_schema_version()]786		#[transactional]787		pub fn set_schema_version(788			origin,789			collection_id: CollectionId,790			version: SchemaVersion791		) -> DispatchResult {792			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;794			target_collection.check_is_owner_or_admin(&sender)?;795			target_collection.schema_version = version;796			target_collection.save()797		}798799		/// Set off-chain data schema.800		///801		/// # Permissions802		///803		/// * Collection Owner804		/// * Collection Admin805		///806		/// # Arguments807		///808		/// * collection_id.809		///810		/// * schema: String representing the offchain data schema.811		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]812		#[transactional]813		pub fn set_offchain_schema(814			origin,815			collection_id: CollectionId,816			schema: Vec<u8>817		) -> DispatchResult {818			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;820			target_collection.check_is_owner_or_admin(&sender)?;821822			// check schema limit823			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");824825			target_collection.offchain_schema = schema;826			target_collection.save()827		}828829		/// Set const on-chain data schema.830		///831		/// # Permissions832		///833		/// * Collection Owner834		/// * Collection Admin835		///836		/// # Arguments837		///838		/// * collection_id.839		///840		/// * schema: String representing the const on-chain data schema.841		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]842		#[transactional]843		pub fn set_const_on_chain_schema (844			origin,845			collection_id: CollectionId,846			schema: Vec<u8>847		) -> DispatchResult {848			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);849			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;850			target_collection.check_is_owner_or_admin(&sender)?;851852			// check schema limit853			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");854855			target_collection.const_on_chain_schema = schema;856			target_collection.save()857		}858859		/// Set variable on-chain data schema.860		///861		/// # Permissions862		///863		/// * Collection Owner864		/// * Collection Admin865		///866		/// # Arguments867		///868		/// * collection_id.869		///870		/// * schema: String representing the variable on-chain data schema.871		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]872		#[transactional]873		pub fn set_variable_on_chain_schema (874			origin,875			collection_id: CollectionId,876			schema: Vec<u8>877		) -> DispatchResult {878			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;880			target_collection.check_is_owner_or_admin(&sender)?;881882			// check schema limit883			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");884885			target_collection.variable_on_chain_schema = schema;886			target_collection.save()887		}888889		#[weight = <SelfWeightOf<T>>::set_collection_limits()]890		#[transactional]891		pub fn set_collection_limits(892			origin,893			collection_id: CollectionId,894			new_limit: CollectionLimits,895		) -> DispatchResult {896			let mut new_limit = new_limit;897			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;899			target_collection.check_is_owner(&sender)?;900			let old_limit = &target_collection.limits;901902			macro_rules! limit_default {903				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{904					$(905						if let Some($new) = $new.$field {906							let $old = $old.$field($($arg)?);907							let _ = $new;908							let _ = $old;909							$check910						} else {911							$new.$field = $old.$field912						}913					)*914				}};915			}916917			limit_default!(old_limit, new_limit,918				account_token_ownership_limit => ensure!(919					new_limit <= MAX_TOKEN_OWNERSHIP,920					<Error<T>>::CollectionLimitBoundsExceeded,921				),922				sponsor_transfer_timeout(match target_collection.mode {923					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,924					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,925					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,926				}) => ensure!(927					new_limit <= MAX_SPONSOR_TIMEOUT,928					<Error<T>>::CollectionLimitBoundsExceeded,929				),930				sponsored_data_size => ensure!(931					new_limit <= CUSTOM_DATA_LIMIT,932					<Error<T>>::CollectionLimitBoundsExceeded,933				),934				token_limit => ensure!(935					old_limit >= new_limit && new_limit > 0,936					<CommonError<T>>::CollectionTokenLimitExceeded937				),938				owner_can_transfer => ensure!(939					old_limit || !new_limit,940					<Error<T>>::OwnerPermissionsCantBeReverted,941				),942				owner_can_destroy => ensure!(943					old_limit || !new_limit,944					<Error<T>>::OwnerPermissionsCantBeReverted,945				),946				sponsored_data_rate_limit => {},947				transfers_enabled => {},948			);949950			target_collection.limits = new_limit;951952			target_collection.save()953		}954	}955}
modifiedpallets/nft/src/sponsorship.rsdiffbeforeafterboth
--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -1,175 +1,222 @@
 use crate::{
 	Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket,
-	FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode,
+	FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket,
+	FungibleApproveBasket, RefungibleApproveBasket,
 };
 use core::marker::PhantomData;
 use up_sponsorship::SponsorshipHandler;
 use frame_support::{
 	traits::{IsSubType},
-	storage::{StorageMap, StorageDoubleMap},
+	storage::{StorageMap, StorageDoubleMap, StorageNMap},
 };
 use nft_data_structs::{
-	TokenId, CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,
 };
-use pallet_common::{CollectionById};
-
-pub struct NftSponsorshipHandler<T>(PhantomData<T>);
-impl<T: Config> NftSponsorshipHandler<T> {
-	pub fn withdraw_create_item(
-		who: &T::AccountId,
-		collection_id: &CollectionId,
-		_properties: &CreateItemData,
-	) -> Option<T::AccountId> {
-		let collection = CollectionById::<T>::get(collection_id)?;
-
-		// sponsor timeout
-		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+use pallet_common::{CollectionHandle};
+use pallet_common::account::CrossAccountId;
 
-		let limit = collection
-			.limits
-			.sponsor_transfer_timeout(match _properties {
-				CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
-				CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-				CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-			});
-		if CreateItemBasket::<T>::contains_key((collection_id, &who)) {
-			let last_tx_block = CreateItemBasket::<T>::get((collection_id, &who));
-			let limit_time = last_tx_block + limit.into();
-			if block_number <= limit_time {
+pub fn withdraw_transfer<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::CrossAccountId,
+	item_id: &TokenId,
+) -> Option<()> {
+	// preliminary sponsoring correctness check
+	match collection.mode {
+		CollectionMode::NFT => {
+			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
+			if !owner.conv_eq(who) {
 				return None;
 			}
 		}
-		CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
+		CollectionMode::Fungible(_) => {
+			if item_id != &TokenId::default() {
+				return None;
+			}
+			if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
+				return None;
+			}
+		}
+		CollectionMode::ReFungible => {
+			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+				return None;
+			}
+		}
+	}
+
+	// sponsor timeout
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection
+		.limits
+		.sponsor_transfer_timeout(match collection.mode {
+			CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+			CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+		});
+
+	let last_tx_block = match collection.mode {
+		CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),
+		CollectionMode::Fungible(_) => {
+			<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+		}
+		CollectionMode::ReFungible => {
+			<ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))
+		}
+	};
 
-		// check free create limit
-		if collection.limits.sponsored_data_size() >= (_properties.data_size() as u32) {
-			collection.sponsorship.sponsor().cloned()
-		} else {
-			None
+	if let Some(last_tx_block) = last_tx_block {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
 		}
 	}
 
-	pub fn withdraw_transfer(
-		who: &T::AccountId,
-		collection_id: &CollectionId,
-		item_id: &TokenId,
-	) -> Option<T::AccountId> {
-		let collection = CollectionById::<T>::get(collection_id)?;
+	match collection.mode {
+		CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
+		CollectionMode::Fungible(_) => {
+			<FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
+		}
+		CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
+			(collection.id, item_id, who.as_sub()),
+			block_number,
+		),
+	};
 
-		let mut sponsor_transfer = false;
-		if collection.sponsorship.confirmed() {
-			let collection_limits = collection.limits.clone();
-			let collection_mode = collection.mode.clone();
+	Some(())
+}
 
-			// sponsor timeout
-			let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-			sponsor_transfer = match collection_mode {
-				CollectionMode::NFT => {
-					// get correct limit
-					let limit =
-						collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
+pub fn withdraw_create_item<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::AccountId,
+	_properties: &CreateItemData,
+) -> Option<()> {
+	if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {
+		return None;
+	}
 
-					let mut sponsored = true;
-					if NftTransferBasket::<T>::contains_key(collection_id, item_id) {
-						let last_tx_block = NftTransferBasket::<T>::get(collection_id, item_id);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
-					}
-					if sponsored {
-						NftTransferBasket::<T>::insert(collection_id, item_id, block_number);
-					}
+	// sponsor timeout
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection
+		.limits
+		.sponsor_transfer_timeout(match _properties {
+			CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
+			CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+		});
+
+	if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, &who)) {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
+		}
+	}
 
-					sponsored
-				}
-				CollectionMode::Fungible(_) => {
-					// get correct limit
-					let limit = collection_limits
-						.sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
+	CreateItemBasket::<T>::insert((collection.id, who.clone()), block_number);
 
-					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-					let mut sponsored = true;
-					if FungibleTransferBasket::<T>::contains_key(collection_id, who) {
-						let last_tx_block = FungibleTransferBasket::<T>::get(collection_id, who);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
-					}
-					if sponsored {
-						FungibleTransferBasket::<T>::insert(collection_id, who, block_number);
-					}
+	Some(())
+}
 
-					sponsored
-				}
-				CollectionMode::ReFungible => {
-					// get correct limit
-					let limit = collection_limits
-						.sponsor_transfer_timeout(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
+pub fn withdraw_set_variable_meta_data<T: Config>(
+	who: &T::CrossAccountId,
+	collection: &CollectionHandle<T>,
+	item_id: &TokenId,
+	data: &[u8],
+) -> Option<()> {
+	// TODO: make it work for admins
+	if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {
+		return None;
+	}
+	// preliminary sponsoring correctness check
+	match collection.mode {
+		CollectionMode::NFT => {
+			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
+			if !owner.conv_eq(who) {
+				return None;
+			}
+		}
+		CollectionMode::Fungible(_) => {
+			if item_id != &TokenId::default() {
+				return None;
+			}
+			if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
+				return None;
+			}
+		}
+		CollectionMode::ReFungible => {
+			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+				return None;
+			}
+		}
+	}
 
-					let mut sponsored = true;
-					if ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
-						let last_tx_block =
-							ReFungibleTransferBasket::<T>::get(collection_id, item_id);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
-					}
-					if sponsored {
-						ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);
-					}
+	// Can't sponsor fungible collection, this tx will be rejected
+	// as invalid
+	if matches!(collection.mode, CollectionMode::Fungible(_)) {
+		return None;
+	}
+	if data.len() > collection.limits.sponsored_data_size() as usize {
+		return None;
+	}
 
-					sponsored
-				}
-			};
-		}
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection.limits.sponsored_data_rate_limit()?;
 
-		if !sponsor_transfer {
-			None
-		} else {
-			collection.sponsorship.sponsor().cloned()
+	if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
 		}
 	}
 
-	pub fn withdraw_set_variable_meta_data(
-		collection_id: &CollectionId,
-		item_id: &TokenId,
-		data: &[u8],
-	) -> Option<T::AccountId> {
-		let mut sponsor_metadata_changes = false;
+	<VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);
 
-		let collection = CollectionById::<T>::get(collection_id)?;
+	Some(())
+}
 
-		if collection.sponsorship.confirmed() &&
-			// Can't sponsor fungible collection, this tx will be rejected
-			// as invalid
-			!matches!(collection.mode, CollectionMode::Fungible(_)) &&
-			data.len() <= collection.limits.sponsored_data_size() as usize
-		{
-			if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit() {
-				let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+pub fn withdraw_approve<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::AccountId,
+	item_id: &TokenId,
+) -> Option<()> {
+	// sponsor timeout
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection.limits.sponsor_approve_timeout();
 
-				if VariableMetaDataBasket::<T>::get(collection_id, item_id)
-					.map(|last_block| block_number - last_block > rate_limit.into())
-					.unwrap_or(true)
-				{
-					sponsor_metadata_changes = true;
-					VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);
-				}
-			}
+	let last_tx_block = match collection.mode {
+		CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
+		CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
+		CollectionMode::ReFungible => {
+			<RefungibleApproveBasket<T>>::get((collection.id, item_id, who))
 		}
+	};
 
-		if !sponsor_metadata_changes {
-			None
-		} else {
-			collection.sponsorship.sponsor().cloned()
+	if let Some(last_tx_block) = last_tx_block {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
 		}
 	}
+
+	match collection.mode {
+		CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
+		CollectionMode::Fungible(_) => {
+			<FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
+		}
+		CollectionMode::ReFungible => {
+			<RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
+		}
+	};
+
+	Some(())
+}
+
+fn load<T: Config>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
+	let collection = CollectionHandle::new(id)?;
+	let sponsor = collection.sponsorship.sponsor().cloned()?;
+	Some((sponsor, collection))
 }
 
+pub struct NftSponsorshipHandler<T>(PhantomData<T>);
 impl<T, C> SponsorshipHandler<T::AccountId, C> for NftSponsorshipHandler<T>
 where
 	T: Config,
@@ -181,17 +228,54 @@
 				collection_id,
 				data,
 				..
-			} => Self::withdraw_create_item(who, collection_id, data),
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_create_item::<T>(&collection, who, data).map(|()| sponsor)
+			}
 			Call::transfer {
 				collection_id,
 				item_id,
 				..
-			} => Self::withdraw_transfer(who, collection_id, item_id),
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_transfer::<T>(
+					&collection,
+					&T::CrossAccountId::from_sub(who.clone()),
+					item_id,
+				)
+				.map(|()| sponsor)
+			}
+			Call::transfer_from {
+				collection_id,
+				item_id,
+				from,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)
+			}
+			Call::approve {
+				collection_id,
+				item_id,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
+			}
 			Call::set_variable_meta_data {
 				collection_id,
 				item_id,
 				data,
-			} => Self::withdraw_set_variable_meta_data(collection_id, item_id, data),
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_set_variable_meta_data::<T>(
+					&T::CrossAccountId::from_sub(who.clone()),
+					&collection,
+					item_id,
+					data,
+				)
+				.map(|()| sponsor)
+			}
 			_ => None,
 		}
 	}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -58,18 +58,18 @@
 	}
 
 	#[pallet::pallet]
-	#[pallet::generate_store(pub(super) trait Store)]
+	#[pallet::generate_store(pub trait Store)]
 	pub struct Pallet<T>(_);
 
 	#[pallet::storage]
-	pub(super) type TokensMinted<T: Config> =
+	pub type TokensMinted<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
 	#[pallet::storage]
-	pub(super) type TokensBurnt<T: Config> =
+	pub type TokensBurnt<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
 
 	#[pallet::storage]
-	pub(super) type TokenData<T: Config> = StorageNMap<
+	pub type TokenData<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
 		Value = ItemData<T>,
 		QueryKind = OptionQuery,
@@ -77,7 +77,7 @@
 
 	/// Used to enumerate tokens owned by account
 	#[pallet::storage]
-	pub(super) type Owned<T: Config> = StorageNMap<
+	pub type Owned<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			Key<Blake2_128Concat, T::CrossAccountId>,
@@ -88,7 +88,7 @@
 	>;
 
 	#[pallet::storage]
-	pub(super) type AccountBalance<T: Config> = StorageNMap<
+	pub type AccountBalance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			Key<Blake2_128Concat, T::CrossAccountId>,
@@ -98,7 +98,7 @@
 	>;
 
 	#[pallet::storage]
-	pub(super) type Allowance<T: Config> = StorageNMap<
+	pub type Allowance<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
 		Value = T::CrossAccountId,
 		QueryKind = OptionQuery,
@@ -133,8 +133,8 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {
-		PalletCommon::init_collection(data)
+	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(data)
 	}
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -54,25 +54,25 @@
 	}
 
 	#[pallet::pallet]
-	#[pallet::generate_store(pub(super) trait Store)]
+	#[pallet::generate_store(pub trait Store)]
 	pub struct Pallet<T>(_);
 
 	#[pallet::storage]
-	pub(super) type TokensMinted<T: Config> =
+	pub type TokensMinted<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
 	#[pallet::storage]
-	pub(super) type TokensBurnt<T: Config> =
+	pub type TokensBurnt<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
 
 	#[pallet::storage]
-	pub(super) type TokenData<T: Config> = StorageNMap<
+	pub type TokenData<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
 		Value = ItemData,
 		QueryKind = ValueQuery,
 	>;
 
 	#[pallet::storage]
-	pub(super) type TotalSupply<T: Config> = StorageNMap<
+	pub type TotalSupply<T: Config> = StorageNMap<
 		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
 		Value = u128,
 		QueryKind = ValueQuery,
@@ -80,7 +80,7 @@
 
 	/// Used to enumerate tokens owned by account
 	#[pallet::storage]
-	pub(super) type Owned<T: Config> = StorageNMap<
+	pub type Owned<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			Key<Blake2_128Concat, T::CrossAccountId>,
@@ -91,7 +91,7 @@
 	>;
 
 	#[pallet::storage]
-	pub(super) type AccountBalance<T: Config> = StorageNMap<
+	pub type AccountBalance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			// Owner
@@ -102,7 +102,7 @@
 	>;
 
 	#[pallet::storage]
-	pub(super) type Balance<T: Config> = StorageNMap<
+	pub type Balance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			Key<Twox64Concat, TokenId>,
@@ -114,7 +114,7 @@
 	>;
 
 	#[pallet::storage]
-	pub(super) type Allowance<T: Config> = StorageNMap<
+	pub type Allowance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
 			Key<Twox64Concat, TokenId>,
@@ -156,8 +156,8 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {
-		PalletCommon::init_collection(data)
+	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(data)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
addedprimitives/evm-mapping/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/primitives/evm-mapping/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "up-evm-mapping"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
+frame-support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.12' }
+
+[features]
+default = ["std"]
+std = [
+	"sp-core/std",
+	"frame-support/std",
+]
addedprimitives/evm-mapping/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/evm-mapping/src/lib.rs
@@ -0,0 +1,23 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use frame_support::sp_runtime::AccountId32;
+use sp_core::H160;
+
+/// Transforms substrate addresses to ethereum (Reverse of `EvmAddressMapping`)
+/// pallet_evm doesn't have this, as it only checks if eth address
+/// is owned by substrate via `EnsureAddressOrigin` trait
+///
+/// This trait implementations shouldn't conflict with used `EnsureAddressOrigin`
+pub trait EvmBackwardsAddressMapping<AccountId> {
+	fn from_account_id(account_id: AccountId) -> H160;
+}
+
+/// Should have same mapping as EnsureAddressTruncated
+pub struct MapBackwardsAddressTruncated;
+impl EvmBackwardsAddressMapping<AccountId32> for MapBackwardsAddressTruncated {
+	fn from_account_id(account_id: AccountId32) -> H160 {
+		let mut out = [0; 20];
+		out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]);
+		H160(out)
+	}
+}
modifiedprimitives/nft/src/lib.rsdiffbeforeafterboth
--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -54,6 +54,8 @@
 pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;
 pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;
 
+pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;
+
 // Schema limits
 pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;
 pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;
@@ -207,8 +209,8 @@
 
 #[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub struct Collection<T: frame_system::Config> {
-	pub owner: T::AccountId,
+pub struct Collection<AccountId> {
+	pub owner: AccountId,
 	pub mode: CollectionMode,
 	pub access: AccessMode,
 	pub name: Vec<u16>,        // 64 include null escape char
@@ -217,7 +219,7 @@
 	pub mint_mode: bool,
 	pub offchain_schema: Vec<u8>,
 	pub schema_version: SchemaVersion,
-	pub sponsorship: SponsorshipState<T::AccountId>,
+	pub sponsorship: SponsorshipState<AccountId>,
 	pub limits: CollectionLimits,          // Collection private restrictions
 	pub variable_on_chain_schema: Vec<u8>, //
 	pub const_on_chain_schema: Vec<u8>,    //
@@ -246,6 +248,7 @@
 	pub variable_data: Vec<u8>,
 }
 
+/// All fields are wrapped in `Option`s, where None means chain default
 #[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct CollectionLimits {
@@ -254,11 +257,12 @@
 	/// None - setVariableMetadata is not sponsored
 	/// Some(v) - setVariableMetadata is sponsored
 	///           if there is v block between txs
-	pub sponsored_data_rate_limit: Option<u32>,
+	pub sponsored_data_rate_limit: Option<(Option<u32>,)>,
 	pub token_limit: Option<u32>,
 
 	// Timeouts for item types in passed blocks
 	pub sponsor_transfer_timeout: Option<u32>,
+	pub sponsor_approve_timeout: Option<u32>,
 	pub owner_can_transfer: Option<bool>,
 	pub owner_can_destroy: Option<bool>,
 	pub transfers_enabled: Option<bool>,
@@ -285,6 +289,11 @@
 			.unwrap_or(default)
 			.min(MAX_SPONSOR_TIMEOUT)
 	}
+	pub fn sponsor_approve_timeout(&self) -> u32 {
+		self.sponsor_approve_timeout
+			.unwrap_or(SPONSOR_APPROVE_TIMEOUT)
+			.min(MAX_SPONSOR_TIMEOUT)
+	}
 	pub fn owner_can_transfer(&self) -> bool {
 		self.owner_can_transfer.unwrap_or(true)
 	}
@@ -296,6 +305,8 @@
 	}
 	pub fn sponsored_data_rate_limit(&self) -> Option<u32> {
 		self.sponsored_data_rate_limit
+			.unwrap_or((None,))
+			.0
 			.map(|v| v.min(MAX_SPONSOR_TIMEOUT))
 	}
 }
@@ -413,3 +424,11 @@
 		CreateItemData::Fungible(item)
 	}
 }
+
+#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+pub struct CollectionStats {
+	pub created: u32,
+	pub destroyed: u32,
+	pub alive: u32,
+}
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -1,6 +1,6 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use nft_data_structs::{CollectionId, TokenId};
+use nft_data_structs::{CollectionId, TokenId, Collection, CollectionStats};
 use sp_std::vec::Vec;
 use sp_core::H160;
 use codec::Decode;
@@ -32,6 +32,9 @@
 
 		fn adminlist(collection: CollectionId) -> Vec<CrossAccountId>;
 		fn allowlist(collection: CollectionId) -> Vec<CrossAccountId>;
+		fn allowed(collection: CollectionId, user: CrossAccountId) -> bool;
 		fn last_token_id(collection: CollectionId) -> TokenId;
+		fn collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>>;
+		fn collection_stats() -> CollectionStats;
 	}
 }
modifiedruntime/Cargo.tomldiffbeforeafterboth
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -69,6 +69,7 @@
     'pallet-ethereum/std',
     'fp-rpc/std',
     'up-rpc/std',
+    'up-evm-mapping/std',
     'fp-self-contained/std',
     'parachain-info/std',
     'serde',
@@ -80,7 +81,6 @@
     'pallet-nft/std',
     'pallet-unq-scheduler/std',
     'pallet-nft-charge-transaction/std',
-    'pallet-nft-transaction-payment/std',
     'nft-data-structs/std',
     'sp-api/std',
     'sp-block-builder/std',
@@ -362,7 +362,7 @@
 default-features = false
 
 [dependencies.orml-vesting]
-git = "https://github.com/open-web3-stack/open-runtime-module-library"
+git = 'https://github.com/UniqueNetwork/open-runtime-module-library'
 version = "0.4.1-dev" 
 default-features = false
 
@@ -376,6 +376,7 @@
 derivative = "2.2.0"
 pallet-nft = { path = '../pallets/nft', default-features = false, version = '3.0.0' }
 up-rpc = { path = "../primitives/rpc", default-features = false }
+up-evm-mapping = { path = "../primitives/evm-mapping", default-features = false }
 pallet-inflation = { path = '../pallets/inflation', default-features = false, version = '3.0.0' }
 nft-data-structs = { path = '../primitives/nft', default-features = false, version = '0.9.0' }
 pallet-common = { default-features = false, path = "../pallets/common" }
@@ -384,8 +385,7 @@
 pallet-nonfungible = { default-features = false, path = "../pallets/nonfungible" }
 pallet-unq-scheduler = { path = '../pallets/scheduler', default-features = false, version = '3.0.0' }
 # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
-pallet-nft-transaction-payment = { path = '../pallets/nft-transaction-payment', default-features = false, version = '3.0.0' }
-pallet-nft-charge-transaction = {git = "https://github.com/UniqueNetwork/pallet-sponsoring", package = "pallet-template-transaction-payment", default-features = false, version = '3.0.0' }
+pallet-nft-charge-transaction = { git = "https://github.com/UniqueNetwork/pallet-sponsoring", package = "pallet-template-transaction-payment", default-features = false, version = '3.0.0' }
 pallet-evm-migration = { path = '../pallets/evm-migration', default-features = false }
 pallet-evm-contract-helpers = { path = '../pallets/evm-contract-helpers', default-features = false }
 pallet-evm-transaction-payment = { path = '../pallets/evm-transaction-payment', default-features = false }
modifiedruntime/src/lib.rsdiffbeforeafterboth
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -736,7 +736,7 @@
 
 impl pallet_common::Config for Runtime {
 	type Event = Event;
-	type EvmBackwardsAddressMapping = pallet_common::account::MapBackwardsAddressTruncated;
+	type EvmBackwardsAddressMapping = up_evm_mapping::MapBackwardsAddressTruncated;
 	type EvmAddressMapping = HashedAddressMapping<Self::Hashing>;
 	type CrossAccountId = pallet_common::account::BasicCrossAccountId<Self>;
 
@@ -771,54 +771,43 @@
 	type InflationBlockInterval = InflationBlockInterval;
 }
 
-parameter_types! {
-	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
-		RuntimeBlockWeights::get().max_block;
-	pub const MaxScheduledPerBlock: u32 = 50;
-}
+// parameter_types! {
+// 	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
+// 		RuntimeBlockWeights::get().max_block;
+// 	pub const MaxScheduledPerBlock: u32 = 50;
+// }
 
-pub struct Sponsoring;
-impl SponsoringResolve<AccountId, Call> for Sponsoring {
-	fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>
-	where
-		Call: Dispatchable<Info = DispatchInfo>,
-		AccountId: AsRef<[u8]>,
-	{
-		pallet_nft_transaction_payment::Module::<Runtime>::withdraw_type(who, call)
-	}
-}
-
+type EvmSponsorshipHandler = (
+	pallet_nft::NftEthSponsorshipHandler<Runtime>,
+	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
+);
 type SponsorshipHandler = (
 	pallet_nft::NftSponsorshipHandler<Runtime>,
 	//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
+	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
 );
 
-impl pallet_unq_scheduler::Config for Runtime {
-	type Event = Event;
-	type Origin = Origin;
-	type PalletsOrigin = OriginCaller;
-	type Call = Call;
-	type MaximumWeight = MaximumSchedulerWeight;
-	type ScheduleOrigin = EnsureSigned<AccountId>;
-	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-	type SponsorshipHandler = SponsorshipHandler;
-	type WeightInfo = ();
-}
+// impl pallet_unq_scheduler::Config for Runtime {
+// 	type Event = Event;
+// 	type Origin = Origin;
+// 	type PalletsOrigin = OriginCaller;
+// 	type Call = Call;
+// 	type MaximumWeight = MaximumSchedulerWeight;
+// 	type ScheduleOrigin = EnsureSigned<AccountId>;
+// 	type MaxScheduledPerBlock = MaxScheduledPerBlock;
+// 	type SponsorshipHandler = SponsorshipHandler;
+// 	type WeightInfo = ();
+// }
 
-impl pallet_nft_transaction_payment::Config for Runtime {
-	type SponsorshipHandler = SponsorshipHandler;
-}
-
 impl pallet_evm_transaction_payment::Config for Runtime {
-	type SponsorshipHandler = (
-		pallet_nft::NftEthSponsorshipHandler<Self>,
-		pallet_evm_contract_helpers::HelpersContractSponsoring<Self>,
-	);
+	type EvmSponsorshipHandler = EvmSponsorshipHandler;
 	type Currency = Balances;
+	type EvmAddressMapping = HashedAddressMapping<Self::Hashing>;
+	type EvmBackwardsAddressMapping = up_evm_mapping::MapBackwardsAddressTruncated;
 }
 
 impl pallet_nft_charge_transaction::Config for Runtime {
-	type SponsorshipHandler = pallet_nft::NftSponsorshipHandler<Runtime>;
+	type SponsorshipHandler = SponsorshipHandler;
 }
 
 // impl pallet_contract_helpers::Config for Runtime {
@@ -869,8 +858,8 @@
 		// Unique Pallets
 		Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
 		Nft: pallet_nft::{Pallet, Call, Storage} = 61,
-		Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
-		NftPayment: pallet_nft_transaction_payment::{Pallet, Call, Storage} = 63,
+		// Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
+		// free = 63
 		Charging: pallet_nft_charge_transaction::{Pallet, Call, Storage } = 64,
 		// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
 		Common: pallet_common::{Pallet, Storage, Event<T>} = 66,
@@ -1056,14 +1045,23 @@
 				.or_else(|| <pallet_evm_contract_helpers::HelpersOnMethodCall<Self>>::get_code(&account))
 		}
 		fn adminlist(collection: CollectionId) -> Vec<CrossAccountId> {
-			<pallet_nft::Pallet<Runtime>>::adminlist(collection)
+			<pallet_common::Pallet<Runtime>>::adminlist(collection)
 		}
 		fn allowlist(collection: CollectionId) -> Vec<CrossAccountId> {
-			<pallet_nft::Pallet<Runtime>>::allowlist(collection)
+			<pallet_common::Pallet<Runtime>>::allowlist(collection)
 		}
+		fn allowed(collection: CollectionId, user: CrossAccountId) -> bool {
+			<pallet_common::Pallet<Runtime>>::allowed(collection, user)
+		}
 		fn last_token_id(collection: CollectionId) -> TokenId {
 			dispatch_nft_runtime!(collection.last_token_id())
 		}
+		fn collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>> {
+			<pallet_common::CollectionById<Runtime>>::get(collection)
+		}
+		fn collection_stats() -> CollectionStats {
+			<pallet_common::Pallet<Runtime>>::collection_stats()
+		}
 	}
 
 	impl sp_api::Core<Block> for Runtime {
modifiedtests/src/addCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -8,7 +8,7 @@
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from './substrate/privateKey';
 import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api';
-import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId} from './util/helpers';
+import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId, queryCollectionExpectSuccess} from './util/helpers';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -20,7 +20,7 @@
       const alice = privateKey('//Alice');
       const bob = privateKey('//Bob');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.equal(alice.address);
 
       const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
@@ -38,7 +38,7 @@
       const bob = privateKey('//Bob');
       const charlie = privateKey('//CHARLIE');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.equal(alice.address);
 
       const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
modifiedtests/src/addToAllowList.test.tsdiffbeforeafterboth
--- a/tests/src/addToAllowList.test.ts
+++ b/tests/src/addToAllowList.test.ts
@@ -18,6 +18,7 @@
   normalizeAccountId,
   addCollectionAdminExpectSuccess,
   addToAllowListExpectFail,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -55,7 +56,7 @@
   it('Allow list an address in the collection that does not exist', async () => {
     await usingApi(async (api) => {
       // tslint:disable-next-line: no-bitwise
-      const collectionId = ((await api.query.common.createdCollectionCount()).toNumber()) + 1;
+      const collectionId = await getCreatedCollectionCount(api) + 1;
       const bob = privateKey('//Bob');
 
       const tx = api.tx.nft.addToAllowList(collectionId, normalizeAccountId(bob.address));
modifiedtests/src/approve.test.tsdiffbeforeafterboth
--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -18,9 +18,13 @@
   transferExpectSuccess,
   addCollectionAdminExpectSuccess,
   adminApproveFromExpectSuccess,
+  getCreatedCollectionCount,
+  transferFromExpectSuccess,
+  transferFromExpectFail,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
+const expect = chai.expect;
 
 describe('Integration Test approve(spender, collection_id, item_id, amount):', () => {
   let alice: IKeyringPair;
@@ -78,6 +82,320 @@
   });
 });
 
+describe('Normal user can approve other users to transfer:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+    });
+  });  
+
+  it('NFT', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address);
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+  });
+
+  it('Fungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', bob.address); 
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+  });
+
+  it('ReFungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } });
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address);
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+  });
+});
+
+describe('Approved users can transferFrom up to approved amount:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+    });
+  });  
+
+  it('NFT', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address);
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'NFT');
+  });
+
+  it('Fungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', bob.address); 
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'Fungible');
+  });
+
+  it('ReFungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } });
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address);
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'ReFungible');
+  });
+});
+
+describe('Approved users cannot use transferFrom to repeat transfers if approved amount was already transferred:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+    });
+  });  
+
+  it('NFT', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address);
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'NFT');
+    await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1);
+  });
+
+  it('Fungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', bob.address); 
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'Fungible');
+    await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1);
+  });
+
+  it('ReFungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } });
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address);
+    await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'ReFungible');
+    await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1);
+  });
+});
+
+describe('Approved amount decreases by the transferred amount.:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+  let dave: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+      dave = privateKey('//Dave');
+    });
+  });  
+
+  it('If a user B is approved to transfer 10 Fungible tokens from user A, they can transfer 2 tokens to user C, which will result in decreasing approval from 10 to 8. Then user B can transfer 8 tokens to user D.', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', alice.address); 
+    await approveExpectSuccess(collectionId, itemId, alice, bob.address, 10);
+    await transferFromExpectSuccess(collectionId, itemId, bob, alice, charlie, 2, 'Fungible');
+    await transferFromExpectSuccess(collectionId, itemId, bob, alice, dave, 8, 'Fungible');
+  });
+});
+
+describe('User may clear the approvals to approving for 0 amount:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+    });
+  });
+
+  it('NFT', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT');
+    await approveExpectSuccess(collectionId, itemId, alice, bob.address, 1);
+    await approveExpectSuccess(collectionId, itemId, alice, bob.address, 0);
+    await transferFromExpectFail(collectionId, itemId, bob, bob, charlie, 1);
+  });
+
+  it('Fungible', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 1);
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 0);
+    await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, bob, charlie, 1);
+  });
+
+  it('ReFungible', async () => {
+    const reFungibleCollectionId =
+      await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 1);
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 0);
+    await transferFromExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, bob, charlie, 1);
+  });
+});
+
+describe('User cannot approve for the amount greater than they own:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+    });
+  });
+
+  it('1 for NFT', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address);
+    await approveExpectFail(collectionId, itemId, bob, charlie, 2);
+  });
+
+  it('Fungible', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+    await approveExpectFail(fungibleCollectionId, newFungibleTokenId, bob, charlie, 11);
+  });
+
+  it('ReFungible', async () => {
+    const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, charlie, 101);
+  });
+});
+
+describe('Administrator and collection owner do not need approval in order to execute TransferFrom:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+  let dave: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+      dave = privateKey('//Dave');
+    });
+  });  
+
+  it('NFT', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, alice, charlie, dave, 1, 'NFT');
+    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
+    await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'NFT');
+  });
+
+  it('Fungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', charlie.address); 
+    await transferFromExpectSuccess(collectionId, itemId, alice, charlie, dave, 1, 'Fungible');
+    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
+    await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'Fungible');
+  });
+
+  it('ReFungible up to an approved amount', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } });
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', charlie.address);
+    await transferFromExpectSuccess(collectionId, itemId, alice, charlie, dave, 1, 'ReFungible');
+    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
+    await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'ReFungible');
+  });
+});
+
+describe('Repeated approvals add up', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+  let dave: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+      dave = privateKey('//Dave');
+    });
+  });  
+
+  it.skip('Owned 10, approval 1: 1, approval 2: 1, resulting approved value: 2. Fungible', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    await createItemExpectSuccess(alice, collectionId, 'Fungible', alice.address);
+    await approveExpectSuccess(collectionId, 0, alice, bob.address, 1);
+    await approveExpectSuccess(collectionId, 0, alice, charlie.address, 1);
+    // const allowances1 = await getAllowance(collectionId, 0, Alice.address, Bob.address);
+    // const allowances2 = await getAllowance(collectionId, 0, Alice.address, Charlie.address);
+    // expect(allowances1 + allowances2).to.be.eq(BigInt(2));
+  });
+
+  it.skip('Owned 10, approval 1: 1, approval 2: 1, resulting approved value: 2. ReFungible', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } });
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address);
+    await approveExpectSuccess(collectionId, itemId, alice, bob.address, 1);
+    await approveExpectSuccess(collectionId, itemId, alice, charlie.address, 1);
+    // const allowances1 = await getAllowance(collectionId, itemId, Alice.address, Bob.address);
+    // const allowances2 = await getAllowance(collectionId, itemId, Alice.address, Charlie.address);
+    // expect(allowances1 + allowances2).to.be.eq(BigInt(2));
+  });
+
+  // Canceled by changing approve logic
+  it.skip('Cannot approve for more than total user`s amount (owned: 10, approval 1: 5 - should succeed, approval 2: 6 - should fail). Fungible', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'Fungible', decimalPoints: 0 }});
+    await createItemExpectSuccess(alice, collectionId, 'Fungible', dave.address);
+    await approveExpectSuccess(collectionId, 0, dave, bob.address, 5);
+    await approveExpectFail(collectionId, 0, dave, charlie, 6);
+  });
+
+  // Canceled by changing approve logic
+  it.skip('Cannot approve for more than total users amount (owned: 100, approval 1: 50 - should succeed, approval 2: 51 - should fail). ReFungible', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode:{ type: 'ReFungible' } });
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', dave.address);
+    await approveExpectSuccess(collectionId, itemId, dave, bob.address, 50);
+    await approveExpectFail(collectionId, itemId, dave, charlie, 51);
+  });
+});
+
+describe('Integration Test approve(spender, collection_id, item_id, amount) with collection admin permissions:', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      charlie = privateKey('//Charlie');
+    });
+  });
+
+  it('can be called by collection admin on non-owned item', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
+    await adminApproveFromExpectSuccess(collectionId, itemId, bob, alice.address, charlie.address);
+  });
+});
+
 describe('Negative Integration Test approve(spender, collection_id, item_id, amount):', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
@@ -94,13 +412,13 @@
   it('Approve for a collection that does not exist', async () => {
     await usingApi(async (api: ApiPromise) => {
       // nft
-      const nftCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const nftCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(nftCollectionCount + 1, 1, alice, bob);
       // fungible
-      const fungibleCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const fungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(fungibleCollectionCount + 1, 0, alice, bob);
       // reFungible
-      const reFungibleCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const reFungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(reFungibleCollectionCount + 1, 1, alice, bob);
     });
   });
@@ -169,27 +487,5 @@
     await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: false});
 
     await approveExpectFail(collectionId, itemId, alice, charlie);
-  });
-});
-
-describe('Integration Test approve(spender, collection_id, item_id, amount) with collection admin permissions:', () => {
-  let alice: IKeyringPair;
-  let bob: IKeyringPair;
-  let charlie: IKeyringPair;
-
-  before(async () => {
-    await usingApi(async () => {
-      alice = privateKey('//Alice');
-      bob = privateKey('//Bob');
-      charlie = privateKey('//Charlie');
-    });
-  });
-
-  it('can be called by collection admin on non-owned item', async () => {
-    const collectionId = await createCollectionExpectSuccess();
-    const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
-
-    await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
-    await adminApproveFromExpectSuccess(collectionId, itemId, bob, alice.address, charlie.address);
   });
 });
modifiedtests/src/change-collection-owner.test.tsdiffbeforeafterboth
--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -22,6 +22,7 @@
   setMintPermissionExpectFailure,
   destroyCollectionExpectFailure,
   setPublicAccessModeExpectSuccess,
+  queryCollectionExpectSuccess,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -34,13 +35,13 @@
       const alice = privateKey('//Alice');
       const bob = privateKey('//Bob');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection =await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.deep.eq(alice.address);
 
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
       await submitTransactionAsync(alice, changeOwnerTx);
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(bob.address);
     });
   });
@@ -53,7 +54,7 @@
       const alice = privateKey('//Alice');
       const bob = privateKey('//Bob');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.deep.eq(alice.address);
 
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
@@ -62,7 +63,7 @@
       const badChangeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, alice.address);
       await expect(submitTransactionExpectFailAsync(alice, badChangeOwnerTx)).to.be.rejected;
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(bob.address);
     });
   });
@@ -74,13 +75,13 @@
       const bob = privateKey('//Bob');
       const charlie = privateKey('//Charlie');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.deep.eq(alice.address);
 
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
       await submitTransactionAsync(alice, changeOwnerTx);
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(bob.address);
 
       // After changing the owner of the collection, all privileged methods are available to the new owner
@@ -118,20 +119,20 @@
       const bob = privateKey('//Bob');
       const charlie = privateKey('//Charlie');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.deep.eq(alice.address);
 
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
       await submitTransactionAsync(alice, changeOwnerTx);
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(bob.address);
 
       const changeOwnerTx2 = api.tx.nft.changeCollectionOwner(collectionId, charlie.address);
       await submitTransactionAsync(bob, changeOwnerTx2);
 
       // ownership lost
-      const collectionAfterOwnerChange2 = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange2 = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange2.owner.toString()).to.be.deep.eq(charlie.address);
     });
   });
@@ -147,7 +148,7 @@
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
       await expect(submitTransactionExpectFailAsync(bob, changeOwnerTx)).to.be.rejected;
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(alice.address);
 
       // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
@@ -166,7 +167,7 @@
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
       await expect(submitTransactionExpectFailAsync(bob, changeOwnerTx)).to.be.rejected;
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(alice.address);
 
       // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
@@ -195,7 +196,7 @@
       const bob = privateKey('//Bob');
       const charlie = privateKey('//Charlie');
 
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.deep.eq(alice.address);
 
       const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
@@ -204,7 +205,7 @@
       const badChangeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, alice.address);
       await expect(submitTransactionExpectFailAsync(alice, badChangeOwnerTx)).to.be.rejected;
 
-      const collectionAfterOwnerChange = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collectionAfterOwnerChange = await queryCollectionExpectSuccess(api, collectionId);
       expect(collectionAfterOwnerChange.owner.toString()).to.be.deep.eq(bob.address);
 
       await setCollectionSponsorExpectFailure(collectionId, charlie.address, '//Alice');
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -20,6 +20,7 @@
   addToAllowListExpectSuccess,
   normalizeAccountId,
   addCollectionAdminExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 import {Keyring} from '@polkadot/api';
 import {IKeyringPair} from '@polkadot/types/types';
@@ -251,23 +252,20 @@
       const zeroBalance = await findUnusedAddress(api);
 
       // Mint token for alice
-      const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address);
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', zeroBalance.address);
 
-      // Transfer this token from Alice to unused address and back
-      // Alice to Zero gets sponsored
-      const aliceToZero = api.tx.nft.transfer(normalizeAccountId(zeroBalance.address), collectionId, itemId, 1);
-      const events1 = await submitTransactionAsync(alice, aliceToZero);
+      const zeroToAlice = api.tx.nft.transfer(normalizeAccountId(alice.address), collectionId, itemId, 1);
+
+      // Zero to alice gets sponsored
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
       const result1 = getGenericResult(events1);
       expect(result1.success).to.be.true;
 
       // Second transfer should fail
       const sponsorBalanceBefore = (await api.query.system.account(bob.address)).data.free.toBigInt();
-      const zeroToAlice = api.tx.nft.transfer(normalizeAccountId(alice.address), collectionId, itemId, 1);
-      const badTransaction = async function () {
-        await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
-      };
-      await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');
+      await expect(submitTransactionExpectFailAsync(zeroBalance, zeroToAlice)).to.be.rejectedWith('Inability to pay some fees');
       const sponsorBalanceAfter = (await api.query.system.account(bob.address)).data.free.toBigInt();
+      expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);
 
       // Try again after Zero gets some balance - now it should succeed
       const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
@@ -275,8 +273,6 @@
       const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
       const result2 = getGenericResult(events2);
       expect(result2.success).to.be.true;
-
-      expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);
     });
   });
 
@@ -335,7 +331,7 @@
     // Find the collection that never existed
     let collectionId = 0;
     await usingApi(async (api) => {
-      collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      collectionId = await getCreatedCollectionCount(api) + 1;
     });
 
     await confirmSponsorshipExpectFailure(collectionId, '//Bob');
modifiedtests/src/contracts.test.tsdiffbeforeafterboth
--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -228,7 +228,7 @@
       const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, contract.address);
       await submitTransactionAsync(alice, changeAdminTx);
 
-      expect(await isAllowlisted(collectionId, bob.address)).to.be.false;
+      expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false;
 
       {
         const transferTx = contract.tx.toggleAllowList(value, gasLimit, collectionId, bob.address, true);
@@ -236,7 +236,7 @@
         const result = getGenericResult(events);
         expect(result.success).to.be.true;
 
-        expect(await isAllowlisted(collectionId, bob.address)).to.be.true;
+        expect(await isAllowlisted(api, collectionId, bob.address)).to.be.true;
       }
       {
         const transferTx = contract.tx.toggleAllowList(value, gasLimit, collectionId, bob.address, false);
@@ -244,7 +244,7 @@
         const result = getGenericResult(events);
         expect(result.success).to.be.true;
 
-        expect(await isAllowlisted(collectionId, bob.address)).to.be.false;
+        expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false;
       }
     });
   });
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -20,6 +20,7 @@
   getLastTokenId,
   getVariableMetadata,
   getConstMetadata,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -273,7 +274,7 @@
 
   it('Create token in not existing collection', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      const collectionId = await getCreatedCollectionCount(api) + 1;
       const createMultipleItemsTx = api.tx.nft
         .createMultipleItems(collectionId, normalizeAccountId(alice.address), ['NFT', 'NFT', 'NFT']);
       await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTx)).to.be.rejected;
modifiedtests/src/creditFeesToTreasury.test.tsdiffbeforeafterboth
--- a/tests/src/creditFeesToTreasury.test.ts
+++ b/tests/src/creditFeesToTreasury.test.ts
@@ -14,6 +14,7 @@
   createItemExpectSuccess,
   getGenericResult,
   transferExpectSuccess,
+  UNIQUE,
 } from './util/helpers';
 
 import {default as waitNewBlocks} from './substrate/wait-new-blocks';
@@ -169,12 +170,11 @@
       const aliceBalanceBefore: bigint = (await api.query.system.account(alicesPublicKey)).data.free.toBigInt();
       await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');
       const aliceBalanceAfter: bigint = (await api.query.system.account(alicesPublicKey)).data.free.toBigInt();
-      const fee = aliceBalanceBefore - aliceBalanceAfter;
+      const fee = Number(aliceBalanceBefore - aliceBalanceAfter) / Number(UNIQUE);
 
-      // console.log(fee.toString());
       const expectedTransferFee = 0.1;
       const tolerance = 0.001;
-      expect(Number(fee) / 1e15 - expectedTransferFee).to.be.lessThan(tolerance);
+      expect(Number(fee) / Number(UNIQUE) - expectedTransferFee).to.be.lessThan(tolerance);
     });
   });
 
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -13,6 +13,7 @@
   destroyCollectionExpectFailure,
   setCollectionLimitsExpectSuccess,
   addCollectionAdminExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -46,7 +47,7 @@
   it('(!negative test!) Destroy a collection that never existed', async () => {
     await usingApi(async (api) => {
       // Find the collection that never existed
-      const collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      const collectionId = await getCreatedCollectionCount(api) + 1;
       await destroyCollectionExpectFailure(collectionId);
     });
   });
modifiedtests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth
--- a/tests/src/eth/marketplace/marketplace.test.ts
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -2,7 +2,7 @@
 import {getBalanceSingle, transferBalanceExpectSuccess} from '../../substrate/get-balance';
 import privateKey from '../../substrate/privateKey';
 import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, getTokenOwner, setCollectionSponsorExpectSuccess, transferExpectSuccess, transferFromExpectSuccess} from '../../util/helpers';
-import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase} from '../util/helpers';
+import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, subToEth, subToEthLowercase, transferBalanceToEth} from '../util/helpers';
 import {evmToAddress} from '@polkadot/util-crypto';
 import nonFungibleAbi from '../nonFungibleAbi.json';
 import fungibleAbi from '../fungibleAbi.json';
@@ -12,35 +12,33 @@
 
 describe('Matcher contract usage', () => {
   itWeb3('With UNQ', async ({api, web3}) => {
+    const alice = privateKey('//Alice');
+    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId));
     const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const helpers = contractHelpers(web3, matcherOwner);
+
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
       ...GAS_ARGS,
     });
     const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments:[matcherOwner]}).send({from: matcherOwner});
-    const helpers = contractHelpers(web3, matcherOwner);
+
+    await transferBalanceToEth(api, alice, matcher.options.address);
     await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: matcherOwner});
-    await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
 
-    const alice = privateKey('//Alice');
-    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner});
+    await transferBalanceToEth(api, alice, subToEth(alice.address));
     await setCollectionSponsorExpectSuccess(collectionId, alice.address);
     await confirmSponsorshipExpectSuccess(collectionId);
 
-    await helpers.methods.toggleAllowed(matcher.options.address, subToEth(alice.address), true).send({from: matcherOwner});
-    await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(alice.address)));
-
-    const seller = privateKey('//Bob');
+    const seller = privateKey('//Seller/' + Date.now());
+    await addToAllowListExpectSuccess(alice, collectionId, {Ethereum:subToEth(seller.address)});
     await helpers.methods.toggleAllowed(matcher.options.address, subToEth(seller.address), true).send({from: matcherOwner});
-    await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(seller.address)));
 
     const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address);
 
-    // To transfer item to matcher it first needs to be transfered to EVM account of bob
+    // To transfer item to matcher it first needs to be transfered to EVM account of seller
     await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)});
-
-    // Token is owned by seller initially
     expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)});
 
     // Ask
@@ -55,14 +53,12 @@
     // Buy
     {
       const sellerBalanceBeforePurchase = await getBalanceSingle(api, seller.address);
-      // There is two functions named 'buy', so we should provide full signature
-      await executeEthTxOnSub(api, alice, matcher, m => m['buy(address,uint256)'](evmCollection.options.address, tokenId), {value: PRICE});
+      await executeEthTxOnSub(api, alice, matcher, m => m.buy(evmCollection.options.address, tokenId), {value: PRICE});
       expect(await getBalanceSingle(api, seller.address) - sellerBalanceBeforePurchase === PRICE);
     }
 
     // Token is transferred to evm account of alice
     expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(alice.address)});
-
 
     // Transfer token to substrate side of alice
     await transferFromExpectSuccess(collectionId, tokenId, alice, {Ethereum: subToEth(alice.address)}, {Substrate: alice.address});
@@ -137,37 +133,35 @@
   });
 
   itWeb3('With escrow', async ({api, web3}) => {
+    const alice = privateKey('//Alice');
+    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId));
     const matcherOwner = await createEthAccountWithBalance(api, web3);
+    const helpers = contractHelpers(web3, matcherOwner);
     const escrow = await createEthAccountWithBalance(api, web3);
+
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
       ...GAS_ARGS,
     });
     const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments: [matcherOwner]}).send({from: matcherOwner});
     await matcher.methods.setEscrow(escrow).send({from: matcherOwner});
-    const helpers = contractHelpers(web3, matcherOwner);
+
+    await transferBalanceToEth(api, alice, matcher.options.address);
     await helpers.methods.toggleSponsoring(matcher.options.address, true).send({from: matcherOwner});
-    await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
 
-    const alice = privateKey('//Alice');
-    const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner});
+    await transferBalanceToEth(api, alice, subToEth(alice.address));
     await setCollectionSponsorExpectSuccess(collectionId, alice.address);
     await confirmSponsorshipExpectSuccess(collectionId);
-
-    await helpers.methods.toggleAllowed(matcher.options.address, subToEth(alice.address), true).send({from: matcherOwner});
-    await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(alice.address)));
 
-    const seller = privateKey('//Bob');
+    const seller = privateKey('//Seller/' + Date.now());
     await helpers.methods.toggleAllowed(matcher.options.address, subToEth(seller.address), true).send({from: matcherOwner});
     await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(seller.address)));
 
     const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address);
 
-    // To transfer item to matcher it first needs to be transfered to EVM account of bob
+    // To transfer item to matcher it first needs to be transfered to EVM account of seller
     await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)});
-
-    // Token is owned by seller initially
     expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)});
 
     // Ask
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -228,6 +228,18 @@
   return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});
 }
 
+/**
+ * Execute ethereum method call using substrate account
+ * @param to target contract
+ * @param mkTx - closure, receiving `contract.methods`, and returning method call,
+ * to be used as following (assuming `to` = erc20 contract):
+ * `m => m.transfer(to, amount)`
+ * 
+ * # Example
+ * ```ts
+ * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount));
+ * ```
+ */
 export async function executeEthTxOnSub(api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) {
   const tx = api.tx.evm.call(
     subToEth(from.address),
@@ -246,6 +258,11 @@
   return (await getBalance(api, [evmToAddress(address)]))[0];
 }
 
+/**
+ * Measure how much gas given closure consumes
+ * 
+ * @param user which user balance will be checked
+ */
 export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise<any>): Promise<bigint> {
   const before = await ethBalanceViaSub(api, user);
 
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -1,7 +1,7 @@
 // Auto-generated via `yarn polkadot-types-from-chain`, do not edit
 /* eslint-disable */
 
-import type { NftDataStructsCollectionId, NftDataStructsTokenId, PalletCommonAccountBasicCrossAccountIdRepr } from './nft';
+import type { NftDataStructsCollection, NftDataStructsCollectionId, NftDataStructsCollectionStats, NftDataStructsTokenId, PalletCommonAccountBasicCrossAccountIdRepr } from './nft';
 import type { Bytes, HashMap, Json, Metadata, Null, Option, StorageKey, Text, U256, U64, Vec, bool, u128, u32, u64 } from '@polkadot/types';
 import type { ExtrinsicOrHash, ExtrinsicStatus } from '@polkadot/types/interfaces/author';
 import type { EpochAuthorship } from '@polkadot/types/interfaces/babe';
@@ -373,6 +373,10 @@
        **/
       allowance: AugmentedRpc<(collection: NftDataStructsCollectionId | AnyNumber | Uint8Array, sender: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, spender: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, tokenId: NftDataStructsTokenId | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u128>>;
       /**
+       * Check if user is allowed to use collection
+       **/
+      allowed: AugmentedRpc<(collection: NftDataStructsCollectionId | AnyNumber | Uint8Array, account: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<bool>>;
+      /**
        * Get allowlist
        **/
       allowlist: AugmentedRpc<(collection: NftDataStructsCollectionId | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<PalletCommonAccountBasicCrossAccountIdRepr>>>;
@@ -381,6 +385,14 @@
        **/
       balance: AugmentedRpc<(collection: NftDataStructsCollectionId | AnyNumber | Uint8Array, account: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, tokenId: NftDataStructsTokenId | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u128>>;
       /**
+       * Get collection by specified id
+       **/
+      collectionById: AugmentedRpc<(collection: NftDataStructsCollectionId | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<NftDataStructsCollection>>>;
+      /**
+       * Get collection stats
+       **/
+      collectionStats: AugmentedRpc<(at?: Hash | string | Uint8Array) => Observable<NftDataStructsCollectionStats>>;
+      /**
        * Get tokens contained in collection
        **/
       collectionTokens: AugmentedRpc<(collection: NftDataStructsCollectionId | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<NftDataStructsTokenId>>>;
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -2,7 +2,7 @@
 /* eslint-disable */
 
 import type { EthereumBlock, EthereumLog, EthereumReceipt, EthereumTransactionLegacyTransaction, EvmCoreErrorExitReason, FpRpcTransactionStatus } from './ethereum';
-import type { NftDataStructsAccessMode, NftDataStructsCollection, NftDataStructsCollectionId, NftDataStructsCollectionLimits, NftDataStructsCollectionMode, NftDataStructsCreateItemData, NftDataStructsMetaUpdatePermission, NftDataStructsSchemaVersion, NftDataStructsSponsorshipState, NftDataStructsTokenId, PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2 } from './nft';
+import type { NftDataStructsAccessMode, NftDataStructsCollection, NftDataStructsCollectionId, NftDataStructsCollectionLimits, NftDataStructsCollectionMode, NftDataStructsCollectionStats, NftDataStructsCreateItemData, NftDataStructsMetaUpdatePermission, NftDataStructsSchemaVersion, NftDataStructsSponsorshipState, NftDataStructsTokenId, PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2 } from './nft';
 import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundStatus, CumulusPalletXcmpQueueOutboundStatus, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1PersistedValidationData } from './polkadot';
 import type { BitVec, Bool, Bytes, Data, I128, I16, I256, I32, I64, I8, Json, Null, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
@@ -627,6 +627,7 @@
     NftDataStructsCollectionId: NftDataStructsCollectionId;
     NftDataStructsCollectionLimits: NftDataStructsCollectionLimits;
     NftDataStructsCollectionMode: NftDataStructsCollectionMode;
+    NftDataStructsCollectionStats: NftDataStructsCollectionStats;
     NftDataStructsCreateItemData: NftDataStructsCreateItemData;
     NftDataStructsMetaUpdatePermission: NftDataStructsMetaUpdatePermission;
     NftDataStructsSchemaVersion: NftDataStructsSchemaVersion;
modifiedtests/src/interfaces/nft/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/nft/definitions.ts
+++ b/tests/src/interfaces/nft/definitions.ts
@@ -40,6 +40,9 @@
     constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
     variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
     tokenExists: fun('Check if token exists', [collectionParam, tokenParam], 'bool'),
+    collectionById: fun('Get collection by specified id', [collectionParam], 'Option<NftDataStructsCollection>'),
+    collectionStats: fun('Get collection stats', [], 'NftDataStructsCollectionStats'),
+    allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
   },
   types: {
     PalletCommonAccountBasicCrossAccountIdRepr: {
@@ -64,6 +67,11 @@
       constOnChainSchema: 'Vec<u8>',
       metaUpdatePermission: 'NftDataStructsMetaUpdatePermission',
     },
+    NftDataStructsCollectionStats: {
+      created: 'u32',
+      destroyed: 'u32',
+      alive: 'u32',
+    },
     NftDataStructsCollectionId: 'u32',
     NftDataStructsTokenId: 'u32',
     PalletNonfungibleItemData: mkDummy('NftItemData'),
modifiedtests/src/interfaces/nft/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/nft/types.ts
+++ b/tests/src/interfaces/nft/types.ts
@@ -48,6 +48,13 @@
   readonly dummyCollectionMode: u32;
 }
 
+/** @name NftDataStructsCollectionStats */
+export interface NftDataStructsCollectionStats extends Struct {
+  readonly created: u32;
+  readonly destroyed: u32;
+  readonly alive: u32;
+}
+
 /** @name NftDataStructsCreateItemData */
 export interface NftDataStructsCreateItemData extends Struct {
   readonly dummyCreateItemData: u32;
modifiedtests/src/pallet-presence.test.tsdiffbeforeafterboth
--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -39,7 +39,6 @@
   'nonfungible',
   'refungible',
   'scheduler',
-  'nftpayment',
   'charging',
 ];
 
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -8,7 +8,7 @@
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from './substrate/privateKey';
 import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api';
-import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId} from './util/helpers';
+import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId, queryCollectionExpectSuccess} from './util/helpers';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -19,7 +19,7 @@
       const collectionId = await createCollectionExpectSuccess();
       const alice = privateKey('//Alice');
       const bob = privateKey('//Bob');
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.deep.eq(alice.address);
       // first - add collection admin Bob
       const addAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
@@ -43,7 +43,7 @@
       const alice = privateKey('//Alice');
       const bob = privateKey('//Bob');
       const charlie = privateKey('//Charlie');
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       // first - add collection admin Bob
       const addAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
modifiedtests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionSponsor.test.ts
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -18,6 +18,7 @@
   removeCollectionSponsorExpectFailure,
   normalizeAccountId,
   addCollectionAdminExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 import {Keyring} from '@polkadot/api';
 import {IKeyringPair} from '@polkadot/types/types';
@@ -98,7 +99,7 @@
     // Find the collection that never existed
     let collectionId = 0;
     await usingApi(async (api) => {
-      collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      collectionId = await getCreatedCollectionCount(api) + 1;
     });
 
     await removeCollectionSponsorExpectFailure(collectionId);
modifiedtests/src/removeFromAllowList.test.tsdiffbeforeafterboth
--- a/tests/src/removeFromAllowList.test.ts
+++ b/tests/src/removeFromAllowList.test.ts
@@ -37,13 +37,13 @@
   });
 
   it('ensure bob is not in allowlist after removal', async () => {
-    await usingApi(async () => {
+    await usingApi(async api => {
       const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await enableAllowListExpectSuccess(alice, collectionId);
       await addToAllowListExpectSuccess(alice, collectionId, bob.address);
 
       await removeFromAllowListExpectSuccess(alice, collectionId, normalizeAccountId(bob.address));
-      expect(await isAllowlisted(collectionId, bob.address)).to.be.false;
+      expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false;
     });
   });
 
@@ -104,13 +104,13 @@
   });
 
   it('ensure address is not in allowlist after removal', async () => {
-    await usingApi(async () => {
+    await usingApi(async api => {
       const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await enableAllowListExpectSuccess(alice, collectionId);
       await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
       await addToAllowListExpectSuccess(alice, collectionId, charlie.address);
       await removeFromAllowListExpectSuccess(bob, collectionId, normalizeAccountId(charlie.address));
-      expect(await isAllowlisted(collectionId, charlie.address)).to.be.false;
+      expect(await isAllowlisted(api, collectionId, charlie.address)).to.be.false;
     });
   });
 
modifiedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -11,6 +11,7 @@
   destroyCollectionExpectSuccess,
   setCollectionSponsorExpectFailure,
   addCollectionAdminExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 import {Keyring} from '@polkadot/api';
 import {IKeyringPair} from '@polkadot/types/types';
@@ -77,7 +78,7 @@
     // Find the collection that never existed
     let collectionId = 0;
     await usingApi(async (api) => {
-      collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      collectionId = await getCreatedCollectionCount(api) + 1;
     });
 
     await setCollectionSponsorExpectFailure(collectionId, bob.address);
modifiedtests/src/setConstOnChainSchema.test.tsdiffbeforeafterboth
--- a/tests/src/setConstOnChainSchema.test.ts
+++ b/tests/src/setConstOnChainSchema.test.ts
@@ -12,6 +12,8 @@
   createCollectionExpectSuccess,
   destroyCollectionExpectSuccess,
   addCollectionAdminExpectSuccess,
+  queryCollectionExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -37,7 +39,7 @@
   it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
     await usingApi(async (api) => {
       const collectionId = await createCollectionExpectSuccess();
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       const setShema = api.tx.nft.setConstOnChainSchema(collectionId, shema);
       await submitTransactionAsync(alice, setShema);
@@ -47,7 +49,7 @@
   it('Collection admin can set the scheme', async () => {
     await usingApi(async (api) => {
       const collectionId = await createCollectionExpectSuccess();
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
       const setShema = api.tx.nft.setConstOnChainSchema(collectionId, shema);
@@ -60,7 +62,7 @@
       const collectionId = await createCollectionExpectSuccess();
       const setShema = api.tx.nft.setConstOnChainSchema(collectionId, shema);
       await submitTransactionAsync(alice, setShema);
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.constOnChainSchema.toString()).to.be.eq(shema);
     });
   });
@@ -71,7 +73,7 @@
   it('Set a non-existent collection', async () => {
     await usingApi(async (api) => {
       // tslint:disable-next-line: radix
-      const collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      const collectionId = await getCreatedCollectionCount(api) + 1;
       const setShema = api.tx.nft.setConstOnChainSchema(collectionId, shema);
       await expect(submitTransactionExpectFailAsync(alice, setShema)).to.be.rejected;
     });
@@ -97,7 +99,7 @@
   it('Execute method not on behalf of the collection owner', async () => {
     await usingApi(async (api) => {
       const collectionId = await createCollectionExpectSuccess();
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       const setShema = api.tx.nft.setConstOnChainSchema(collectionId, shema);
       await expect(submitTransactionExpectFailAsync(bob, setShema)).to.be.rejected;
modifiedtests/src/setPublicAccessMode.test.tsdiffbeforeafterboth
--- a/tests/src/setPublicAccessMode.test.ts
+++ b/tests/src/setPublicAccessMode.test.ts
@@ -19,6 +19,7 @@
   enableAllowListExpectSuccess,
   normalizeAccountId,
   addCollectionAdminExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -60,7 +61,7 @@
   it('Set a non-existent collection', async () => {
     await usingApi(async (api: ApiPromise) => {
       // tslint:disable-next-line: radix
-      const collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      const collectionId = await getCreatedCollectionCount(api) + 1;
       const tx = api.tx.nft.setPublicAccessMode(collectionId, 'AllowList');
       await expect(submitTransactionExpectFailAsync(alice, tx)).to.be.rejected;
     });
modifiedtests/src/setVariableOnChainSchema.test.tsdiffbeforeafterboth
--- a/tests/src/setVariableOnChainSchema.test.ts
+++ b/tests/src/setVariableOnChainSchema.test.ts
@@ -12,6 +12,8 @@
   createCollectionExpectSuccess,
   destroyCollectionExpectSuccess,
   addCollectionAdminExpectSuccess,
+  queryCollectionExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -37,7 +39,7 @@
   it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
     await usingApi(async (api) => {
       const collectionId = await createCollectionExpectSuccess();
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, schema);
       await submitTransactionAsync(alice, setSchema);
@@ -49,7 +51,7 @@
       const collectionId = await createCollectionExpectSuccess();
       const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, schema);
       await submitTransactionAsync(alice, setSchema);
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.variableOnChainSchema.toString()).to.be.eq(schema);
 
     });
@@ -61,7 +63,7 @@
   it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
     await usingApi(async (api) => {
       const collectionId = await createCollectionExpectSuccess();
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
       const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, schema);
@@ -75,7 +77,7 @@
       await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
       const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, schema);
       await submitTransactionAsync(bob, setSchema);
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.variableOnChainSchema.toString()).to.be.eq(schema);
 
     });
@@ -87,7 +89,7 @@
   it('Set a non-existent collection', async () => {
     await usingApi(async (api) => {
       // tslint:disable-next-line: radix
-      const collectionId = (await api.query.common.createdCollectionCount()).toNumber() + 1;
+      const collectionId = await getCreatedCollectionCount(api) + 1;
       const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, schema);
       await expect(submitTransactionExpectFailAsync(alice, setSchema)).to.be.rejected;
     });
@@ -113,7 +115,7 @@
   it('Execute method not on behalf of the collection owner', async () => {
     await usingApi(async (api) => {
       const collectionId = await createCollectionExpectSuccess();
-      const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+      const collection = await queryCollectionExpectSuccess(api, collectionId);
       expect(collection.owner.toString()).to.be.eq(alice.address);
       const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, schema);
       await expect(submitTransactionExpectFailAsync(bob, setSchema)).to.be.rejected;
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -19,6 +19,11 @@
   transferExpectFailure,
   transferExpectSuccess,
   addCollectionAdminExpectSuccess,
+  getCreatedCollectionCount,
+  toSubstrateAddress,
+  getTokenOwner,
+  normalizeAccountId,
+  getBalance as getTokenBalance,
 } from './util/helpers';
 
 let alice: IKeyringPair;
@@ -132,13 +137,13 @@
   it('Transfer with not existed collection_id', async () => {
     await usingApi(async (api) => {
       // nft
-      const nftCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const nftCollectionCount = await getCreatedCollectionCount(api);
       await transferExpectFailure(nftCollectionCount + 1, 1, alice, bob, 1);
       // fungible
-      const fungibleCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const fungibleCollectionCount = await getCreatedCollectionCount(api);
       await transferExpectFailure(fungibleCollectionCount + 1, 0, alice, bob, 1);
       // reFungible
-      const reFungibleCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const reFungibleCollectionCount = await getCreatedCollectionCount(api);
       await transferExpectFailure(reFungibleCollectionCount + 1, 1, alice, bob, 1);
     });
   });
@@ -230,3 +235,61 @@
     );
   });
 });
+
+describe('Zero value transfer(From)', () => {
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+    });
+  });
+
+  it('NFT', async () => {
+    await usingApi(async (api: ApiPromise) => {
+      const nftCollectionId = await createCollectionExpectSuccess();
+      const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
+
+      const transferTx = api.tx.nft.transfer(normalizeAccountId(bob), nftCollectionId, newNftTokenId, 0);
+      await submitTransactionAsync(alice, transferTx);
+      const address = normalizeAccountId(await getTokenOwner(api, nftCollectionId, newNftTokenId));
+
+      expect(toSubstrateAddress(address)).to.be.equal(alice.address);
+    });
+  });
+
+  it('RFT', async () => {
+    await usingApi(async (api: ApiPromise) => {
+      const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+      const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+      const balanceBeforeAlice = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(alice), newReFungibleTokenId);
+      const balanceBeforeBob = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(bob), newReFungibleTokenId);
+
+      const transferTx = api.tx.nft.transfer(normalizeAccountId(bob), reFungibleCollectionId, newReFungibleTokenId, 0);
+      await submitTransactionAsync(alice, transferTx);
+
+      const balanceAfterAlice = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(alice), newReFungibleTokenId);
+      const balanceAfterBob = await getTokenBalance(api, reFungibleCollectionId, normalizeAccountId(bob), newReFungibleTokenId);
+
+      expect((balanceBeforeAlice)).to.be.equal(balanceAfterAlice);
+      expect((balanceBeforeBob)).to.be.equal(balanceAfterBob);
+    });
+  });
+
+  it('Fungible', async () => {
+    await usingApi(async (api: ApiPromise) => {
+      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+      const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+      const balanceBeforeAlice = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(alice), newFungibleTokenId);
+      const balanceBeforeBob = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(bob), newFungibleTokenId);
+
+      const transferTx = api.tx.nft.transfer(normalizeAccountId(bob), fungibleCollectionId, newFungibleTokenId, 0);
+      await submitTransactionAsync(alice, transferTx);
+
+      const balanceAfterAlice = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(alice), newFungibleTokenId);
+      const balanceAfterBob = await getTokenBalance(api, fungibleCollectionId, normalizeAccountId(bob), newFungibleTokenId);
+
+      expect((balanceBeforeAlice)).to.be.equal(balanceAfterAlice);
+      expect((balanceBeforeBob)).to.be.equal(balanceAfterBob);
+    });
+  });
+});
\ No newline at end of file
modifiedtests/src/transferFrom.test.tsdiffbeforeafterboth
--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -19,6 +19,7 @@
   transferFromExpectSuccess,
   burnItemExpectSuccess,
   setCollectionLimitsExpectSuccess,
+  getCreatedCollectionCount,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -109,18 +110,18 @@
   it('transferFrom for a collection that does not exist', async () => {
     await usingApi(async (api: ApiPromise) => {
       // nft
-      const nftCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const nftCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(nftCollectionCount + 1, 1, alice, bob);
 
       await transferFromExpectFail(nftCollectionCount + 1, 1, bob, alice, charlie, 1);
 
       // fungible
-      const fungibleCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const fungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(fungibleCollectionCount + 1, 0, alice, bob);
 
       await transferFromExpectFail(fungibleCollectionCount + 1, 0, bob, alice, charlie, 1);
       // reFungible
-      const reFungibleCollectionCount = (await api.query.common.createdCollectionCount()).toNumber();
+      const reFungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(reFungibleCollectionCount + 1, 1, alice, bob);
 
       await transferFromExpectFail(reFungibleCollectionCount + 1, 1, bob, alice, charlie, 1);
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -269,7 +269,7 @@
   let collectionId = 0;
   await usingApi(async (api) => {
     // Get number of collections before the transaction
-    const collectionCountBefore = (await api.query.common.createdCollectionCount()).toNumber();
+    const collectionCountBefore = await getCreatedCollectionCount(api);
 
     // Run the CreateCollection transaction
     const alicePrivateKey = privateKey('//Alice');
@@ -288,10 +288,10 @@
     const result = getCreateCollectionResult(events);
 
     // Get number of collections after the transaction
-    const collectionCountAfter = (await api.query.common.createdCollectionCount()).toNumber();
+    const collectionCountAfter = await getCreatedCollectionCount(api);
 
     // Get the collection
-    const collection = (await api.query.common.collectionById(result.collectionId)).unwrap();
+    const collection = await queryCollectionExpectSuccess(api, result.collectionId);
 
     // What to expect
     // tslint:disable-next-line:no-unused-expression
@@ -325,7 +325,7 @@
 
   await usingApi(async (api) => {
     // Get number of collections before the transaction
-    const collectionCountBefore = (await api.query.common.createdCollectionCount()).toNumber();
+    const collectionCountBefore = await getCreatedCollectionCount(api);
 
     // Run the CreateCollection transaction
     const alicePrivateKey = privateKey('//Alice');
@@ -334,7 +334,7 @@
     const result = getCreateCollectionResult(events);
 
     // Get number of collections after the transaction
-    const collectionCountAfter = (await api.query.common.createdCollectionCount()).toNumber();
+    const collectionCountAfter = await getCreatedCollectionCount(api);
 
     // What to expect
     // tslint:disable-next-line:no-unused-expression
@@ -364,7 +364,7 @@
 }
 
 export async function findNotExistingCollection(api: ApiPromise): Promise<number> {
-  const totalNumber = (await api.query.common.createdCollectionCount()).toNumber();
+  const totalNumber = await getCreatedCollectionCount(api);
   const newCollection: number = totalNumber + 1;
   return newCollection;
 }
@@ -398,7 +398,7 @@
     expect(result).to.be.true;
 
     // What to expect
-    expect((await api.query.common.collectionById(collectionId)).isNone).to.be.true;
+    expect(await getDetailedCollectionInfo(api, collectionId)).to.be.null;
   });
 }
 
@@ -432,7 +432,7 @@
     const result = getGenericResult(events);
 
     // Get the collection
-    const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+    const collection = await queryCollectionExpectSuccess(api, collectionId);
 
     // What to expect
     expect(result.success).to.be.true;
@@ -452,7 +452,7 @@
     const result = getGenericResult(events);
 
     // Get the collection
-    const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+    const collection = await queryCollectionExpectSuccess(api, collectionId);
 
     // What to expect
     expect(result.success).to.be.true;
@@ -490,7 +490,7 @@
     const result = getGenericResult(events);
 
     // Get the collection
-    const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+    const collection = await queryCollectionExpectSuccess(api, collectionId);
 
     // What to expect
     expect(result.success).to.be.true;
@@ -1058,7 +1058,7 @@
     const result = getGenericResult(events);
 
     // Get the collection
-    const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+    const collection = await queryCollectionExpectSuccess(api, collectionId);
 
     // What to expect
     // tslint:disable-next-line:no-unused-expression
@@ -1106,7 +1106,7 @@
     expect(result.success).to.be.true;
 
     // Get the collection
-    const collection = (await api.query.common.collectionById(collectionId)).unwrap();
+    const collection = await queryCollectionExpectSuccess(api, collectionId);
 
     expect(collection.mintMode.toHuman()).to.be.equal(enabled);
   });
@@ -1138,15 +1138,13 @@
   });
 }
 
-export async function isAllowlisted(collectionId: number, address: string | CrossAccountId) {
-  return await usingApi(async (api) => {
-    return (await api.query.common.allowlist(collectionId, normalizeAccountId(address))).toJSON();
-  });
+export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId) {
+  return (await api.rpc.nft.allowed(collectionId, normalizeAccountId(address))).toJSON();
 }
 
 export async function addToAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId | CrossAccountId) {
   await usingApi(async (api) => {
-    expect(await isAllowlisted(collectionId, normalizeAccountId(address))).to.be.false;
+    expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.false;
 
     // Run the transaction
     const tx = api.tx.nft.addToAllowList(collectionId, normalizeAccountId(address));
@@ -1154,14 +1152,14 @@
     const result = getGenericResult(events);
     expect(result.success).to.be.true;
 
-    expect(await isAllowlisted(collectionId, normalizeAccountId(address))).to.be.true;
+    expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true;
   });
 }
 
 export async function addToAllowListAgainExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId) {
   await usingApi(async (api) => {
 
-    expect(await isAllowlisted(collectionId, normalizeAccountId(address))).to.be.true;
+    expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true;
 
     // Run the transaction
     const tx = api.tx.nft.addToAllowList(collectionId, normalizeAccountId(address));
@@ -1169,7 +1167,7 @@
     const result = getGenericResult(events);
     expect(result.success).to.be.true;
 
-    expect(await isAllowlisted(collectionId, normalizeAccountId(address))).to.be.true;
+    expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true;
   });
 }
 
@@ -1215,16 +1213,16 @@
 
 export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number)
   : Promise<NftDataStructsCollection | null> => {
-  return (await api.query.common.collectionById(collectionId)).unwrapOr(null);
+  return (await api.rpc.nft.collectionById(collectionId)).unwrapOr(null);
 };
 
 export const getCreatedCollectionCount = async (api: ApiPromise): Promise<number> => {
   // set global object - collectionsCount
-  return (await api.query.common.createdCollectionCount()).toNumber();
+  return (await api.rpc.nft.collectionStats()).created.toNumber();
 };
 
 export async function queryCollectionExpectSuccess(api: ApiPromise, collectionId: number): Promise<NftDataStructsCollection> {
-  return (await api.query.common.collectionById(collectionId)).unwrap();
+  return (await api.rpc.nft.collectionById(collectionId)).unwrap();
 }
 
 export async function waitNewBlocks(blocksCount = 1): Promise<void> {