git.delta.rocks / unique-network / refs/commits / 999c24f384bf

difftreelog

Merge pull request #469 from UniqueNetwork/feature/refactor_contract_sponsoring

Yaroslav Bolyukin2022-08-25parents: #cdbbf09 #e80fd26.patch.diff
in: master
Feature/refactor contract sponsoring

19 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2345,7 +2345,7 @@
 [[package]]
 name = "fc-consensus"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "async-trait",
  "fc-db",
@@ -2364,7 +2364,7 @@
 [[package]]
 name = "fc-db"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "fp-storage",
  "kvdb-rocksdb",
@@ -2380,7 +2380,7 @@
 [[package]]
 name = "fc-mapping-sync"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "fc-db",
  "fp-consensus",
@@ -2397,7 +2397,7 @@
 [[package]]
 name = "fc-rpc"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -2437,7 +2437,7 @@
 [[package]]
 name = "fc-rpc-core"
 version = "1.1.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -2578,7 +2578,7 @@
 [[package]]
 name = "fp-consensus"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "parity-scale-codec 3.1.5",
@@ -2590,7 +2590,7 @@
 [[package]]
 name = "fp-evm"
 version = "3.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "evm",
  "frame-support",
@@ -2604,7 +2604,7 @@
 [[package]]
 name = "fp-evm-mapping"
 version = "0.1.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "frame-support",
  "sp-core",
@@ -2613,7 +2613,7 @@
 [[package]]
 name = "fp-rpc"
 version = "3.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -2630,7 +2630,7 @@
 [[package]]
 name = "fp-self-contained"
 version = "1.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "frame-support",
@@ -2646,7 +2646,7 @@
 [[package]]
 name = "fp-storage"
 version = "2.0.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "parity-scale-codec 3.1.5",
 ]
@@ -5445,7 +5445,7 @@
 [[package]]
 name = "pallet-base-fee"
 version = "1.0.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "fp-evm",
  "frame-support",
@@ -5552,7 +5552,7 @@
 
 [[package]]
 name = "pallet-common"
-version = "0.1.6"
+version = "0.1.7"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -5660,7 +5660,7 @@
 [[package]]
 name = "pallet-ethereum"
 version = "4.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -5689,7 +5689,7 @@
 [[package]]
 name = "pallet-evm"
 version = "6.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "evm",
  "fp-evm",
@@ -5733,7 +5733,7 @@
 
 [[package]]
 name = "pallet-evm-contract-helpers"
-version = "0.1.2"
+version = "0.2.0"
 dependencies = [
  "evm-coder",
  "fp-evm-mapping",
@@ -12130,7 +12130,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if 0.1.10",
  "digest 0.10.3",
  "rand 0.8.5",
  "static_assertions",
modifiedcrates/evm-coder/CHANGELOG.mddiffbeforeafterboth
--- a/crates/evm-coder/CHANGELOG.md
+++ b/crates/evm-coder/CHANGELOG.md
@@ -1,4 +1,16 @@
-<!-- bureaucrate goes here -->
+# Change Log
+
+All notable changes to this project will be documented in this file.
+## [v0.1.2] 2022-08-19
+
+### Added
+
+ - Implementation `AbiWrite` for tuples.
+
+ ### Fixes
+
+ - Tuple generation for solidity.
+
 ## [v0.1.1] 2022-08-16
 
 ### Other changes
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -342,6 +342,16 @@
 				))
 			}
 		}
+		#[allow(non_snake_case)]
+		impl<$($ident),+> AbiWrite for &($($ident,)+)
+		where
+			$($ident: AbiWrite,)+
+		{
+			fn abi_write(&self, writer: &mut AbiWriter) {
+				let ($($ident,)+) = self;
+				$($ident.abi_write(writer);)+
+			}
+		}
 	};
 }
 
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -180,9 +180,16 @@
 			fn is_simple() -> bool {
 				false
 			}
+			#[allow(unused_assignments)]
 			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 				write!(writer, "{}(", tc.collect_tuple::<Self>())?;
+				let mut first = true;
 				$(
+					if !first {
+						write!(writer, ",")?;
+					} else {
+						first = false;
+					}
 					<$ident>::solidity_default(writer, tc)?;
 				)*
 				write!(writer, ")")
modifiedpallets/common/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/common/CHANGELOG.md
+++ b/pallets/common/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.1.7] - 2022-08-19
+
+### Added
+
+ - Add convert funtion from `CrossAccountId` to eth `uint256`.
+
+ 
 ## [0.1.6] - 2022-08-16
 
 ### Added
modifiedpallets/common/Cargo.tomldiffbeforeafterboth
--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-common"
-version = "0.1.6"
+version = "0.1.7"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,13 +16,11 @@
 
 //! The module contains a number of functions for converting and checking ethereum identifiers.
 
-use evm_coder::{types::*};
-pub use pallet_evm::account::CrossAccountId;
+use evm_coder::types::uint256;
+pub use pallet_evm::account::{Config, CrossAccountId};
 use sp_core::H160;
 use up_data_structs::CollectionId;
 
-use crate::Config;
-
 // 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
 // TODO: Unhardcode prefix
 const ETH_COLLECTION_PREFIX: [u8; 16] = [
@@ -52,6 +50,15 @@
 	address[0..16] == ETH_COLLECTION_PREFIX
 }
 
+/// Convert `CrossAccountId` to `uint256`.
+pub fn convert_cross_account_to_uint256<T: Config>(from: &T::CrossAccountId) -> uint256
+where
+	T::AccountId: AsRef<[u8; 32]>,
+{
+	let slice = from.as_sub().as_ref();
+	uint256::from_big_endian(slice)
+}
+
 /// Converts Substrate address to CrossAccountId
 pub fn convert_substrate_address_to_cross_account_id<T: Config>(
 	address: uint256,
modifiedpallets/evm-contract-helpers/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/evm-contract-helpers/CHANGELOG.md
+++ b/pallets/evm-contract-helpers/CHANGELOG.md
@@ -1,4 +1,24 @@
-<!-- bureaucrate goes here -->
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [v0.2.0] - 2022-08-19
+
+### Added
+
+ - Set arbitrary evm address as contract sponsor.
+ - Ability to remove current sponsor.
+
+### Removed
+ - Remove methods
+   + sponsoring_enabled
+   + toggle_sponsoring
+
+ ### Changed
+
+ - Change `toggle_sponsoring` to `self_sponsored_enable`.
+
+
 ## [v0.1.2] 2022-08-16
 
 ### Other changes
modifiedpallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-evm-contract-helpers"
-version = "0.1.2"
+version = "0.2.0"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,14 +16,16 @@
 
 use core::marker::PhantomData;
 use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*};
