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

difftreelog

Merge branch 'develop' into feature/core-214

Igor Kozyrev2021-11-22parents: #9052adc #5846f9c.patch.diff
in: master

24 files changed

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 = [
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
before · pallets/common/src/account.rs
1use crate::Config;2use codec::{Encode, EncodeLike, Decode};3use sp_core::H160;4use scale_info::{Type, TypeInfo};5use sp_core::crypto::AccountId32;6use core::cmp::Ordering;7use serde::{Serialize, Deserialize};8use pallet_evm::AddressMapping;9use sp_std::vec::Vec;10use sp_std::clone::Clone;1112pub trait CrossAccountId<AccountId>:13	Encode + EncodeLike + Decode + TypeInfo + Clone + PartialEq + Ord + core::fmt::Debug + Default14// +15// Serialize + Deserialize<'static>16{17	fn as_sub(&self) -> &AccountId;18	fn as_eth(&self) -> &H160;1920	fn from_sub(account: AccountId) -> Self;21	fn from_eth(account: H160) -> Self;2223	fn conv_eq(&self, other: &Self) -> bool;24}2526#[derive(Encode, Decode, Serialize, Deserialize, TypeInfo)]27#[serde(rename_all = "camelCase")]28enum BasicCrossAccountIdRepr<AccountId> {29	Substrate(AccountId),30	Ethereum(H160),31}3233#[derive(PartialEq, Eq)]34pub struct BasicCrossAccountId<T: Config> {35	/// If true - then ethereum is canonical encoding36	from_ethereum: bool,37	substrate: T::AccountId,38	ethereum: H160,39}4041impl<T: Config> TypeInfo for BasicCrossAccountId<T> {42	type Identity = Self;4344	fn type_info() -> Type {45		<BasicCrossAccountIdRepr<T::AccountId>>::type_info()46	}47}4849impl<T: Config> Default for BasicCrossAccountId<T> {50	fn default() -> Self {51		Self::from_sub(T::AccountId::default())52	}53}5455impl<T: Config> core::fmt::Debug for BasicCrossAccountId<T> {56	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {57		if self.from_ethereum {58			fmt.debug_tuple("CrossAccountId::Ethereum")59				.field(&self.ethereum)60				.finish()61		} else {62			fmt.debug_tuple("CrossAccountId::Substrate")63				.field(&self.substrate)64				.finish()65		}66	}67}6869impl<T: Config> PartialOrd for BasicCrossAccountId<T> {70	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {71		Some(self.substrate.cmp(&other.substrate))72	}73}7475impl<T: Config> Ord for BasicCrossAccountId<T> {76	fn cmp(&self, other: &Self) -> Ordering {77		self.partial_cmp(other)78			.expect("substrate account is total ordered")79	}80}8182impl<T: Config> Clone for BasicCrossAccountId<T> {83	fn clone(&self) -> Self {84		Self {85			from_ethereum: self.from_ethereum,86			substrate: self.substrate.clone(),87			ethereum: self.ethereum,88		}89	}90}91impl<T: Config> Encode for BasicCrossAccountId<T> {92	fn encode(&self) -> Vec<u8> {93		BasicCrossAccountIdRepr::from(self.clone()).encode()94	}95}96impl<T: Config> EncodeLike for BasicCrossAccountId<T> {}97impl<T: Config> Decode for BasicCrossAccountId<T> {98	fn decode<I>(input: &mut I) -> Result<Self, codec::Error>99	where100		I: codec::Input,101	{102		Ok(BasicCrossAccountIdRepr::decode(input)?.into())103	}104}105impl<T> Serialize for BasicCrossAccountId<T>106where107	T: Config,108	T::AccountId: Serialize,109{110	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>111	where112		S: serde::Serializer,113	{114		let repr = BasicCrossAccountIdRepr::from(self.clone());115		(&repr).serialize(serializer)116	}117}118impl<'de, T> Deserialize<'de> for BasicCrossAccountId<T>119where120	T: Config,121	T::AccountId: Deserialize<'de>,122{123	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>124	where125		D: serde::Deserializer<'de>,126	{127		Ok(BasicCrossAccountIdRepr::deserialize(deserializer)?.into())128	}129}130impl<T: Config> CrossAccountId<T::AccountId> for BasicCrossAccountId<T> {131	fn as_sub(&self) -> &T::AccountId {132		&self.substrate133	}134	fn as_eth(&self) -> &H160 {135		&self.ethereum136	}137	fn from_sub(substrate: T::AccountId) -> Self {138		Self {139			ethereum: T::EvmBackwardsAddressMapping::from_account_id(substrate.clone()),140			substrate,141			from_ethereum: false,142		}143	}144	fn from_eth(ethereum: H160) -> Self {145		Self {146			ethereum,147			substrate: T::EvmAddressMapping::into_account_id(ethereum),148			from_ethereum: true,149		}150	}151	fn conv_eq(&self, other: &Self) -> bool {152		if self.from_ethereum == other.from_ethereum {153			self.substrate == other.substrate && self.ethereum == other.ethereum154		} else if self.from_ethereum {155			// ethereum is canonical encoding, but we need to compare derived address156			self.substrate == other.substrate157		} else {158			self.ethereum == other.ethereum159		}160	}161}162impl<T: Config> From<BasicCrossAccountIdRepr<T::AccountId>> for BasicCrossAccountId<T> {163	fn from(repr: BasicCrossAccountIdRepr<T::AccountId>) -> Self {164		match repr {165			BasicCrossAccountIdRepr::Substrate(s) => Self::from_sub(s),166			BasicCrossAccountIdRepr::Ethereum(e) => Self::from_eth(e),167		}168	}169}170impl<T: Config> From<BasicCrossAccountId<T>> for BasicCrossAccountIdRepr<T::AccountId> {171	fn from(v: BasicCrossAccountId<T>) -> Self {172		if v.from_ethereum {173			BasicCrossAccountIdRepr::Ethereum(*v.as_eth())174		} else {175			BasicCrossAccountIdRepr::Substrate(v.as_sub().clone())176		}177	}178}179180pub trait EvmBackwardsAddressMapping<AccountId> {181	fn from_account_id(account_id: AccountId) -> H160;182}183184/// Should have same mapping as EnsureAddressTruncated185pub struct MapBackwardsAddressTruncated;186impl EvmBackwardsAddressMapping<AccountId32> for MapBackwardsAddressTruncated {187	fn from_account_id(account_id: AccountId32) -> H160 {188		let mut out = [0; 20];189		out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]);190		H160(out)191	}192}
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -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<
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,
 		}
 	}
 }
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,64 @@
 //! 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_evm::AddressMapping;
 
 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 as pallet_common::Config>::EvmAddressMapping::into_account_id(*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, .. },