-use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
+use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm};
 use pallet_evm::{
 	ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,
 	account::CrossAccountId,
 };
 use sp_core::H160;
+use up_data_structs::SponsorshipState;
 use crate::{
 	AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringRateLimit, SponsoringModeT,
+	Sponsoring,
 };
 use frame_support::traits::Get;
 use up_sponsorship::SponsorshipHandler;
@@ -41,36 +43,135 @@
 }
 
 #[solidity_interface(name = "ContractHelpers")]
-impl<T: Config> ContractHelpers<T> {
+impl<T: Config> ContractHelpers<T>
+where
+	T::AccountId: AsRef<[u8; 32]>,
+{
+	/// Get contract ovner
+	///
+	/// @param Contract_address contract for which the owner is being determined.
+	/// @return Contract owner.
 	fn contract_owner(&self, contract_address: address) -> Result<address> {
 		Ok(<Owner<T>>::get(contract_address))
 	}
 
-	fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
-		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
-	}
-
-	/// Deprecated
-	fn toggle_sponsoring(
+	/// Set sponsor.
+	///
+	/// @param contract_address Contract for which a sponsor is being established.
+	/// @param sponsor User address who set as pending sponsor.
+	fn set_sponsor(
 		&mut self,
 		caller: caller,
 		contract_address: address,
-		enabled: bool,
+		sponsor: address,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
-		<Pallet<T>>::toggle_sponsoring(contract_address, enabled);
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::set_sponsor(
+			&T::CrossAccountId::from_eth(caller),
+			contract_address,
+			&T::CrossAccountId::from_eth(sponsor),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(())
+	}
+
+	/// Set contract as self sponsored.
+	///
+	/// @param contract_address Contract for which a self sponsoring is being enabled.
+	fn self_sponsored_enable(&mut self, caller: caller, contract_address: address) -> Result<void> {
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::self_sponsored_enable(&T::CrossAccountId::from_eth(caller), contract_address)
+			.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(())
+	}
+
+	/// Remove sponsor.
+	///
+	/// @param contract_address Contract for which a sponsorship is being removed.
+	fn remove_sponsor(&mut self, caller: caller, contract_address: address) -> Result<void> {
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::remove_sponsor(&T::CrossAccountId::from_eth(caller), contract_address)
+			.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(())
+	}
+
+	/// Confirm sponsorship.
+	///
+	/// @dev Caller must be same that set via [`set_sponsor`].
+	///
+	/// @param contract_address Сontract for which need to confirm sponsorship.
+	fn confirm_sponsorship(&mut self, caller: caller, contract_address: address) -> Result<void> {
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::confirm_sponsorship(&T::CrossAccountId::from_eth(caller), contract_address)
+			.map_err(dispatch_to_evm::<T>)?;
+
 		Ok(())
 	}
 
+	/// Get current sponsor.
+	///
+	/// @param contract_address The contract for which a sponsor is requested.
+	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+	fn get_sponsor(&self, contract_address: address) -> Result<(address, uint256)> {
+		let sponsor =
+			Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?;
+		let result: (address, uint256) = if sponsor.is_canonical_substrate() {
+			let sponsor = pallet_common::eth::convert_cross_account_to_uint256::<T>(&sponsor);
+			(Default::default(), sponsor)
+		} else {
+			let sponsor = *sponsor.as_eth();
+			(sponsor, Default::default())
+		};
+		Ok(result)
+	}
+
+	/// Check tat contract has confirmed sponsor.
+	///
+	/// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+	/// @return **true** if contract has confirmed sponsor.
+	fn has_sponsor(&self, contract_address: address) -> Result<bool> {
+		Ok(Pallet::<T>::get_sponsor(contract_address).is_some())
+	}
+
+	/// Check tat contract has pending sponsor.
+	///
+	/// @param contract_address The contract for which the presence of a pending sponsor is checked.
+	/// @return **true** if contract has pending sponsor.
+	fn has_pending_sponsor(&self, contract_address: address) -> Result<bool> {
+		Ok(match Sponsoring::<T>::get(contract_address) {
+			SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => false,
+			SponsorshipState::Unconfirmed(_) => true,
+		})
+	}
+
+	fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
+		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
+	}
+
 	fn set_sponsoring_mode(
 		&mut self,
 		caller: caller,
 		contract_address: address,
 		mode: uint8,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		let mode = SponsoringModeT::from_eth(mode).ok_or("unknown mode")?;
 		<Pallet<T>>::set_sponsoring_mode(contract_address, mode);
+
 		Ok(())
 	}
 
@@ -84,8 +185,12 @@
 		contract_address: address,
 		rate_limit: uint32,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());
+
 		Ok(())
 	}
 
@@ -110,8 +215,12 @@
 		contract_address: address,
 		enabled: bool,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		<Pallet<T>>::toggle_allowlist(contract_address, enabled);
+
 		Ok(())
 	}
 
@@ -122,14 +231,21 @@
 		user: address,
 		allowed: bool,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		<Pallet<T>>::toggle_allowed(contract_address, user, allowed);
+
 		Ok(())
 	}
 }
 
 pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);
-impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T> {
+impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T>
+where
+	T::AccountId: AsRef<[u8; 32]>,
+{
 	fn is_reserved(contract: &sp_core::H160) -> bool {
 		contract == &T::ContractAddress::get()
 	}
@@ -179,18 +295,26 @@
 	for HelpersContractSponsoring<T>
 {
 	fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
-		let mode = <Pallet<T>>::sponsoring_mode(call.0);
+		let (contract_address, _) = call;
+		let mode = <Pallet<T>>::sponsoring_mode(*contract_address);
 		if mode == SponsoringModeT::Disabled {
 			return None;
 		}
 
-		if mode == SponsoringModeT::Allowlisted && !<Pallet<T>>::allowed(call.0, *who.as_eth()) {
+		let sponsor = match <Pallet<T>>::get_sponsor(*contract_address) {
+			Some(sponsor) => sponsor,
+			None => return None,
+		};
+
+		if mode == SponsoringModeT::Allowlisted
+			&& !<Pallet<T>>::allowed(*contract_address, *who.as_eth())
+		{
 			return None;
 		}
 		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 
-		if let Some(last_tx_block) = <SponsorBasket<T>>::get(&call.0, who.as_eth()) {
-			let limit = <SponsoringRateLimit<T>>::get(&call.0);
+		if let Some(last_tx_block) = <SponsorBasket<T>>::get(contract_address, who.as_eth()) {
+			let limit = <SponsoringRateLimit<T>>::get(contract_address);
 
 			let timeout = last_tx_block + limit;
 			if block_number < timeout {
@@ -198,9 +322,8 @@
 			}
 		}
 
-		<SponsorBasket<T>>::insert(&call.0, who.as_eth(), block_number);
+		<SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);
 
-		let sponsor = T::CrossAccountId::from_eth(call.0);
 		Some(sponsor)
 	}
 }
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -25,9 +25,11 @@
 #[frame_support::pallet]
 pub mod pallet {
 	pub use super::*;
-	use evm_coder::execution::Result;
 	use frame_support::pallet_prelude::*;
+	use pallet_evm_coder_substrate::DispatchResult;
 	use sp_core::H160;
+	use pallet_evm::account::CrossAccountId;
+	use up_data_structs::SponsorshipState;
 
 	#[pallet::config]
 	pub trait Config:
@@ -39,14 +41,21 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
-		/// This method is only executable by owner
+		/// This method is only executable by owner.
 		NoPermission,
+
+		/// No pending sponsor for contract.
+		NoPendingSponsor,
 	}
 
 	#[pallet::pallet]
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
 
+	/// Store owner for contract.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - owner for contract.
 	#[pallet::storage]
 	pub(super) type Owner<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;
@@ -56,10 +65,33 @@
 	pub(super) type SelfSponsoring<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
 
+	/// Store for contract sponsorship state.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - sponsorship state.
+	#[pallet::storage]
+	pub(super) type Sponsoring<T: Config> = StorageMap<
+		Hasher = Twox64Concat,
+		Key = H160,
+		Value = SponsorshipState<T::CrossAccountId>,
+		QueryKind = ValueQuery,
+	>;
+
+	/// Store for sponsoring mode.
+	///
+	/// ### Usage
+	/// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - [`sponsoring mode`](SponsoringModeT).
 	#[pallet::storage]
 	pub(super) type SponsoringMode<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;
 
+	/// Storage for sponsoring rate limit in blocks.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - amount of sponsored blocks.
 	#[pallet::storage]
 	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<
 		Hasher = Twox128,
@@ -69,6 +101,11 @@
 		OnEmpty = T::DefaultSponsoringRateLimit,
 	>;
 
+	/// Storage for last sponsored block.
+	///
+	/// * **Key1** - contract address.
+	/// * **Key2** - sponsored user address.
+	/// * **Value** - last sponsored block number.
 	#[pallet::storage]
 	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<
 		Hasher1 = Twox128,
@@ -79,10 +116,25 @@
 		QueryKind = OptionQuery,
 	>;
 
+	/// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.
+	///
+	/// ### Usage
+	/// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.
 	#[pallet::storage]
 	pub(super) type AllowlistEnabled<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
 
+	/// Storage for users that allowed for sponsorship.
+	///
+	/// ### Usage
+	/// Prefer to delete record from storage if user no more allowed for sponsorship.
+	///
+	/// * **Key1** - contract address.
+	/// * **Key2** - user that allowed for sponsorship.
+	/// * **Value** - allowance for sponsorship.
 	#[pallet::storage]
 	pub(super) type Allowlist<T: Config> = StorageDoubleMap<
 		Hasher1 = Twox128,
@@ -94,6 +146,77 @@
 	>;
 
 	impl<T: Config> Pallet<T> {
+		/// Get contract owner.
+		pub fn contract_owner(contract: H160) -> H160 {
+			<Owner<T>>::get(contract)
+		}
+
+		/// Set `sponsor` for `contract`.
+		///
+		/// `sender` must be owner of contract.
+		pub fn set_sponsor(
+			sender: &T::CrossAccountId,
+			contract: H160,
+			sponsor: &T::CrossAccountId,
+		) -> DispatchResult {
+			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+			Sponsoring::<T>::insert(
+				contract,
+				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),
+			);
+			Ok(())
+		}
+
+		/// Set `contract` as self sponsored.
+		///
+		/// `sender` must be owner of contract.
+		pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+			Sponsoring::<T>::insert(
+				contract,
+				SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(
+					contract,
+				)),
+			);
+			Ok(())
+		}
+
+		/// Remove sponsor for `contract`.
+		///
+		/// `sender` must be owner of contract.
+		pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+			Sponsoring::<T>::remove(contract);
+			Ok(())
+		}
+
+		/// Confirm sponsorship.
+		///
+		/// `sender` must be same that set via [`set_sponsor`].
+		pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+			match Sponsoring::<T>::get(contract) {
+				SponsorshipState::Unconfirmed(sponsor) => {
+					ensure!(sponsor == *sender, Error::<T>::NoPermission);
+					Sponsoring::<T>::insert(
+						contract,
+						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),
+					);
+					Ok(())
+				}
+				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {
+					Err(Error::<T>::NoPendingSponsor.into())
+				}
+			}
+		}
+
+		/// Get sponsor.
+		pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {
+			match Sponsoring::<T>::get(contract) {
+				SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,
+				SponsorshipState::Confirmed(sponsor) => Some(sponsor),
+			}
+		}
+
 		pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {
 			<SponsoringMode<T>>::get(contract)
 				.or_else(|| {
@@ -101,6 +224,7 @@
 				})
 				.unwrap_or_default()
 		}
+
 		pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {
 			if mode == SponsoringModeT::Disabled {
 				<SponsoringMode<T>>::remove(contract);
@@ -110,17 +234,6 @@
 			<SelfSponsoring<T>>::remove(contract)
 		}
 
-		pub fn toggle_sponsoring(contract: H160, enabled: bool) {
-			Self::set_sponsoring_mode(
-				contract,
-				if enabled {
-					SponsoringModeT::Allowlisted
-				} else {
-					SponsoringModeT::Disabled
-				},
-			)
-		}
-
 		pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {
 			<SponsoringRateLimit<T>>::insert(contract, rate_limit);
 		}
@@ -137,8 +250,8 @@
 			<Allowlist<T>>::insert(contract, user, allowed);
 		}
 
-		pub fn ensure_owner(contract: H160, user: H160) -> Result<()> {
-			ensure!(<Owner<T>>::get(&contract) == user, "no permission");
+		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {
+			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);
 			Ok(())
 		}
 	}
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
+++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
@@ -3,6 +3,12 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
+// Anonymous struct
+struct Tuple0 {
+	address field_0;
+	uint256 field_1;
+}
+
 // Common stubs holder
 contract Dummy {
 	uint8 dummy;
@@ -21,8 +27,13 @@
 	}
 }
 