+					)
+					| UniqueNFTCall::ERC721(ERC721Call::TransferFrom { 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::Approve { token_id, .. }) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_approve::<T>(&collection, &who, &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;
-						}
-					}
-					if sponsored {
-						<FungibleTransferBasket<T>>::insert(
-							collection_id,
-							who.as_sub(),
-							block_number,
-						);
-						return Ok(());
+			crate::CollectionMode::Fungible(_) => {
+				let call = UniqueFungibleCall::parse(method_id, &mut reader).ok()??;
+				#[allow(clippy::single_match)]
+				match call {
+					UniqueFungibleCall::ERC20(
+						ERC20Call::Transfer { .. } | ERC20Call::TransferFrom { .. },
+					) => withdraw_transfer::<T>(&collection, &who, &TokenId::default())
+						.map(|()| sponsor),
+					UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+						withdraw_approve::<T>(&collection, &who, &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;
-				}
-				if try_sponsor(who, collection_id, &collection, &call.1).is_ok() {
-					return collection
-						.sponsorship
-						.sponsor()
-						.cloned()
-						.map(T::EvmBackwardsAddressMapping::from_account_id);
+					_ => None,
 				}
 			}
+			_ => None,
 		}
-		None
 	}
 }
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -136,18 +136,22 @@
 		//#region Tokens transfer rate limit baskets
 		/// (Collection id (controlled?2), who created (real))
 		/// TODO: Off chain worker should remove from this map when collection gets removed