-// Selector: 7b4866f9
+// Selector: 6073d917
 contract ContractHelpers is Dummy, ERC165 {
+	// Get contract ovner
+	//
+	// @param Contract_address contract for which the owner is being determined.
+	// @return Contract owner.
+	//
 	// Selector: contractOwner(address) 5152b14c
 	function contractOwner(address contractAddress)
 		public
@@ -35,26 +46,111 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	// Selector: sponsoringEnabled(address) 6027dc61
-	function sponsoringEnabled(address contractAddress)
+	// Set sponsor.
+	//
+	// @param contract_address Contract for which a sponsor is being established.
+	// @param sponsor User address who set as pending sponsor.
+	//
+	// Selector: setSponsor(address,address) f01fba93
+	function setSponsor(address contractAddress, address sponsor) public {
+		require(false, stub_error);
+		contractAddress;
+		sponsor;
+		dummy = 0;
+	}
+
+	// Set contract as self sponsored.
+	//
+	// @param contract_address Contract for which a self sponsoring is being enabled.
+	//
+	// Selector: selfSponsoredEnable(address) 89f7d9ae
+	function selfSponsoredEnable(address contractAddress) public {
+		require(false, stub_error);
+		contractAddress;
+		dummy = 0;
+	}
+
+	// Remove sponsor.
+	//
+	// @param contract_address Contract for which a sponsorship is being removed.
+	//
+	// Selector: removeSponsor(address) ef784250
+	function removeSponsor(address contractAddress) public {
+		require(false, stub_error);
+		contractAddress;
+		dummy = 0;
+	}
+
+	// Confirm sponsorship.
+	//
+	// @dev Caller must be same that set via [`set_sponsor`].
+	//
+	// @param contract_address Сontract for which need to confirm sponsorship.
+	//
+	// Selector: confirmSponsorship(address) abc00001
+	function confirmSponsorship(address contractAddress) public {
+		require(false, stub_error);
+		contractAddress;
+		dummy = 0;
+	}
+
+	// Get current sponsor.
+	//
+	// @param contract_address The contract for which a sponsor is requested.
+	// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+	//
+	// Selector: getSponsor(address) 743fc745
+	function getSponsor(address contractAddress)
 		public
 		view
-		returns (bool)
+		returns (Tuple0 memory)
 	{
 		require(false, stub_error);
 		contractAddress;
 		dummy;
+		return Tuple0(0x0000000000000000000000000000000000000000, 0);
+	}
+
+	// Check tat contract has confirmed sponsor.
+	//
+	// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+	// @return **true** if contract has confirmed sponsor.
+	//
+	// Selector: hasSponsor(address) 97418603
+	function hasSponsor(address contractAddress) public view returns (bool) {
+		require(false, stub_error);
+		contractAddress;
+		dummy;
 		return false;
 	}
 
-	// Deprecated
+	// Check tat contract has pending sponsor.
+	//
+	// @param contract_address The contract for which the presence of a pending sponsor is checked.
+	// @return **true** if contract has pending sponsor.
 	//
-	// Selector: toggleSponsoring(address,bool) fcac6d86
-	function toggleSponsoring(address contractAddress, bool enabled) public {
+	// Selector: hasPendingSponsor(address) 39b9b242
+	function hasPendingSponsor(address contractAddress)
+		public
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		contractAddress;
+		dummy;
+		return false;
+	}
+
+	// Selector: sponsoringEnabled(address) 6027dc61
+	function sponsoringEnabled(address contractAddress)
+		public
+		view
+		returns (bool)
+	{
 		require(false, stub_error);
 		contractAddress;
-		enabled;
-		dummy = 0;
+		dummy;
+		return false;
 	}
 
 	// Selector: setSponsoringMode(address,uint8) fde8a560
modifiedruntime/tests/src/lib.rsdiffbeforeafterboth
--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -167,7 +167,7 @@
 }
 
 #[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, TypeInfo, MaxEncodedLen)]
-pub struct TestCrossAccountId(u64, sp_core::H160);
+pub struct TestCrossAccountId(u64, sp_core::H160, bool);
 impl CrossAccountId<u64> for TestCrossAccountId {
 	fn as_sub(&self) -> &u64 {
 		&self.0
@@ -178,17 +178,20 @@
 	fn from_sub(sub: u64) -> Self {
 		let mut eth = [0; 20];
 		eth[12..20].copy_from_slice(&sub.to_be_bytes());
-		Self(sub, sp_core::H160(eth))
+		Self(sub, sp_core::H160(eth), true)
 	}
 	fn from_eth(eth: sp_core::H160) -> Self {
 		let mut sub_raw = [0; 8];
 		sub_raw.copy_from_slice(&eth.0[0..8]);
 		let sub = u64::from_be_bytes(sub_raw);
-		Self(sub, eth)
+		Self(sub, eth, false)
 	}
 	fn conv_eq(&self, other: &Self) -> bool {
 		self.as_sub() == other.as_sub()
 	}
+	fn is_canonical_substrate(&self) -> bool {
+		self.2
+	}
 }
 
 impl Default for TestCrossAccountId {
modifiedtests/src/eth/api/ContractHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -3,6 +3,12 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
+// Anonymous struct
+struct Tuple0 {
+	address field_0;
+	uint256 field_1;
+}
+
 // Common stubs holder
 interface Dummy {
 
@@ -12,24 +18,85 @@
 	function supportsInterface(bytes4 interfaceID) external view returns (bool);
 }
 
-// Selector: 7b4866f9
+// Selector: 6073d917
 interface ContractHelpers is Dummy, ERC165 {
+	// Get contract ovner
+	//
+	// @param Contract_address contract for which the owner is being determined.
+	// @return Contract owner.
+	//
 	// Selector: contractOwner(address) 5152b14c
 	function contractOwner(address contractAddress)
 		external
 		view
 		returns (address);
 
+	// Set sponsor.
+	//
+	// @param contract_address Contract for which a sponsor is being established.
+	// @param sponsor User address who set as pending sponsor.
+	//
+	// Selector: setSponsor(address,address) f01fba93
+	function setSponsor(address contractAddress, address sponsor) external;
+
+	// Set contract as self sponsored.
+	//
+	// @param contract_address Contract for which a self sponsoring is being enabled.
+	//
+	// Selector: selfSponsoredEnable(address) 89f7d9ae
+	function selfSponsoredEnable(address contractAddress) external;
+
+	// Remove sponsor.
+	//
+	// @param contract_address Contract for which a sponsorship is being removed.
+	//
+	// Selector: removeSponsor(address) ef784250
+	function removeSponsor(address contractAddress) external;
+
+	// Confirm sponsorship.
+	//
+	// @dev Caller must be same that set via [`set_sponsor`].
+	//
+	// @param contract_address Сontract for which need to confirm sponsorship.
+	//
+	// Selector: confirmSponsorship(address) abc00001
+	function confirmSponsorship(address contractAddress) external;
+
+	// Get current sponsor.
+	//
+	// @param contract_address The contract for which a sponsor is requested.
+	// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+	//
+	// Selector: getSponsor(address) 743fc745
+	function getSponsor(address contractAddress)
+		external
+		view
+		returns (Tuple0 memory);
+
+	// Check tat contract has confirmed sponsor.
+	//
+	// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+	// @return **true** if contract has confirmed sponsor.
+	//
+	// Selector: hasSponsor(address) 97418603
+	function hasSponsor(address contractAddress) external view returns (bool);
+
+	// Check tat contract has pending sponsor.
+	//
+	// @param contract_address The contract for which the presence of a pending sponsor is checked.
+	// @return **true** if contract has pending sponsor.
+	//
+	// Selector: hasPendingSponsor(address) 39b9b242
+	function hasPendingSponsor(address contractAddress)
+		external
+		view
+		returns (bool);
+
 	// Selector: sponsoringEnabled(address) 6027dc61
 	function sponsoringEnabled(address contractAddress)
 		external
 		view
 		returns (bool);
-
-	// Deprecated
-	//
-	// Selector: toggleSponsoring(address,bool) fcac6d86
-	function toggleSponsoring(address contractAddress, bool enabled) external;
 
 	// Selector: setSponsoringMode(address,uint8) fde8a560
 	function setSponsoringMode(address contractAddress, uint8 mode) external;
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -1,7 +1,8 @@
-import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, setCollectionSponsorExpectSuccess} from '../util/helpers';
-import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents} from './util/helpers';
+import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers';
+import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub} from './util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
 import {expect} from 'chai';
+import {evmToAddress} from '@polkadot/util-crypto';
 
 describe('evm collection sponsoring', () => {
   itWeb3('sponsors mint transactions', async ({web3, privateKeyWrapper}) => {
@@ -36,4 +37,130 @@
       },
     ]);
   });
+
+  itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelpers = evmCollectionHelpers(web3, owner);
+    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
+    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+
+    await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
+    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+    const user = createEthAccount(web3);
+    const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+
+    const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
+    expect(oldPermissions.mintMode).to.be.false;
+    expect(oldPermissions.access).to.be.equal('Normal');
+
+    await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
+    await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
+    await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+
+    const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
+    expect(newPermissions.mintMode).to.be.true;
+    expect(newPermissions.access).to.be.equal('AllowList');
+
+    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
+    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+
+    {
+      const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await collectionEvm.methods.mintWithTokenURI(
+        user,
+        nextTokenId,
+        'Test URI',
+      ).send({from: user});
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address: collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: user,
+            tokenId: nextTokenId,
+          },
+        },
+      ]);
+
+      const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+      const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
+
+      expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
+      expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
+    }
+  });
+
+  itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelpers = evmCollectionHelpers(web3, owner);
+    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
+    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+    await sponsorCollection.methods.confirmCollectionSponsorship().send();
+    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+    const user = createEthAccount(web3);
+    await collectionEvm.methods.addCollectionAdmin(user).send();
+    
+    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
+    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+    
+  
+    const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);
+    const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+    result = await userCollectionEvm.methods.mintWithTokenURI(
+      user,
+      nextTokenId,
+      'Test URI',
+    ).send();
+
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: user,
+          tokenId: nextTokenId,
+        },
+      },
+    ]);
+    expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+  
+    const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+    expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
+    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
+    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
+  });
 });
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
before · tests/src/eth/contractSponsoring.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect} from 'chai';18import {19  contractHelpers,20  createEthAccountWithBalance,21  transferBalanceToEth,22  deployFlipper,23  itWeb3,24  SponsoringMode,25  createEthAccount,26  collectionIdToAddress,27  GAS_ARGS,28  normalizeEvents,29  subToEth,30  executeEthTxOnSub,31  evmCollectionHelpers,32  getCollectionAddressFromResult,33  evmCollection,34  ethBalanceViaSub,35} from './util/helpers';36import {37  addCollectionAdminExpectSuccess,38  createCollectionExpectSuccess,39  getDetailedCollectionInfo,40  transferBalanceTo,41} from '../util/helpers';42import nonFungibleAbi from './nonFungibleAbi.json';43import getBalance from '../substrate/get-balance';44import {evmToAddress} from '@polkadot/util-crypto';4546describe('Sponsoring EVM contracts', () => {47  itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {48    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);49    const flipper = await deployFlipper(web3, owner);50    const helpers = contractHelpers(web3, owner);51    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;52    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});53    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;54  });5556  itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {57    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);58    const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);59    const flipper = await deployFlipper(web3, owner);60    const helpers = contractHelpers(web3, owner);61    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;62    await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).send({from: notOwner})).to.rejected;63    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;64  });6566  itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {67    const alice = privateKeyWrapper('//Alice');6869    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);70    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);7172    const flipper = await deployFlipper(web3, owner);7374    const helpers = contractHelpers(web3, owner);7576    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;77    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});78    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});79    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;8081    await transferBalanceToEth(api, alice, flipper.options.address);8283    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);84    expect(originalFlipperBalance).to.be.not.equal('0');8586    await flipper.methods.flip().send({from: caller});87    expect(await flipper.methods.getValue().call()).to.be.true;8889    // Balance should be taken from flipper instead of caller90    const balanceAfter = await web3.eth.getBalance(flipper.options.address);91    expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);92  });9394  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {95    const alice = privateKeyWrapper('//Alice');9697    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);98    const caller = createEthAccount(web3);99100    const flipper = await deployFlipper(web3, owner);101102    const helpers = contractHelpers(web3, owner);103    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});104    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});105106    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;107    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});108    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});109    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;110111    await transferBalanceToEth(api, alice, flipper.options.address);112113    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);114    expect(originalFlipperBalance).to.be.not.equal('0');115116    await flipper.methods.flip().send({from: caller});117    expect(await flipper.methods.getValue().call()).to.be.true;118119    // Balance should be taken from flipper instead of caller120    const balanceAfter = await web3.eth.getBalance(flipper.options.address);121    expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);122  });123124  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {125    const alice = privateKeyWrapper('//Alice');126127    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);128    const caller = createEthAccount(web3);129130    const flipper = await deployFlipper(web3, owner);131132    const helpers = contractHelpers(web3, owner);133134    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;135    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});136    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});137    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;138139    await transferBalanceToEth(api, alice, flipper.options.address);140141    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);142    expect(originalFlipperBalance).to.be.not.equal('0');143144    await expect(flipper.methods.flip().send({from: caller})).to.be.rejectedWith(/InvalidTransaction::Payment/);145    expect(await flipper.methods.getValue().call()).to.be.false;146147    // Balance should be taken from flipper instead of caller148    const balanceAfter = await web3.eth.getBalance(flipper.options.address);149    expect(+balanceAfter).to.be.equals(+originalFlipperBalance);150  });151152  itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {153    const alice = privateKeyWrapper('//Alice');154155    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);156    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);157    const originalCallerBalance = await web3.eth.getBalance(caller);158159    const flipper = await deployFlipper(web3, owner);160161    const helpers = contractHelpers(web3, owner);162    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});163    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});164165    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;166    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});167    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});168    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;169170    await transferBalanceToEth(api, alice, flipper.options.address);171172    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);173    expect(originalFlipperBalance).to.be.not.equal('0');174175    await flipper.methods.flip().send({from: caller});176    expect(await flipper.methods.getValue().call()).to.be.true;177178    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);179  });180181  itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {182    const alice = privateKeyWrapper('//Alice');183184    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);185    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);186    const originalCallerBalance = await web3.eth.getBalance(caller);187188    const flipper = await deployFlipper(web3, owner);189190    const helpers = contractHelpers(web3, owner);191    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});192    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});193194    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;195    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});196    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});197    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;198199    await transferBalanceToEth(api, alice, flipper.options.address);200201    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);202    expect(originalFlipperBalance).to.be.not.equal('0');203204    await flipper.methods.flip().send({from: caller});205    expect(await flipper.methods.getValue().call()).to.be.true;206    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);207208    const newFlipperBalance = await web3.eth.getBalance(flipper.options.address);209    expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);210211    await flipper.methods.flip().send({from: caller});212    expect(await web3.eth.getBalance(flipper.options.address)).to.be.equal(newFlipperBalance);213    expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);214  });215216  // TODO: Find a way to calculate default rate limit217  itWeb3('Default rate limit equals 7200', async ({api, web3, privateKeyWrapper}) => {218    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);219    const flipper = await deployFlipper(web3, owner);220    const helpers = contractHelpers(web3, owner);221    expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');222  });223224  itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => {225    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);226    const collectionHelpers = evmCollectionHelpers(web3, owner);227    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();228    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);229    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);230    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);231    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});232    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;233    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;234    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;235    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));236    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');237238    await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});239    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;240    expect(collectionSub.sponsorship.isConfirmed).to.be.true;241    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));242243    const user = createEthAccount(web3);244    const nextTokenId = await collectionEvm.methods.nextTokenId().call();245    expect(nextTokenId).to.be.equal('1');246247    const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();248    expect(oldPermissions.mintMode).to.be.false;249    expect(oldPermissions.access).to.be.equal('Normal');250251    await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});252    await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});253    await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});254255    const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();256    expect(newPermissions.mintMode).to.be.true;257    expect(newPermissions.access).to.be.equal('AllowList');258259    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);260    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);261262    {263      const nextTokenId = await collectionEvm.methods.nextTokenId().call();264      expect(nextTokenId).to.be.equal('1');265      const result = await collectionEvm.methods.mintWithTokenURI(266        user,267        nextTokenId,268        'Test URI',269      ).send({from: user});270      const events = normalizeEvents(result.events);271272      expect(events).to.be.deep.equal([273        {274          address: collectionIdAddress,275          event: 'Transfer',276          args: {277            from: '0x0000000000000000000000000000000000000000',278            to: user,279            tokenId: nextTokenId,280          },281        },282      ]);283284      const ownerBalanceAfter = await ethBalanceViaSub(api, owner);285      const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);286287      expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');288      expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);289      expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;290    }291  });292293  itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {294    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);295    const collectionHelpers = evmCollectionHelpers(web3, owner);296    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();297    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);298    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);299    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);300    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();301    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;302    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;303    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;304    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));305    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');306    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);307    await sponsorCollection.methods.confirmCollectionSponsorship().send();308    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;309    expect(collectionSub.sponsorship.isConfirmed).to.be.true;310    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));311312    const user = createEthAccount(web3);313    await collectionEvm.methods.addCollectionAdmin(user).send();314    315    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);316    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);317    318  319    const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);320    const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();321    expect(nextTokenId).to.be.equal('1');322    result = await userCollectionEvm.methods.mintWithTokenURI(323      user,324      nextTokenId,325      'Test URI',326    ).send();327328    const events = normalizeEvents(result.events);329    const address = collectionIdToAddress(collectionId);330331    expect(events).to.be.deep.equal([332      {333        address,334        event: 'Transfer',335        args: {336          from: '0x0000000000000000000000000000000000000000',337          to: user,338          tokenId: nextTokenId,339        },340      },341    ]);342    expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');343  344    const ownerBalanceAfter = await ethBalanceViaSub(api, owner);345    expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);346    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);347    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;348  });349});
after · tests/src/eth/contractSponsoring.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect} from 'chai';18import {19  contractHelpers,20  createEthAccountWithBalance,21  transferBalanceToEth,22  deployFlipper,23  itWeb3,24  SponsoringMode,25  createEthAccount,26  ethBalanceViaSub,27} from './util/helpers';2829describe('Sponsoring EVM contracts', () => {30  itWeb3('Self sponsored can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {31    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);32    const flipper = await deployFlipper(web3, owner);33    const helpers = contractHelpers(web3, owner);34    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;35    await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;36    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;37  });3839  itWeb3('Self sponsored can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {40    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);41    const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);42    const flipper = await deployFlipper(web3, owner);43    const helpers = contractHelpers(web3, owner);44    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;45    await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission');46    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;47  });4849  itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {50    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);51    const flipper = await deployFlipper(web3, owner);52    const helpers = contractHelpers(web3, owner);53    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;54    await expect(helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner})).to.be.not.rejected;55    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;56  });5758  itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {59    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);60    const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);61    const flipper = await deployFlipper(web3, owner);62    const helpers = contractHelpers(web3, owner);63    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;64    await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).call({from: notOwner})).to.be.rejectedWith('NoPermission');65    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;66  });67  68  itWeb3('Sponsor can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {69    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);70    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);71    const flipper = await deployFlipper(web3, owner);72    const helpers = contractHelpers(web3, owner);73    expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;74    await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;75    expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true;76  });77  78  itWeb3('Sponsor can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {79    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);80    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);81    const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);82    const flipper = await deployFlipper(web3, owner);83    const helpers = contractHelpers(web3, owner);84    expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;85    await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).call({from: notOwner})).to.be.rejectedWith('NoPermission');86    expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;87  });8889  itWeb3('Sponsorship can be confirmed by the address that pending as sponsor', async ({api, web3, privateKeyWrapper}) => {90    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);91    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);92    const flipper = await deployFlipper(web3, owner);93    const helpers = contractHelpers(web3, owner);94    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;95    await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;96    await expect(helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor})).to.be.not.rejected;97    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;98  });99100  itWeb3('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({api, web3, privateKeyWrapper}) => {101    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);102    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);103    const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);104    const flipper = await deployFlipper(web3, owner);105    const helpers = contractHelpers(web3, owner);106    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;107    await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;108    await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPermission');109    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;110  });111112  itWeb3('Sponsorship can not be confirmed by the address that not set as sponsor', async ({api, web3, privateKeyWrapper}) => {113    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);114    const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);115    const flipper = await deployFlipper(web3, owner);116    const helpers = contractHelpers(web3, owner);117    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;118    await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPendingSponsor');119    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;120  });121122  itWeb3('Get self sponsored sponsor', async ({api, web3, privateKeyWrapper}) => {123    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);124    const flipper = await deployFlipper(web3, owner);125    const helpers = contractHelpers(web3, owner);126    await helpers.methods.selfSponsoredEnable(flipper.options.address).send();127    128    const result = await helpers.methods.getSponsor(flipper.options.address).call();129130    expect(result[0]).to.be.eq(flipper.options.address);131    expect(result[1]).to.be.eq('0');132  });133134  itWeb3('Get confirmed sponsor', async ({api, web3, privateKeyWrapper}) => {135    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);136    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);137    const flipper = await deployFlipper(web3, owner);138    const helpers = contractHelpers(web3, owner);139    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();140    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});141    142    const result = await helpers.methods.getSponsor(flipper.options.address).call();143144    expect(result[0]).to.be.eq(sponsor);145    expect(result[1]).to.be.eq('0');146  });147148  itWeb3('Sponsor can be removed by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {149    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);150    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);151    const flipper = await deployFlipper(web3, owner);152    const helpers = contractHelpers(web3, owner);153154    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;155    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();156    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});157    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;158    159    await helpers.methods.removeSponsor(flipper.options.address).send();160    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;161  });162163  itWeb3('Sponsor can not be removed by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {164    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);165    const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);166    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);167    const flipper = await deployFlipper(web3, owner);168    const helpers = contractHelpers(web3, owner);169170    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;171    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();172    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});173    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;174    175    await expect(helpers.methods.removeSponsor(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission');176    expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;177  });178179  itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {180    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);181    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);182    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);183184    const flipper = await deployFlipper(web3, owner);185186    const helpers = contractHelpers(web3, owner);187188    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();189    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});190191    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});192    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});193194    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);195    const callerBalanceBefore = await ethBalanceViaSub(api, caller);196197    await flipper.methods.flip().send({from: caller});198    expect(await flipper.methods.getValue().call()).to.be.true;199200    // Balance should be taken from sponsor instead of caller201    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);202    const callerBalanceAfter = await ethBalanceViaSub(api, caller);203    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;204    expect(callerBalanceAfter).to.be.eq(callerBalanceBefore);205  });206207  itWeb3('In generous mode, non-allowlisted user transaction will be self sponsored', async ({api, web3, privateKeyWrapper}) => {208    const alice = privateKeyWrapper('//Alice');209210    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);211    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);212213    const flipper = await deployFlipper(web3, owner);214215    const helpers = contractHelpers(web3, owner);216217    await helpers.methods.selfSponsoredEnable(flipper.options.address).send();218219    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});220    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});221222    await transferBalanceToEth(api, alice, flipper.options.address);223224    const contractBalanceBefore = await ethBalanceViaSub(api, flipper.options.address);225    const callerBalanceBefore = await ethBalanceViaSub(api, caller);226227    await flipper.methods.flip().send({from: caller});228    expect(await flipper.methods.getValue().call()).to.be.true;229230    // Balance should be taken from sponsor instead of caller231    const contractBalanceAfter = await ethBalanceViaSub(api, flipper.options.address);232    const callerBalanceAfter = await ethBalanceViaSub(api, caller);233    expect(contractBalanceAfter < contractBalanceBefore).to.be.true;234    expect(callerBalanceAfter).to.be.eq(callerBalanceBefore);235  });236237  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {238    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);239    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);240    const caller = createEthAccount(web3);241242    const flipper = await deployFlipper(web3, owner);243244    const helpers = contractHelpers(web3, owner);245    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});246    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});247248    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});249    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});250251    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();252    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});253254    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);255    expect(sponsorBalanceBefore).to.be.not.equal('0');256257    await flipper.methods.flip().send({from: caller});258    expect(await flipper.methods.getValue().call()).to.be.true;259260    // Balance should be taken from flipper instead of caller261    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);262    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;263  });264265  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {266    const alice = privateKeyWrapper('//Alice');267268    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);269    const caller = createEthAccount(web3);270271    const flipper = await deployFlipper(web3, owner);272273    const helpers = contractHelpers(web3, owner);274275    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});276    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});277278    await transferBalanceToEth(api, alice, flipper.options.address);279280    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);281    expect(originalFlipperBalance).to.be.not.equal('0');282283    await expect(flipper.methods.flip().send({from: caller})).to.be.rejectedWith(/InvalidTransaction::Payment/);284    expect(await flipper.methods.getValue().call()).to.be.false;285286    // Balance should be taken from flipper instead of caller287    const balanceAfter = await web3.eth.getBalance(flipper.options.address);288    expect(+balanceAfter).to.be.equals(+originalFlipperBalance);289  });290291  itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {292    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);293    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);294    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);295296    const flipper = await deployFlipper(web3, owner);297298    const helpers = contractHelpers(web3, owner);299    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});300    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});301302    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});303    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});304305    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();306    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});307308    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);309    const callerBalanceBefore = await ethBalanceViaSub(api, caller);310311    await flipper.methods.flip().send({from: caller});312    expect(await flipper.methods.getValue().call()).to.be.true;313314    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);315    const callerBalanceAfter = await ethBalanceViaSub(api, caller);316    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;317    expect(callerBalanceAfter).to.be.equals(callerBalanceBefore);318  });319320  itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {321    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);322    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);323    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);324    const originalCallerBalance = await web3.eth.getBalance(caller);325326    const flipper = await deployFlipper(web3, owner);327328    const helpers = contractHelpers(web3, owner);329    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});330    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});331332    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});333    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});334335    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();336    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});337338    const originalFlipperBalance = await web3.eth.getBalance(sponsor);339    expect(originalFlipperBalance).to.be.not.equal('0');340341    await flipper.methods.flip().send({from: caller});342    expect(await flipper.methods.getValue().call()).to.be.true;343    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);344345    const newFlipperBalance = await web3.eth.getBalance(sponsor);346    expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);347348    await flipper.methods.flip().send({from: caller});349    expect(await web3.eth.getBalance(sponsor)).to.be.equal(newFlipperBalance);350    expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);351  });352353  // TODO: Find a way to calculate default rate limit354  itWeb3('Default rate limit equals 7200', async ({api, web3, privateKeyWrapper}) => {355    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);356    const flipper = await deployFlipper(web3, owner);357    const helpers = contractHelpers(web3, owner);358    expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');359  });360});
modifiedtests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth
--- a/tests/src/eth/marketplace/marketplace.test.ts
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -39,6 +39,7 @@
 describe('Matcher contract usage', () => {
   itWeb3('With UNQ', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
@@ -48,7 +49,9 @@
     const helpers = contractHelpers(web3, matcherOwner);
     await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner});
     await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
-    await transferBalanceToEth(api, alice, matcher.options.address);
+    
+    await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner});
+    await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor});
 
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1});
@@ -100,6 +103,7 @@
 
   itWeb3('With escrow', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const escrow = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
@@ -111,7 +115,9 @@
     const helpers = contractHelpers(web3, matcherOwner);
     await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner});
     await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