-		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;
+		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;
 		/// Collection id (controlled?2), token id (controlled?2)
-		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;
+		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
 		/// Collection id (controlled?2), owning user (real)
-		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;
+		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
 		/// Collection id (controlled?2), token id (controlled?2)
-		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;
+		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>;
 		//#endregion
 
 		/// Variable metadata sponsoring
 		/// Collection id (controlled?2), token id (controlled?2)
-		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;
+		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+		/// Approval sponsoring
+		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
+		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>;
 	}
 }
 
@@ -249,9 +253,12 @@
 
 			<NftTransferBasket<T>>::remove_prefix(collection_id, None);
 			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);
-			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);
+			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);
 
 			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);
+			<NftApproveBasket<T>>::remove_prefix(collection_id, None);
+			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);
+			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);
 
 			Ok(())
 		}
@@ -592,7 +599,15 @@
 		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
-			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))
+			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;
+			if value == 1 {
+				<NftTransferBasket<T>>::remove(collection_id, item_id);
+				<NftApproveBasket<T>>::remove(collection_id, item_id);
+			}
+			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?
+			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());
+			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));
+			Ok(post_info)
 		}
 
 		/// Destroys a concrete instance of NFT on behalf of the owner
modifiedpallets/nft/src/sponsorship.rsdiffbeforeafterboth
--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -1,175 +1,167 @@
 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, NFT_SPONSOR_TRANSFER_TIMEOUT,
+	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,
 };
-use pallet_common::{CollectionById};
+use pallet_common::{CollectionHandle};
 
-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)?;
+pub fn withdraw_transfer<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_transfer_timeout(match collection.mode {
+			CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+			CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+		});
 
-		// 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 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 {
-				return None;
-			}
+	let last_tx_block = match collection.mode {
+		CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),
+		CollectionMode::Fungible(_) => <FungibleTransferBasket<T>>::get(collection.id, who),
+		CollectionMode::ReFungible => {
+			<ReFungibleTransferBasket<T>>::get((collection.id, item_id, who))
 		}
-		CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
+	};
 
-		// 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, block_number)
+		}
+		CollectionMode::ReFungible => {
+			<ReFungibleTransferBasket<T>>::insert((collection.id, item_id, who), 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,
+		});
 
-					sponsored
-				}
-				CollectionMode::Fungible(_) => {
-					// get correct limit
-					let limit = collection_limits
-						.sponsor_transfer_timeout(FUNGIBLE_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;
+		}
+	}
 
-					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);
-					}
+	CreateItemBasket::<T>::insert((collection.id, who.clone()), block_number);
 
-					sponsored
-				}
-				CollectionMode::ReFungible => {
-					// get correct limit
-					let limit = collection_limits
-						.sponsor_transfer_timeout(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
+	Some(())
+}
 
-					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);
-					}
+pub fn withdraw_set_variable_meta_data<T: Config>(
+	collection: &CollectionHandle<T>,
+	item_id: &TokenId,
+	data: &[u8],
+) -> Option<()> {
+	// 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 +173,39 @@
 				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),
+			}
+			| Call::transfer_from {
+				collection_id,
+				item_id,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_transfer::<T>(&collection, who, 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>(&collection, item_id, data).map(|()| sponsor)
+			}
 			_ => None,
 		}
 	}
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;
@@ -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))
 	}
 }
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>;
 
@@ -777,20 +777,14 @@
 	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 {
@@ -803,22 +797,17 @@
 	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 {
@@ -870,7 +859,7 @@
 		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,
+		// 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,
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -251,23 +251,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 +272,6 @@
       const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
       const result2 = getGenericResult(events2);
       expect(result2.success).to.be.true;
-
-      expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);
     });
   });
 
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/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',
 ];