-    await transferBalanceToEth(api, alice, matcher.options.address);
+    
+    await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner});
+    await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor});
 
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1});
modifiedtests/src/eth/sponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/sponsoring.test.ts
+++ b/tests/src/eth/sponsoring.test.ts
@@ -15,13 +15,12 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {expect} from 'chai';
-import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode, transferBalanceToEth} from './util/helpers';
+import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode} from './util/helpers';
 
 describe('EVM sponsoring', () => {
   itWeb3('Fee is deducted from contract if sponsoring is enabled', async ({api, web3, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = createEthAccount(web3);
     const originalCallerBalance = await web3.eth.getBalance(caller);
     expect(originalCallerBalance).to.be.equal('0');
@@ -31,28 +30,29 @@
     const helpers = contractHelpers(web3, owner);
     await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});
     await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
+    
+    await helpers.methods.setSponsor(flipper.options.address, sponsor).send({from: owner});
+    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
 
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
     await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
     await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
 
-    await transferBalanceToEth(api, alice, flipper.options.address);
-
-    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);
-    expect(originalFlipperBalance).to.be.not.equal('0');
+    const originalSponsorBalance = await web3.eth.getBalance(sponsor);
+    expect(originalSponsorBalance).to.be.not.equal('0');
 
     await flipper.methods.flip().send({from: caller});
     expect(await flipper.methods.getValue().call()).to.be.true;
 
     // Balance should be taken from flipper instead of caller
     expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);
-    expect(await web3.eth.getBalance(flipper.options.address)).to.be.not.equals(originalFlipperBalance);
+    expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance);
   });
+
   itWeb3('...but this doesn\'t applies to payable value', async ({api, web3, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const originalCallerBalance = await web3.eth.getBalance(caller);
     expect(originalCallerBalance).to.be.not.equal('0');
@@ -68,16 +68,17 @@
     await helpers.methods.setSponsoringRateLimit(collector.options.address, 0).send({from: owner});
     expect(await helpers.methods.sponsoringEnabled(collector.options.address).call()).to.be.true;
 
-    await transferBalanceToEth(api, alice, collector.options.address);
+    await helpers.methods.setSponsor(collector.options.address, sponsor).send({from: owner});
+    await helpers.methods.confirmSponsorship(collector.options.address).send({from: sponsor});
 
-    const originalCollectorBalance = await web3.eth.getBalance(collector.options.address);
-    expect(originalCollectorBalance).to.be.not.equal('0');
+    const originalSponsorBalance = await web3.eth.getBalance(sponsor);
+    expect(originalSponsorBalance).to.be.not.equal('0');
 
     await collector.methods.giveMoney().send({from: caller, value: '10000'});
 
     // Balance will be taken from both caller (value) and from collector (fee)
     expect(await web3.eth.getBalance(caller)).to.be.equals((BigInt(originalCallerBalance) - 10000n).toString());
-    expect(await web3.eth.getBalance(collector.options.address)).to.be.not.equals(originalCollectorBalance);
+    expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance);
     expect(await collector.methods.getCollected().call()).to.be.equal('10000');
   });
 });
modifiedtests/src/eth/util/contractHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/util/contractHelpersAbi.json
+++ b/tests/src/eth/util/contractHelpersAbi.json
@@ -34,6 +34,19 @@
         "type": "address"
       }
     ],
+    "name": "confirmSponsorship",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
     "name": "contractOwner",
     "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
     "stateMutability": "view",
@@ -47,6 +60,29 @@
         "type": "address"
       }
     ],
+    "name": "getSponsor",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple0",
+        "name": "",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
     "name": "getSponsoringRateLimit",
     "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
     "stateMutability": "view",
@@ -58,6 +94,72 @@
         "internalType": "address",
         "name": "contractAddress",
         "type": "address"
+      }
+    ],
+    "name": "hasPendingSponsor",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "hasSponsor",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "removeSponsor",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "selfSponsoredEnable",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "address", "name": "sponsor", "type": "address" }
+    ],
+    "name": "setSponsor",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
       },
       { "internalType": "uint8", "name": "mode", "type": "uint8" }
     ],
@@ -140,20 +242,6 @@
       { "internalType": "bool", "name": "enabled", "type": "bool" }
     ],
     "name": "toggleAllowlist",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "address",
-        "name": "contractAddress",
-        "type": "address"
-      },
-      { "internalType": "bool", "name": "enabled", "type": "bool" }
-    ],
-    "name": "toggleSponsoring",
     "outputs": [],
     "stateMutability": "nonpayable",
     "type": "function"