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

difftreelog

merge develop into test/playground-migration

rkv2022-09-09parents: #16ab55f #4f3f998.patch.diff
in: master

79 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -113,6 +113,20 @@
 checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8"
 
 [[package]]
+name = "app-promotion-rpc"
+version = "0.1.0"
+dependencies = [
+ "pallet-common",
+ "pallet-evm",
+ "parity-scale-codec 3.1.5",
+ "sp-api",
+ "sp-core",
+ "sp-runtime",
+ "sp-std",
+ "up-data-structs",
+]
+
+[[package]]
 name = "approx"
 version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5127,6 +5141,7 @@
 name = "opal-runtime"
 version = "0.9.27"
 dependencies = [
+ "app-promotion-rpc",
  "cumulus-pallet-aura-ext",
  "cumulus-pallet-dmp-queue",
  "cumulus-pallet-parachain-system",
@@ -5150,6 +5165,7 @@
  "hex-literal",
  "log",
  "orml-vesting",
+ "pallet-app-promotion",
  "pallet-aura",
  "pallet-balances",
  "pallet-base-fee",
@@ -5296,6 +5312,31 @@
 ]
 
 [[package]]
+name = "pallet-app-promotion"
+version = "0.1.0"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-common",
+ "pallet-evm",
+ "pallet-evm-contract-helpers",
+ "pallet-evm-migration",
+ "pallet-randomness-collective-flip",
+ "pallet-timestamp",
+ "pallet-unique",
+ "parity-scale-codec 3.1.5",
+ "scale-info",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "up-data-structs",
+]
+
+[[package]]
 name = "pallet-aura"
 version = "4.0.0-dev"
 source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05"
@@ -5694,6 +5735,7 @@
 name = "pallet-evm-contract-helpers"
 version = "0.2.0"
 dependencies = [
+ "ethereum",
  "evm-coder",
  "fp-evm-mapping",
  "frame-support",
@@ -6432,7 +6474,7 @@
 
 [[package]]
 name = "pallet-unique"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -8332,6 +8374,7 @@
 name = "quartz-runtime"
 version = "0.9.27"
 dependencies = [
+ "app-promotion-rpc",
  "cumulus-pallet-aura-ext",
  "cumulus-pallet-dmp-queue",
  "cumulus-pallet-parachain-system",
@@ -8355,6 +8398,7 @@
  "hex-literal",
  "log",
  "orml-vesting",
+ "pallet-app-promotion",
  "pallet-aura",
  "pallet-balances",
  "pallet-base-fee",
@@ -12103,9 +12147,10 @@
 
 [[package]]
 name = "uc-rpc"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "anyhow",
+ "app-promotion-rpc",
  "jsonrpsee",
  "pallet-common",
  "pallet-evm",
@@ -12184,6 +12229,7 @@
 name = "unique-node"
 version = "0.9.27"
 dependencies = [
+ "app-promotion-rpc",
  "clap",
  "cumulus-client-cli",
  "cumulus-client-collator",
@@ -12272,6 +12318,7 @@
 name = "unique-rpc"
 version = "0.1.1"
 dependencies = [
+ "app-promotion-rpc",
  "fc-db",
  "fc-mapping-sync",
  "fc-rpc",
@@ -12321,6 +12368,7 @@
 name = "unique-runtime"
 version = "0.9.27"
 dependencies = [
+ "app-promotion-rpc",
  "cumulus-pallet-aura-ext",
  "cumulus-pallet-dmp-queue",
  "cumulus-pallet-parachain-system",
@@ -12344,6 +12392,7 @@
  "hex-literal",
  "log",
  "orml-vesting",
+ "pallet-app-promotion",
  "pallet-aura",
  "pallet-balances",
  "pallet-base-fee",
modifiedMakefilediffbeforeafterboth
--- a/Makefile
+++ b/Makefile
@@ -128,5 +128,9 @@
 bench-rmrk-equip:
 	make _bench PALLET=proxy-rmrk-equip
 
+.PHONY: bench-app-promotion
+bench-app-promotion:
+	make _bench PALLET=app-promotion PALLET_DIR=app-promotion
+	
 .PHONY: bench
 bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible bench-scheduler bench-rmrk-core bench-rmrk-equip
modifiedclient/rpc/CHANGELOG.mddiffbeforeafterboth
--- a/client/rpc/CHANGELOG.md
+++ b/client/rpc/CHANGELOG.md
@@ -3,15 +3,21 @@
 All notable changes to this project will be documented in this file.
 
 <!-- bureaucrate goes here -->
+
+## [v0.1.4] 2022-09-08
+
+### Added
+-  Support RPC for `AppPromotion` pallet. 
+
 ## [v0.1.3] 2022-08-16
 
 ### Other changes
 
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+-   build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
 
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+-   build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
 
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+-   build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
 
 ## [0.1.2] - 2022-08-12
 
modifiedclient/rpc/Cargo.tomldiffbeforeafterboth
--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "uc-rpc"
-version = "0.1.3"
+version = "0.1.4"
 license = "GPLv3"
 edition = "2021"
 
@@ -8,6 +8,7 @@
 pallet-common = { default-features = false, path = '../../pallets/common' }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 up-rpc = { path = "../../primitives/rpc" }
+app-promotion-rpc = { path = "../../primitives/app_promotion_rpc"}
 rmrk-rpc = { path = "../../primitives/rmrk-rpc" }
 codec = { package = "parity-scale-codec", version = "3.1.2" }
 jsonrpsee = { version = "0.14.0", features = ["server", "macros"] }
modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -23,6 +23,7 @@
 	proc_macros::rpc,
 };
 use anyhow::anyhow;
+use sp_runtime::traits::{AtLeast32BitUnsigned, Member};
 use up_data_structs::{
 	RpcCollection, CollectionId, CollectionStats, CollectionLimits, TokenId, Property,
 	PropertyKeyPermission, TokenData, TokenChild,
@@ -30,6 +31,7 @@
 use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};
 use sp_blockchain::HeaderBackend;
 use up_rpc::UniqueApi as UniqueRuntimeApi;
+use app_promotion_rpc::AppPromotionApi as AppPromotionRuntimeApi;
 
 // RMRK
 use rmrk_rpc::RmrkApi as RmrkRuntimeApi;
@@ -37,6 +39,7 @@
 	RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkResourceId,
 };
 
+pub use app_promotion_unique_rpc::AppPromotionApiServer;
 pub use rmrk_unique_rpc::RmrkApiServer;
 
 #[rpc(server)]
@@ -245,6 +248,46 @@
 	) -> Result<Option<String>>;
 }
 
+mod app_promotion_unique_rpc {
+	use super::*;
+
+	#[rpc(server)]
+	#[async_trait]
+	pub trait AppPromotionApi<BlockHash, BlockNumber, CrossAccountId, AccountId> {
+		/// Returns the total amount of staked tokens.
+		#[method(name = "appPromotion_totalStaked")]
+		fn total_staked(
+			&self,
+			staker: Option<CrossAccountId>,
+			at: Option<BlockHash>,
+		) -> Result<String>;
+
+		///Returns the total amount of staked tokens per block when staked.
+		#[method(name = "appPromotion_totalStakedPerBlock")]
+		fn total_staked_per_block(
+			&self,
+			staker: CrossAccountId,
+			at: Option<BlockHash>,
+		) -> Result<Vec<(BlockNumber, String)>>;
+
+		/// Returns the total amount of tokens pending withdrawal from staking.
+		#[method(name = "appPromotion_pendingUnstake")]
+		fn pending_unstake(
+			&self,
+			staker: Option<CrossAccountId>,
+			at: Option<BlockHash>,
+		) -> Result<String>;
+
+		/// Returns the total amount of tokens pending withdrawal from staking per block.
+		#[method(name = "appPromotion_pendingUnstakePerBlock")]
+		fn pending_unstake_per_block(
+			&self,
+			staker: CrossAccountId,
+			at: Option<BlockHash>,
+		) -> Result<Vec<(BlockNumber, String)>>;
+	}
+}
+
 mod rmrk_unique_rpc {
 	use super::*;
 
@@ -367,34 +410,28 @@
 	}
 }
 
-pub struct Unique<C, P> {
-	client: Arc<C>,
-	_marker: std::marker::PhantomData<P>,
-}
+macro_rules! define_struct_for_server_api {
+	($name:ident) => {
+		pub struct $name<C, P> {
+			client: Arc<C>,
+			_marker: std::marker::PhantomData<P>,
+		}
 
-impl<C, P> Unique<C, P> {
-	pub fn new(client: Arc<C>) -> Self {
-		Self {
-			client,
-			_marker: Default::default(),
+		impl<C, P> $name<C, P> {
+			pub fn new(client: Arc<C>) -> Self {
+				Self {
+					client,
+					_marker: Default::default(),
+				}
+			}
 		}
-	}
+	};
 }
 
-pub struct Rmrk<C, P> {
-	client: Arc<C>,
-	_marker: std::marker::PhantomData<P>,
-}
+define_struct_for_server_api!(Unique);
+define_struct_for_server_api!(AppPromotion);
+define_struct_for_server_api!(Rmrk);
 
-impl<C, P> Rmrk<C, P> {
-	pub fn new(client: Arc<C>) -> Self {
-		Self {
-			client,
-			_marker: Default::default(),
-		}
-	}
-}
-
 macro_rules! pass_method {
 	(
 		$method_name:ident(
@@ -440,6 +477,12 @@
 	};
 }
 
+macro_rules! app_promotion_api {
+	() => {
+		dyn AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>
+	};
+}
+
 macro_rules! rmrk_api {
 	() => {
 		dyn RmrkRuntimeApi<Block, AccountId, CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme>
@@ -522,6 +565,35 @@
 	pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
 }
 
+impl<C, Block, BlockNumber, CrossAccountId, AccountId>
+	app_promotion_unique_rpc::AppPromotionApiServer<
+		<Block as BlockT>::Hash,
+		BlockNumber,
+		CrossAccountId,
+		AccountId,
+	> for AppPromotion<C, Block>
+where
+	Block: BlockT,
+	BlockNumber: Decode + Member + AtLeast32BitUnsigned,
+	AccountId: Decode,
+	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
+	C::Api: AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>,
+	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,
+{
+	pass_method!(total_staked(staker: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);
+	pass_method!(total_staked_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> =>
+		|v| v
+		.into_iter()
+		.map(|(b, a)| (b, a.to_string()))
+		.collect::<Vec<_>>(), app_promotion_api);
+	pass_method!(pending_unstake(staker: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);
+	pass_method!(pending_unstake_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, String)> =>
+		|v| v
+		.into_iter()
+		.map(|(b, a)| (b, a.to_string()))
+		.collect::<Vec<_>>(), app_promotion_api);
+}
+
 #[allow(deprecated)]
 impl<
 		C,
addeddoc/separate_rpc.mddiffbeforeafterboth
--- /dev/null
+++ b/doc/separate_rpc.md
@@ -0,0 +1,123 @@
+1. Create, in the `primitives` folder, a crate with a trait for RPC generation.
+    ```rust
+    sp_api::decl_runtime_apis! {
+        #[api_version(2)]
+        pub trait ModuleNameApi<CrossAccountId> 
+        where
+            CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,
+        {
+            fn method_name(user: Option<CrossAccountId>) -> Result<u128, DispatchError>;
+        }
+    }
+    ```
+
+2. client/rpc/src/lib.rs
+    * Add a trait with the required methods. Mark it with `#[rpc(server)]` and `#[async_trait]` directives.
+        ```rust
+        #[rpc(server)]
+        #[async_trait]
+        pub trait ModuleNameApi<BlockHash, CrossAccountId> {
+            #[method(name = "moduleName_methodName")]
+            fn method_name(&self, user: Option<CrossAccountId>, at: Option<BlockHash>)
+                -> Result<String>;
+        }
+        ```
+    * Don't forget to write the correct method identifier in the form `moduleName_methodName`.
+    * Add a structure for which the server API interface will be implemented.
+        ```rust
+        define_struct_for_server_api!(ModuleName);
+        ```
+    * Define a macro to be used in the implementation of the server API interface.
+        ```rust
+        macro_rules! module_api {
+            () => {
+                dyn ModuleNameRuntimeApi<BlockHash, CrossAccountId>
+            };
+        }
+        ```
+    * Implement a server API interface.
+        ```rust
+        impl<C, Block, CrossAccountId> 
+        ModuleNameApiServer<<Block as BlockT>::Hash, CrossAccountId> for ModuleName<C, Block>
+        where
+            Block: BlockT,
+            C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
+            C::Api: AppPromotionRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>,
+            CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,
+        {
+            pass_method!(method_name(user: Option<CrossAccountId>) -> String => |v| v.to_string(), app_promotion_api);
+        }
+        ```
+
+3. runtime/common/runtime_apis.rs
+    * Implement the `ModuleNameApi` interface for `Runtime`. Optionally, you can mark a feature flag to disable the functionality.
+        ```rust
+        impl MethodApi<Block, BlockNumber, CrossAccountId, AccountId> for Runtime {
+                fn method_name(user: Option<CrossAccountId>) -> Result<u128, DispatchError> {
+                    #[cfg(not(feature = "module"))]
+                    return unsupported!();
+
+                    #[cfg(feature = "module")]
+                    return Ok(0);
+                }
+            }
+        ```
+
+4. node/cli/src/service.rs
+    * Set the `MethodApi<Block, Runtime::CrossAccountId>` bound in the `start_node_impl`, `start_node`, `start_dev_node` methods.
+
+5. node/rpc/src/lib.rs
+    * Add `MethodApi<Block, Runtime::CrossAccountId>` bound to `create_full` method.
+    * Enable RPC in the `create_full` method by adding `io.merge(ModuleName::new(client.clone()).into_rpc())?;`
+
+6. Add a new crate (see point 1) into dependencies.
+    * client/rpc/Cargo.toml
+    * node/rpc/Cargo.toml
+    * runtime/opal/Cargo.toml
+    * runtime/quartz/Cargo.toml
+    * runtime/unique/Cargo.toml
+
+7. Create tests/src/interfaces/ModuleName/definitions.ts and describe the necessary methods in it.
+    ```ts
+    type RpcParam = {
+        name: string;
+        type: string;
+        isOptional?: true;
+        };
+
+        const CROSS_ACCOUNT_ID_TYPE = 'PalletEvmAccountBasicCrossAccountIdRepr';
+
+        const fun = (description: string, params: RpcParam[], type: string) => ({
+        description,
+        params: [...params, atParam],
+        type,
+        });
+
+        export default {
+            types: {},
+            rpc: {
+                methodName: fun(
+                'Documentation for method',
+                [{name: 'user', type: CROSS_ACCOUNT_ID_TYPE, isOptional: true}],
+                'u128',
+                ),
+            },
+        };
+    ```
+
+8. Describe definitions from paragraph 7 in tests/src/interfaces/definitions.ts.
+    ```ts
+    export {default as ModuleName} from './module/definitions';
+    ```
+
+9. tests/src/substrate/substrate-api.ts
+    * Set the RPC interface in the `defaultApiOptions` function, add an entry in the `rpc` parameter
+        ```ts
+        module: defs.module.rpc,
+        ```
+
+10. tests/src/util/playgrounds/unique.dev.ts
+    * Specify RPC interface in `connect` function, add entry in `rpc` parameter
+        ```ts
+        module: defs.module.rpc,
+        ```
\ No newline at end of file
modifiednode/cli/Cargo.tomldiffbeforeafterboth
--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -318,6 +318,7 @@
 pallet-ethereum = { git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" }
 
 unique-rpc = { default-features = false, path = "../rpc" }
+app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false}
 rmrk-rpc = { path = "../../primitives/rmrk-rpc" }
 
 [features]
modifiednode/cli/src/service.rsdiffbeforeafterboth
--- a/node/cli/src/service.rs
+++ b/node/cli/src/service.rs
@@ -63,7 +63,9 @@
 use fc_rpc_core::types::FilterPool;
 use fc_mapping_sync::{MappingSyncWorker, SyncStrategy};
 
-use up_common::types::opaque::{AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block};
+use up_common::types::opaque::{
+	AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block, BlockNumber,
+};
 
 // RMRK
 use up_data_structs::{
@@ -362,6 +364,7 @@
 		+ pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance>
 		+ sp_api::ApiExt<Block, StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>
 		+ up_rpc::UniqueApi<Block, Runtime::CrossAccountId, AccountId>
+		+ app_promotion_rpc::AppPromotionApi<Block, BlockNumber, Runtime::CrossAccountId, AccountId>
 		+ rmrk_rpc::RmrkApi<
 			Block,
 			AccountId,
@@ -663,6 +666,7 @@
 		+ pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance>
 		+ sp_api::ApiExt<Block, StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>
 		+ up_rpc::UniqueApi<Block, Runtime::CrossAccountId, AccountId>
+		+ app_promotion_rpc::AppPromotionApi<Block, BlockNumber, Runtime::CrossAccountId, AccountId>
 		+ rmrk_rpc::RmrkApi<
 			Block,
 			AccountId,
@@ -807,6 +811,7 @@
 		+ pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance>
 		+ sp_api::ApiExt<Block, StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>
 		+ up_rpc::UniqueApi<Block, Runtime::CrossAccountId, AccountId>
+		+ app_promotion_rpc::AppPromotionApi<Block, BlockNumber, Runtime::CrossAccountId, AccountId>
 		+ rmrk_rpc::RmrkApi<
 			Block,
 			AccountId,
modifiednode/rpc/Cargo.tomldiffbeforeafterboth
--- a/node/rpc/Cargo.toml
+++ b/node/rpc/Cargo.toml
@@ -53,6 +53,7 @@
 pallet-unique = { path = "../../pallets/unique" }
 uc-rpc = { path = "../../client/rpc" }
 up-rpc = { path = "../../primitives/rpc" }
+app-promotion-rpc = { path = "../../primitives/app_promotion_rpc"}
 rmrk-rpc = { path = "../../primitives/rmrk-rpc" }
 up-data-structs = { default-features = false, path = "../../primitives/data-structs" }
 
modifiednode/rpc/src/lib.rsdiffbeforeafterboth
--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -145,6 +145,12 @@
 	C::Api: fp_rpc::EthereumRuntimeRPCApi<Block>,
 	C::Api: fp_rpc::ConvertTransactionRuntimeApi<Block>,
 	C::Api: up_rpc::UniqueApi<Block, <R as RuntimeInstance>::CrossAccountId, AccountId>,
+	C::Api: app_promotion_rpc::AppPromotionApi<
+		Block,
+		BlockNumber,
+		<R as RuntimeInstance>::CrossAccountId,
+		AccountId,
+	>,
 	C::Api: rmrk_rpc::RmrkApi<
 		Block,
 		AccountId,
@@ -169,6 +175,7 @@
 		EthPubSubApiServer, EthSigner, Net, NetApiServer, Web3, Web3ApiServer,
 	};
 	use uc_rpc::{UniqueApiServer, Unique};
+	use uc_rpc::{AppPromotionApiServer, AppPromotion};
 
 	#[cfg(not(feature = "unique-runtime"))]
 	use uc_rpc::{RmrkApiServer, Rmrk};
@@ -227,6 +234,9 @@
 
 	io.merge(Unique::new(client.clone()).into_rpc())?;
 
+	#[cfg(not(any(feature = "unique-runtime", feature = "quartz-runtime")))]
+	io.merge(AppPromotion::new(client.clone()).into_rpc())?;
+
 	#[cfg(not(feature = "unique-runtime"))]
 	io.merge(Rmrk::new(client.clone()).into_rpc())?;
 
addedpallets/app-promotion/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/Cargo.toml
@@ -0,0 +1,73 @@
+################################################################################
+# Package
+
+[package]
+authors = ['Unique Network <support@uniquenetwork.io>']
+description = 'Unique App Promotion Pallet'
+edition = '2021'
+homepage = 'https://unique.network'
+license = 'GPLv3'
+name = 'pallet-app-promotion'
+repository = 'https://github.com/UniqueNetwork/unique-chain'
+version = '0.1.0'
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
+
+[features]
+default = ['std']
+runtime-benchmarks = [
+    'frame-benchmarking',
+    'frame-support/runtime-benchmarks',
+    'frame-system/runtime-benchmarks',
+    # 'pallet-unique/runtime-benchmarks',
+]
+std = [
+    'codec/std',
+    'frame-benchmarking/std',
+    'frame-support/std',
+    'frame-system/std',
+    'pallet-balances/std',
+    'pallet-timestamp/std',
+    'pallet-randomness-collective-flip/std',
+    'pallet-evm/std',
+    'sp-io/std',
+    'sp-std/std',
+    'sp-runtime/std',
+    'sp-core/std',
+    'serde/std',
+
+]
+[dependencies]
+scale-info = { version = "2.0.1", default-features = false, features = [
+    "derive",
+] }
+################################################################################
+# Substrate Dependencies
+
+codec = { default-features = false, features = ['derive'], package = 'parity-scale-codec', version = '3.1.2' }
+frame-benchmarking = {default-features = false, optional = true,  git = "https://github.com/paritytech/substrate",  branch = "polkadot-v0.9.27" }
+frame-support = { default-features = false,  git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+frame-system ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+pallet-balances ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+pallet-timestamp ={ default-features = false,  git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+pallet-randomness-collective-flip ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+pallet-evm ={ default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" }
+sp-std ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+sp-io ={ default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+serde = { default-features = false, features = ['derive'], version = '1.0.130' }
+
+################################################################################
+# local dependencies
+
+up-data-structs ={ default-features = false, path = "../../primitives/data-structs" }
+pallet-common ={ default-features = false, path = "../common" }
+pallet-unique ={ default-features = false, path = "../unique" }
+pallet-evm-contract-helpers ={ default-features = false, path = "../evm-contract-helpers" }
+
+[dev-dependencies]
+pallet-evm-migration ={ default-features = false, path = "../evm-migration" }
+
+################################################################################
addedpallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/benchmarking.rs
@@ -0,0 +1,162 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+use crate::Pallet as PromototionPallet;
+
+use sp_runtime::traits::Bounded;
+use sp_std::vec;
+
+use frame_benchmarking::{benchmarks, account};
+use frame_support::traits::OnInitialize;
+use frame_system::{Origin, RawOrigin};
+use pallet_unique::benchmarking::create_nft_collection;
+use pallet_evm_migration::Pallet as EvmMigrationPallet;
+
+const SEED: u32 = 0;
+
+fn set_admin<T>() -> Result<T::AccountId, sp_runtime::DispatchError>
+where
+	T: Config + pallet_unique::Config + pallet_evm_migration::Config,
+	T::BlockNumber: From<u32> + Into<u32>,
+	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,
+{
+	let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
+
+	<T as Config>::Currency::make_free_balance_be(
+		&pallet_admin,
+		Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value(),
+	);
+
+	PromototionPallet::<T>::set_admin_address(
+		RawOrigin::Root.into(),
+		T::CrossAccountId::from_sub(pallet_admin.clone()),
+	)?;
+
+	Ok(pallet_admin)
+}
+
+benchmarks! {
+	where_clause{
+		where T:  Config + pallet_unique::Config + pallet_evm_migration::Config ,
+		T::BlockNumber: From<u32> + Into<u32>,
+		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>
+	}
+
+	on_initialize {
+		let b in 0..PENDING_LIMIT_PER_BLOCK;
+		set_admin::<T>()?;
+
+		(0..b).try_for_each(|index| {
+			let staker = account::<T::AccountId>("staker", index, SEED);
+			<T as Config>::Currency::make_free_balance_be(&staker,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+			PromototionPallet::<T>::stake(RawOrigin::Signed(staker.clone()).into(), Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get())?;
+			PromototionPallet::<T>::unstake(RawOrigin::Signed(staker.clone()).into()).map_err(|e| e.error)?;
+			Result::<(), sp_runtime::DispatchError>::Ok(())
+		})?;
+		let block_number = <frame_system::Pallet<T>>::current_block_number() + T::PendingInterval::get();
+	}: {PromototionPallet::<T>::on_initialize(block_number)}
+
+	set_admin_address {
+		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
+		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+	} : _(RawOrigin::Root, T::CrossAccountId::from_sub(pallet_admin))
+
+	payout_stakers{
+		let b in 1..101;
+
+		let pallet_admin = account::<T::AccountId>("admin", 1, SEED);
+		let share = Perbill::from_rational(1u32, 20);
+		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
+		<T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+
+		let staker: T::AccountId = account("caller", 0, SEED);
+		<T as Config>::Currency::make_free_balance_be(&staker,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let stakers: Vec<T::AccountId> = (0..b).map(|index| account("staker", index, SEED)).collect();
+		stakers.iter().for_each(|staker| {
+			<T as Config>::Currency::make_free_balance_be(&staker,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		});
+		(0..10).try_for_each(|_| {
+			stakers.iter()
+				.map(|staker| {
+					PromototionPallet::<T>::stake(RawOrigin::Signed(staker.clone()).into(), Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get())
+				}).collect::<Result<Vec<_>, _>>()?;
+			<frame_system::Pallet<T>>::finalize();
+			Result::<(), sp_runtime::DispatchError>::Ok(())
+		})?;
+	} : _(RawOrigin::Signed(pallet_admin.clone()), Some(b as u8))
+
+	stake {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+		let share = Perbill::from_rational(1u32, 10);
+		let _ = <T as Config>::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+	} : _(RawOrigin::Signed(caller.clone()), share * <T as Config>::Currency::total_balance(&caller))
+
+	unstake {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+		let share = Perbill::from_rational(1u32, 20);
+		let _ = <T as Config>::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		(0..10).map(|_| {
+			<frame_system::Pallet<T>>::finalize();
+			PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * <T as Config>::Currency::total_balance(&caller))
+		}).collect::<Result<Vec<_>, _>>()?;
+
+	} : _(RawOrigin::Signed(caller.clone()))
+
+	sponsor_collection {
+		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
+		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
+		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let caller: T::AccountId = account("caller", 0, SEED);
+		let _ = <T as Config>::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let collection = create_nft_collection::<T>(caller.clone())?;
+	} : _(RawOrigin::Signed(pallet_admin.clone()), collection)
+
+	stop_sponsoring_collection {
+		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
+		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
+		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let caller: T::AccountId = account("caller", 0, SEED);
+		let _ = <T as Config>::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let collection = create_nft_collection::<T>(caller.clone())?;
+		PromototionPallet::<T>::sponsor_collection(RawOrigin::Signed(pallet_admin.clone()).into(), collection)?;
+	} : _(RawOrigin::Signed(pallet_admin.clone()), collection)
+
+	sponsor_contract {
+		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
+		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
+
+		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let address = H160::from_low_u64_be(SEED as u64);
+		let data: Vec<u8> = (0..20 as u8).collect();
+		<EvmMigrationPallet<T>>::begin(RawOrigin::Root.into(), address)?;
+		<EvmMigrationPallet<T>>::finish(RawOrigin::Root.into(), address, data)?;
+	} : _(RawOrigin::Signed(pallet_admin.clone()), address)
+
+	stop_sponsoring_contract {
+		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
+		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
+
+		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let address = H160::from_low_u64_be(SEED as u64);
+		let data: Vec<u8> = (0..20 as u8).collect();
+		<EvmMigrationPallet<T>>::begin(RawOrigin::Root.into(), address)?;
+		<EvmMigrationPallet<T>>::finish(RawOrigin::Root.into(), address, data)?;
+		PromototionPallet::<T>::sponsor_contract(RawOrigin::Signed(pallet_admin.clone()).into(), address)?;
+	} : _(RawOrigin::Signed(pallet_admin.clone()), address)
+}
addedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/lib.rs
@@ -0,0 +1,758 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! # App promotion
+//!
+//! The app promotion pallet is designed to ... .
+//!
+//! ## Interface
+//!
+//! ### Dispatchable Functions
+//!
+
+// #![recursion_limit = "1024"]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;
+
+pub mod types;
+pub mod weights;
+
+use sp_std::{
+	vec::{Vec},
+	vec,
+	iter::Sum,
+	borrow::ToOwned,
+	cell::RefCell,
+};
+use sp_core::H160;
+use codec::EncodeLike;
+use pallet_balances::BalanceLock;
+pub use types::*;
+
+use up_data_structs::CollectionId;
+
+use frame_support::{
+	dispatch::{DispatchResult},
+	traits::{
+		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,
+	},
+	ensure,
+};
+
+use weights::WeightInfo;
+
+pub use pallet::*;
+use pallet_evm::account::CrossAccountId;
+use sp_runtime::{
+	Perbill,
+	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},
+	ArithmeticError,
+};
+
+pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";
+
+const PENDING_LIMIT_PER_BLOCK: u32 = 3;
+
+type BalanceOf<T> =
+	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::{
+		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,
+		traits::ReservableCurrency,
+	};
+	use frame_system::pallet_prelude::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_evm::account::Config {
+		/// Type to interact with the native token
+		type Currency: ExtendedLockableCurrency<Self::AccountId>
+			+ ReservableCurrency<Self::AccountId>;
+
+		/// Type for interacting with collections
+		type CollectionHandler: CollectionHandler<
+			AccountId = Self::AccountId,
+			CollectionId = CollectionId,
+		>;
+
+		/// Type for interacting with conrtacts
+		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;
+
+		/// ID for treasury
+		type TreasuryAccountId: Get<Self::AccountId>;
+
+		/// The app's pallet id, used for deriving its sovereign account ID.
+		#[pallet::constant]
+		type PalletId: Get<PalletId>;
+
+		/// In relay blocks.
+		#[pallet::constant]
+		type RecalculationInterval: Get<Self::BlockNumber>;
+
+		/// In parachain blocks.
+		#[pallet::constant]
+		type PendingInterval: Get<Self::BlockNumber>;
+
+		/// Rate of return for interval in blocks defined in `RecalculationInterval`.
+		#[pallet::constant]
+		type IntervalIncome: Get<Perbill>;
+
+		/// Decimals for the `Currency`.
+		#[pallet::constant]
+		type Nominal: Get<BalanceOf<Self>>;
+
+		/// Weight information for extrinsics in this pallet.
+		type WeightInfo: WeightInfo;
+
+		// The relay block number provider
+		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;
+
+		/// Events compatible with [`frame_system::Config::Event`].
+		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
+	}
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	pub struct Pallet<T>(_);
+
+	#[pallet::event]
+	#[pallet::generate_deposit(fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// Staking recalculation was performed
+		///
+		/// # Arguments
+		/// * AccountId: ID of the staker.
+		/// * Balance : recalculation base
+		/// * Balance : total income
+		StakingRecalculation(
+			/// An recalculated staker
+			T::AccountId,
+			/// Base on which interest is calculated
+			BalanceOf<T>,
+			/// Amount of accrued interest
+			BalanceOf<T>,
+		),
+
+		/// Staking was performed
+		///
+		/// # Arguments
+		/// * AccountId: ID of the staker
+		/// * Balance : staking amount
+		Stake(T::AccountId, BalanceOf<T>),
+
+		/// Unstaking was performed
+		///
+		/// # Arguments
+		/// * AccountId: ID of the staker
+		/// * Balance : unstaking amount
+		Unstake(T::AccountId, BalanceOf<T>),
+
+		/// The admin was set
+		///
+		/// # Arguments
+		/// * AccountId: ID of the admin
+		SetAdmin(T::AccountId),
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// Error due to action requiring admin to be set.
+		AdminNotSet,
+		/// No permission to perform an action.
+		NoPermission,
+		/// Insufficient funds to perform an action.
+		NotSufficientFunds,
+		/// Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.
+		PendingForBlockOverflow,
+		/// The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.
+		SponsorNotSet,
+		/// Errors caused by incorrect actions with a locked balance.
+		IncorrectLockedBalanceOperation,
+	}
+
+	#[pallet::storage]
+	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;
+
+	#[pallet::storage]
+	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;
+
+	/// Amount of tokens staked by account in the blocknumber.
+	#[pallet::storage]
+	pub type Staked<T: Config> = StorageNMap<
+		Key = (
+			Key<Blake2_128Concat, T::AccountId>,
+			Key<Twox64Concat, T::BlockNumber>,
+		),
+		Value = (BalanceOf<T>, T::BlockNumber),
+		QueryKind = ValueQuery,
+	>;
+	/// Amount of stakes for an Account
+	#[pallet::storage]
+	pub type StakesPerAccount<T: Config> =
+		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;
+
+	#[pallet::storage]
+	pub type PendingUnstake<T: Config> = StorageMap<
+		_,
+		Twox64Concat,
+		T::BlockNumber,
+		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,
+		ValueQuery,
+	>;
+
+	/// Stores a key for record for which the next revenue recalculation would be performed.
+	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
+	#[pallet::storage]
+	#[pallet::getter(fn get_next_calculated_record)]
+	pub type NextCalculatedRecord<T: Config> =
+		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		/// Block overflow is impossible due to the fact that the unstake algorithm in on_initialize
+		/// implies the execution of a strictly limited number of relatively lightweight operations.
+		/// A separate benchmark has been implemented to scale the weight depending on the number of pendings.
+		fn on_initialize(current_block_number: T::BlockNumber) -> Weight
+		where
+			<T as frame_system::Config>::BlockNumber: From<u32>,
+		{
+			let block_pending = PendingUnstake::<T>::take(current_block_number);
+			let counter = block_pending.len() as u32;
+
+			if !block_pending.is_empty() {
+				block_pending.into_iter().for_each(|(staker, amount)| {
+					<T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);
+				});
+			}
+
+			T::WeightInfo::on_initialize(counter)
+		}
+	}
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T>
+	where
+		T::BlockNumber: From<u32> + Into<u32>,
+		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,
+	{
+		#[pallet::weight(T::WeightInfo::set_admin_address())]
+		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {
+			ensure_root(origin)?;
+
+			<Admin<T>>::set(Some(admin.as_sub().to_owned()));
+
+			Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));
+
+			Ok(())
+		}
+
+		#[pallet::weight(T::WeightInfo::stake())]
+		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
+			let staker_id = ensure_signed(staker)?;
+
+			ensure!(
+				StakesPerAccount::<T>::get(&staker_id) < 10,
+				Error::<T>::NoPermission
+			);
+
+			ensure!(
+				amount >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),
+				ArithmeticError::Underflow
+			);
+
+			let balance =
+				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);
+
+			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(
+				&staker_id,
+				amount,
+				WithdrawReasons::all(),
+				balance
+					.checked_sub(&amount)
+					.ok_or(ArithmeticError::Underflow)?,
+			)?;
+
+			Self::add_lock_balance(&staker_id, amount)?;
+
+			let block_number = T::RelayBlockNumberProvider::current_block_number();
+
+			let recalculate_after_interval: T::BlockNumber =
+				if block_number % T::RecalculationInterval::get() == 0u32.into() {
+					1u32.into()
+				} else {
+					2u32.into()
+				};
+
+			let recalc_block = (block_number / T::RecalculationInterval::get()
+				+ recalculate_after_interval)
+				* T::RecalculationInterval::get();
+
+			<Staked<T>>::insert((&staker_id, block_number), {
+				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));
+				balance_and_recalc_block.0 = balance_and_recalc_block
+					.0
+					.checked_add(&amount)
+					.ok_or(ArithmeticError::Overflow)?;
+				balance_and_recalc_block.1 = recalc_block;
+				balance_and_recalc_block
+			});
+
+			<TotalStaked<T>>::set(
+				<TotalStaked<T>>::get()
+					.checked_add(&amount)
+					.ok_or(ArithmeticError::Overflow)?,
+			);
+
+			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);
+
+			Self::deposit_event(Event::Stake(staker_id, amount));
+
+			Ok(())
+		}
+
+		#[pallet::weight(T::WeightInfo::unstake())]
+		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {
+			let staker_id = ensure_signed(staker)?;
+			let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();
+			let mut pendings = <PendingUnstake<T>>::get(block);
+
+			ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);
+
+			let mut total_stakes = 0u64;
+
+			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))
+				.map(|(_, (amount, _))| {
+					total_stakes += 1;
+					amount
+				})
+				.sum();
+
+			if total_staked.is_zero() {
+				return Ok(None.into()); // TO-DO
+			}
+
+			pendings
+				.try_push((staker_id.clone(), total_staked))
+				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;
+
+			<PendingUnstake<T>>::insert(block, pendings);
+
+			Self::unlock_balance(&staker_id, total_staked)?;
+
+			<T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;
+
+			TotalStaked::<T>::set(
+				TotalStaked::<T>::get()
+					.checked_sub(&total_staked)
+					.ok_or(ArithmeticError::Underflow)?,
+			);
+
+			StakesPerAccount::<T>::remove(&staker_id);
+
+			Self::deposit_event(Event::Unstake(staker_id, total_staked));
+
+			Ok(None.into())
+		}
+
+		#[pallet::weight(T::WeightInfo::sponsor_collection())]
+		pub fn sponsor_collection(
+			admin: OriginFor<T>,
+			collection_id: CollectionId,
+		) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
+
+			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)
+		}
+		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]
+		pub fn stop_sponsoring_collection(
+			admin: OriginFor<T>,
+			collection_id: CollectionId,
+		) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
+
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
+
+			ensure!(
+				T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?
+					== Self::account_id(),
+				<Error<T>>::NoPermission
+			);
+			T::CollectionHandler::remove_collection_sponsor(collection_id)
+		}
+
+		#[pallet::weight(T::WeightInfo::sponsor_contract())]
+		pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
+
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
+
+			T::ContractHandler::set_sponsor(
+				T::CrossAccountId::from_sub(Self::account_id()),
+				contract_id,
+			)
+		}
+
+		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]
+		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
+
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
+
+			ensure!(
+				T::ContractHandler::sponsor(contract_id)?
+					.ok_or(<Error<T>>::SponsorNotSet)?
+					.as_sub() == &Self::account_id(),
+				<Error<T>>::NoPermission
+			);
+			T::ContractHandler::remove_contract_sponsor(contract_id)
+		}
+
+		#[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]
+		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
+
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
+
+			let current_recalc_block =
+				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());
+			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();
+
+			let mut storage_iterator = Self::get_next_calculated_key()
+				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));
+
+			NextCalculatedRecord::<T>::set(None);
+
+			// {
+			// 	let mut stakers_number = stakers_number.unwrap_or(20);
+			// 	let mut last_id = admin_id;
+			// 	let mut income_acc = BalanceOf::<T>::default();
+			// 	let mut amount_acc = BalanceOf::<T>::default();
+
+			// 	while let Some((
+			// 		(current_id, staked_block),
+			// 		(amount, next_recalc_block_for_stake),
+			// 	)) = storage_iterator.next()
+			// 	{
+			// 		if last_id != current_id {
+			// 			if income_acc != BalanceOf::<T>::default() {
+			// 				<T::Currency as Currency<T::AccountId>>::transfer(
+			// 					&T::TreasuryAccountId::get(),
+			// 					&last_id,
+			// 					income_acc,
+			// 					ExistenceRequirement::KeepAlive,
+			// 				)
+			// 				.and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;
+
+			// 				Self::deposit_event(Event::StakingRecalculation(
+			// 					last_id, amount, income_acc,
+			// 				));
+			// 			}
+
+			// 			if stakers_number == 0 {
+			// 				NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));
+			// 				break;
+			// 			}
+			// 			stakers_number -= 1;
+			// 			income_acc = BalanceOf::<T>::default();
+			// 			last_id = current_id;
+			// 		};
+			// 		if current_recalc_block >= next_recalc_block_for_stake {
+			// 			Self::recalculate_and_insert_stake(
+			// 				&last_id,
+			// 				staked_block,
+			// 				next_recalc_block,
+			// 				amount,
+			// 				((current_recalc_block - next_recalc_block_for_stake)
+			// 					/ T::RecalculationInterval::get())
+			// 				.into() + 1,
+			// 				&mut income_acc,
+			// 			);
+			// 		}
+			// 	}
+			// }
+
+			{
+				let mut stakers_number = stakers_number.unwrap_or(20);
+				let last_id = RefCell::new(None);
+				let income_acc = RefCell::new(BalanceOf::<T>::default());
+				let amount_acc = RefCell::new(BalanceOf::<T>::default());
+
+				let flush_stake = || -> DispatchResult {
+					if let Some(last_id) = &*last_id.borrow() {
+						if !income_acc.borrow().is_zero() {
+							<T::Currency as Currency<T::AccountId>>::transfer(
+								&T::TreasuryAccountId::get(),
+								last_id,
+								*income_acc.borrow(),
+								ExistenceRequirement::KeepAlive,
+							)
+							.and_then(|_| {
+								Self::add_lock_balance(last_id, *income_acc.borrow())?;
+								<TotalStaked<T>>::try_mutate(|staked| {
+									staked
+										.checked_add(&*income_acc.borrow())
+										.ok_or(ArithmeticError::Overflow.into())
+								})
+							})?;
+
+							Self::deposit_event(Event::StakingRecalculation(
+								last_id.clone(),
+								*amount_acc.borrow(),
+								*income_acc.borrow(),
+							));
+						}
+
+						*income_acc.borrow_mut() = BalanceOf::<T>::default();
+						*amount_acc.borrow_mut() = BalanceOf::<T>::default();
+					}
+					Ok(())
+				};
+
+				while let Some((
+					(current_id, staked_block),
+					(amount, next_recalc_block_for_stake),
+				)) = storage_iterator.next()
+				{
+					if stakers_number == 0 {
+						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));
+						break;
+					}
+					if last_id.borrow().as_ref() != Some(&current_id) {
+						flush_stake()?;
+						*last_id.borrow_mut() = Some(current_id.clone());
+						stakers_number -= 1;
+					};
+					if current_recalc_block >= next_recalc_block_for_stake {
+						*amount_acc.borrow_mut() += amount;
+						Self::recalculate_and_insert_stake(
+							&current_id,
+							staked_block,
+							next_recalc_block,
+							amount,
+							((current_recalc_block - next_recalc_block_for_stake)
+								/ T::RecalculationInterval::get())
+							.into() + 1,
+							&mut *income_acc.borrow_mut(),
+						);
+					}
+				}
+				flush_stake()?;
+			}
+
+			Ok(())
+		}
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	pub fn account_id() -> T::AccountId {
+		T::PalletId::get().into_account_truncating()
+	}
+
+	fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+		let locked_balance = Self::get_locked_balance(staker)
+			.map(|l| l.amount)
+			.ok_or(<Error<T>>::IncorrectLockedBalanceOperation)?;
+
+		// It is understood that we cannot unlock more funds than were locked by staking.
+		// Therefore, if implemented correctly, this error should not occur.
+		Self::set_lock_unchecked(
+			staker,
+			locked_balance
+				.checked_sub(&amount)
+				.ok_or(ArithmeticError::Underflow)?,
+		);
+		Ok(())
+	}
+
+	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+		Self::get_locked_balance(staker)
+			.map_or(<BalanceOf<T>>::default(), |l| l.amount)
+			.checked_add(&amount)
+			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))
+			.ok_or(ArithmeticError::Overflow.into())
+	}
+
+	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {
+		if amount.is_zero() {
+			<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);
+		} else {
+			<T::Currency as LockableCurrency<T::AccountId>>::set_lock(
+				LOCK_IDENTIFIER,
+				staker,
+				amount,
+				WithdrawReasons::all(),
+			)
+		}
+	}
+
+	pub fn get_locked_balance(
+		staker: impl EncodeLike<T::AccountId>,
+	) -> Option<BalanceLock<BalanceOf<T>>> {
+		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)
+			.into_iter()
+			.find(|l| l.id == LOCK_IDENTIFIER)
+	}
+
+	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {
+		let staked = Staked::<T>::iter_prefix((staker,))
+			.into_iter()
+			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {
+				acc + amount
+			});
+		if staked != <BalanceOf<T>>::default() {
+			Some(staked)
+		} else {
+			None
+		}
+	}
+
+	pub fn total_staked_by_id_per_block(
+		staker: impl EncodeLike<T::AccountId>,
+	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {
+		let mut staked = Staked::<T>::iter_prefix((staker,))
+			.into_iter()
+			.map(|(block, (amount, _))| (block, amount))
+			.collect::<Vec<_>>();
+		staked.sort_by_key(|(block, _)| *block);
+		if !staked.is_empty() {
+			Some(staked)
+		} else {
+			None
+		}
+	}
+
+	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {
+		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {
+			Self::total_staked_by_id(s.as_sub())
+		})
+	}
+
+	// pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {
+	// 	Self::get_locked_balance(staker.as_sub())
+	// 		.map(|l| l.amount)
+	// 		.unwrap_or_default()
+	// }
+
+	pub fn cross_id_total_staked_per_block(
+		staker: T::CrossAccountId,
+	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {
+		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()
+	}
+
+	fn recalculate_and_insert_stake(
+		staker: &T::AccountId,
+		staked_block: T::BlockNumber,
+		next_recalc_block: T::BlockNumber,
+		base: BalanceOf<T>,
+		iters: u32,
+		income_acc: &mut BalanceOf<T>,
+	) {
+		let income = Self::calculate_income(base, iters);
+
+		base.checked_add(&income).map(|res| {
+			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));
+			*income_acc += income;
+		});
+	}
+
+	fn calculate_income<I>(base: I, iters: u32) -> I
+	where
+		I: EncodeLike<BalanceOf<T>> + Balance,
+	{
+		let mut income = base;
+
+		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);
+
+		income - base
+	}
+
+	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {
+		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()
+	}
+
+	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {
+	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()
+	// }
+
+	fn get_next_calculated_key() -> Option<Vec<u8>> {
+		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))
+	}
+}
+
+impl<T: Config> Pallet<T>
+where
+	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,
+{
+	/// Since user funds are not transferred anywhere by staking, overflow protection is provided
+	/// at the level of the associated type `Balance` of `Currency` trait. In order to overflow,
+	/// the staker must have more funds on his account than the maximum set for `Balance` type.
+	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {
+		staker.map_or(
+			PendingUnstake::<T>::iter_values()
+				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))
+				.sum(),
+			|s| {
+				PendingUnstake::<T>::iter_values()
+					.flatten()
+					.filter_map(|(id, amount)| {
+						if id == *s.as_sub() {
+							Some(amount)
+						} else {
+							None
+						}
+					})
+					.sum()
+			},
+		)
+	}
+
+	pub fn cross_id_pending_unstake_per_block(
+		staker: T::CrossAccountId,
+	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {
+		let mut unsorted_res = vec![];
+		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {
+			pendings.into_iter().for_each(|(id, amount)| {
+				if id == *staker.as_sub() {
+					unsorted_res.push((block, amount));
+				};
+			})
+		});
+
+		unsorted_res.sort_by_key(|(block, _)| *block);
+		unsorted_res
+	}
+}
addedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/types.rs
@@ -0,0 +1,107 @@
+use codec::EncodeLike;
+use frame_support::{traits::LockableCurrency, WeakBoundedVec, Parameter, dispatch::DispatchResult};
+
+use pallet_balances::{BalanceLock, Config as BalancesConfig, Pallet as PalletBalances};
+use pallet_common::CollectionHandle;
+
+use sp_runtime::DispatchError;
+use up_data_structs::{CollectionId};
+use sp_std::borrow::ToOwned;
+use pallet_evm_contract_helpers::{Pallet as EvmHelpersPallet, Config as EvmHelpersConfig};
+
+pub trait ExtendedLockableCurrency<AccountId: Parameter>: LockableCurrency<AccountId> {
+	fn locks<KArg>(who: KArg) -> WeakBoundedVec<BalanceLock<Self::Balance>, Self::MaxLocks>
+	where
+		KArg: EncodeLike<AccountId>;
+}
+
+impl<T: BalancesConfig<I>, I: 'static> ExtendedLockableCurrency<T::AccountId>
+	for PalletBalances<T, I>
+{
+	fn locks<KArg>(who: KArg) -> WeakBoundedVec<BalanceLock<Self::Balance>, Self::MaxLocks>
+	where
+		KArg: EncodeLike<T::AccountId>,
+	{
+		Self::locks(who)
+	}
+}
+
+pub trait CollectionHandler {
+	type CollectionId;
+	type AccountId;
+
+	fn set_sponsor(
+		sponsor_id: Self::AccountId,
+		collection_id: Self::CollectionId,
+	) -> DispatchResult;
+
+	fn remove_collection_sponsor(collection_id: Self::CollectionId) -> DispatchResult;
+
+	fn sponsor(collection_id: Self::CollectionId)
+		-> Result<Option<Self::AccountId>, DispatchError>;
+}
+
+impl<T: pallet_unique::Config> CollectionHandler for pallet_unique::Pallet<T> {
+	type CollectionId = CollectionId;
+
+	type AccountId = T::AccountId;
+
+	fn set_sponsor(
+		sponsor_id: Self::AccountId,
+		collection_id: Self::CollectionId,
+	) -> DispatchResult {
+		Self::force_set_sponsor(sponsor_id, collection_id)
+	}
+
+	fn remove_collection_sponsor(collection_id: Self::CollectionId) -> DispatchResult {
+		Self::force_remove_collection_sponsor(collection_id)
+	}
+
+	fn sponsor(
+		collection_id: Self::CollectionId,
+	) -> Result<Option<Self::AccountId>, DispatchError> {
+		Ok(<CollectionHandle<T>>::try_get(collection_id)?
+			.sponsorship
+			.sponsor()
+			.map(|acc| acc.to_owned()))
+	}
+}
+
+pub trait ContractHandler {
+	type ContractId;
+	type AccountId;
+
+	fn set_sponsor(
+		sponsor_id: Self::AccountId,
+		contract_address: Self::ContractId,
+	) -> DispatchResult;
+
+	fn remove_contract_sponsor(contract_address: Self::ContractId) -> DispatchResult;
+
+	fn sponsor(
+		contract_address: Self::ContractId,
+	) -> Result<Option<Self::AccountId>, DispatchError>;
+}
+
+impl<T: EvmHelpersConfig> ContractHandler for EvmHelpersPallet<T> {
+	type ContractId = sp_core::H160;
+
+	type AccountId = T::CrossAccountId;
+
+	fn set_sponsor(
+		sponsor_id: Self::AccountId,
+		contract_address: Self::ContractId,
+	) -> DispatchResult {
+		Self::force_set_sponsor(contract_address, &sponsor_id)
+	}
+
+	fn remove_contract_sponsor(contract_address: Self::ContractId) -> DispatchResult {
+		Self::force_remove_sponsor(contract_address)
+	}
+
+	fn sponsor(
+		contract_address: Self::ContractId,
+	) -> Result<Option<Self::AccountId>, DispatchError> {
+		Ok(Self::get_sponsor(contract_address))
+	}
+}
addedpallets/app-promotion/src/weights.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/weights.rs
@@ -0,0 +1,209 @@
+// Template adopted from https://github.com/paritytech/substrate/blob/master/.maintain/frame-weight-template.hbs
+
+//! Autogenerated weights for pallet_app_promotion
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-09-07, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
+
+// Executed Command:
+// target/release/unique-collator
+// benchmark
+// pallet
+// --pallet
+// pallet-app-promotion
+// --wasm-execution
+// compiled
+// --extrinsic
+// *
+// --template
+// .maintain/frame-weight-template.hbs
+// --steps=50
+// --repeat=80
+// --heap-pages=4096
+// --output=./pallets/app-promotion/src/weights.rs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for pallet_app_promotion.
+pub trait WeightInfo {
+	fn on_initialize(b: u32, ) -> Weight;
+	fn set_admin_address() -> Weight;
+	fn payout_stakers(b: u32, ) -> Weight;
+	fn stake() -> Weight;
+	fn unstake() -> Weight;
+	fn sponsor_collection() -> Weight;
+	fn stop_sponsoring_collection() -> Weight;
+	fn sponsor_contract() -> Weight;
+	fn stop_sponsoring_contract() -> Weight;
+}
+
+/// Weights for pallet_app_promotion using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	// Storage: AppPromotion PendingUnstake (r:1 w:0)
+	// Storage: System Account (r:1 w:1)
+	fn on_initialize(b: u32, ) -> Weight {
+		(2_651_000 as Weight)
+			// Standard Error: 103_000
+			.saturating_add((6_024_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(b as Weight)))
+	}
+	// Storage: AppPromotion Admin (r:0 w:1)
+	fn set_admin_address() -> Weight {
+		(7_117_000 as Weight)
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: AppPromotion NextCalculatedRecord (r:1 w:1)
+	// Storage: AppPromotion Staked (r:2 w:0)
+	fn payout_stakers(b: u32, ) -> Weight {
+		(9_958_000 as Weight)
+			// Standard Error: 8_000
+			.saturating_add((4_406_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(4 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion StakesPerAccount (r:1 w:1)
+	// Storage: System Account (r:1 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: AppPromotion Staked (r:1 w:1)
+	// Storage: AppPromotion TotalStaked (r:1 w:1)
+	fn stake() -> Weight {
+		(20_574_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(6 as Weight))
+			.saturating_add(T::DbWeight::get().writes(5 as Weight))
+	}
+	// Storage: AppPromotion PendingUnstake (r:1 w:1)
+	// Storage: AppPromotion Staked (r:2 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: System Account (r:1 w:1)
+	// Storage: AppPromotion TotalStaked (r:1 w:1)
+	// Storage: AppPromotion StakesPerAccount (r:0 w:1)
+	fn unstake() -> Weight {
+		(31_703_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(6 as Weight))
+			.saturating_add(T::DbWeight::get().writes(6 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: Common CollectionById (r:1 w:1)
+	fn sponsor_collection() -> Weight {
+		(12_932_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: Common CollectionById (r:1 w:1)
+	fn stop_sponsoring_collection() -> Weight {
+		(12_453_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: EvmContractHelpers Sponsoring (r:0 w:1)
+	fn sponsor_contract() -> Weight {
+		(11_952_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: EvmContractHelpers Sponsoring (r:1 w:1)
+	fn stop_sponsoring_contract() -> Weight {
+		(12_538_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+	// Storage: AppPromotion PendingUnstake (r:1 w:0)
+	// Storage: System Account (r:1 w:1)
+	fn on_initialize(b: u32, ) -> Weight {
+		(2_651_000 as Weight)
+			// Standard Error: 103_000
+			.saturating_add((6_024_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(b as Weight)))
+	}
+	// Storage: AppPromotion Admin (r:0 w:1)
+	fn set_admin_address() -> Weight {
+		(7_117_000 as Weight)
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: AppPromotion NextCalculatedRecord (r:1 w:1)
+	// Storage: AppPromotion Staked (r:2 w:0)
+	fn payout_stakers(b: u32, ) -> Weight {
+		(9_958_000 as Weight)
+			// Standard Error: 8_000
+			.saturating_add((4_406_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion StakesPerAccount (r:1 w:1)
+	// Storage: System Account (r:1 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: AppPromotion Staked (r:1 w:1)
+	// Storage: AppPromotion TotalStaked (r:1 w:1)
+	fn stake() -> Weight {
+		(20_574_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(6 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
+	}
+	// Storage: AppPromotion PendingUnstake (r:1 w:1)
+	// Storage: AppPromotion Staked (r:2 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: System Account (r:1 w:1)
+	// Storage: AppPromotion TotalStaked (r:1 w:1)
+	// Storage: AppPromotion StakesPerAccount (r:0 w:1)
+	fn unstake() -> Weight {
+		(31_703_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(6 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: Common CollectionById (r:1 w:1)
+	fn sponsor_collection() -> Weight {
+		(12_932_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: Common CollectionById (r:1 w:1)
+	fn stop_sponsoring_collection() -> Weight {
+		(12_453_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: EvmContractHelpers Sponsoring (r:0 w:1)
+	fn sponsor_contract() -> Weight {
+		(11_952_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: AppPromotion Admin (r:1 w:0)
+	// Storage: EvmContractHelpers Sponsoring (r:1 w:1)
+	fn stop_sponsoring_contract() -> Weight {
+		(12_538_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+}
modifiedpallets/common/Cargo.tomldiffbeforeafterboth
--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -40,4 +40,7 @@
     "up-data-structs/std",
     "pallet-evm/std",
 ]
-runtime-benchmarks = ["frame-benchmarking"]
+runtime-benchmarks = [
+    "frame-benchmarking/runtime-benchmarks",
+    "up-data-structs/runtime-benchmarks",
+]
modifiedpallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -9,6 +9,7 @@
     "derive",
 ] }
 log = { default-features = false, version = "0.4.14" }
+ethereum = { version = "0.12.0", default-features = false }
 
 # Substrate
 frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -17,7 +17,9 @@
 //! Implementation of magic contract
 
 use core::marker::PhantomData;
-use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*};
+use evm_coder::{
+	abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,
+};
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm};
 use pallet_evm::{
 	ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,
@@ -33,6 +35,35 @@
 use up_sponsorship::SponsorshipHandler;
 use sp_std::vec::Vec;
 
+/// Pallet events.
+#[derive(ToLog)]
+pub enum ContractHelpersEvents {
+	/// Contract sponsor was set.
+	ContractSponsorSet {
+		/// Contract address of the affected collection.
+		#[indexed]
+		contract_address: address,
+		/// New sponsor address.
+		sponsor: address,
+	},
+
+	/// New sponsor was confirm.
+	ContractSponsorshipConfirmed {
+		/// Contract address of the affected collection.
+		#[indexed]
+		contract_address: address,
+		/// New sponsor address.
+		sponsor: address,
+	},
+
+	/// Collection sponsor was removed.
+	ContractSponsorRemoved {
+		/// Contract address of the affected collection.
+		#[indexed]
+		contract_address: address,
+	},
+}
+
 /// See [`ContractHelpersCall`]
 pub struct ContractHelpers<T: Config>(SubstrateRecorder<T>);
 impl<T: Config> WithRecorder<T> for ContractHelpers<T> {
@@ -46,7 +77,7 @@
 }
 
 /// @title Magic contract, which allows users to reconfigure other contracts
-#[solidity_interface(name = ContractHelpers)]
+#[solidity_interface(name = ContractHelpers, events(ContractHelpersEvents))]
 impl<T: Config> ContractHelpers<T>
 where
 	T::AccountId: AsRef<[u8; 32]>,
@@ -91,9 +122,17 @@
 		self.recorder().consume_sload()?;
 		self.recorder().consume_sstore()?;
 
-		Pallet::<T>::self_sponsored_enable(&T::CrossAccountId::from_eth(caller), contract_address)
+		let caller = T::CrossAccountId::from_eth(caller);
+
+		Pallet::<T>::ensure_owner(contract_address, *caller.as_eth())
 			.map_err(dispatch_to_evm::<T>)?;
 
+		Pallet::<T>::force_set_sponsor(
+			contract_address,
+			&T::CrossAccountId::from_eth(contract_address),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
 		Ok(())
 	}
 
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -16,7 +16,7 @@
 
 #![doc = include_str!("../README.md")]
 #![cfg_attr(not(feature = "std"), no_std)]
-#![deny(missing_docs)]
+#![warn(missing_docs)]
 
 use codec::{Decode, Encode, MaxEncodedLen};
 pub use pallet::*;
@@ -27,18 +27,24 @@
 #[frame_support::pallet]
 pub mod pallet {
 	pub use super::*;
+	use crate::eth::ContractHelpersEvents;
 	use frame_support::pallet_prelude::*;
 	use pallet_evm_coder_substrate::DispatchResult;
 	use sp_core::H160;
-	use pallet_evm::account::CrossAccountId;
+	use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 	use up_data_structs::SponsorshipState;
+	use evm_coder::ToLog;
 
 	#[pallet::config]
 	pub trait Config:
 		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config
 	{
+		/// Overarching event type.
+		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
+
 		/// Address, under which magic contract will be available
 		type ContractAddress: Get<H160>;
+
 		/// In case of enabled sponsoring, but no sponsoring rate limit set,
 		/// this value will be used implicitly
 		type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;
@@ -150,6 +156,32 @@
 		QueryKind = ValueQuery,
 	>;
 
+	#[pallet::event]
+	#[pallet::generate_deposit(pub fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// Contract sponsor was set.
+		ContractSponsorSet(
+			/// Contract address of the affected collection.
+			H160,
+			/// New sponsor address.
+			T::AccountId,
+		),
+
+		/// New sponsor was confirm.
+		ContractSponsorshipConfirmed(
+			/// Contract address of the affected collection.
+			H160,
+			/// New sponsor address.
+			T::AccountId,
+		),
+
+		/// Collection sponsor was removed.
+		ContractSponsorRemoved(
+			/// Contract address of the affected collection.
+			H160,
+		),
+	}
+
 	impl<T: Config> Pallet<T> {
 		/// Get contract owner.
 		pub fn contract_owner(contract: H160) -> H160 {
@@ -169,43 +201,118 @@
 				contract,
 				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),
 			);
+
+			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorSet(
+				contract,
+				sponsor.as_sub().clone(),
+			));
+			<PalletEvm<T>>::deposit_log(
+				ContractHelpersEvents::ContractSponsorSet {
+					contract_address: contract,
+					sponsor: *sponsor.as_eth(),
+				}
+				.to_log(contract),
+			);
 			Ok(())
 		}
 
-		/// Set `contract` as self sponsored.
+		/// TO-DO
 		///
-		/// `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())?;
+		///
+		pub fn force_set_sponsor(
+			contract_address: H160,
+			sponsor: &T::CrossAccountId,
+		) -> DispatchResult {
 			Sponsoring::<T>::insert(
-				contract,
-				SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(
-					contract,
-				)),
+				contract_address,
+				SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor.clone()),
 			);
+
+			let eth_sponsor = *sponsor.as_eth();
+			let sub_sponsor = sponsor.as_sub().clone();
+
+			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorSet(
+				contract_address,
+				sub_sponsor.clone(),
+			));
+			<PalletEvm<T>>::deposit_log(
+				ContractHelpersEvents::ContractSponsorSet {
+					contract_address,
+					sponsor: eth_sponsor,
+				}
+				.to_log(contract_address),
+			);
+
+			<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorshipConfirmed(
+				contract_address,
+				sub_sponsor,
+			));
+			<PalletEvm<T>>::deposit_log(
+				ContractHelpersEvents::ContractSponsorshipConfirmed {
+					contract_address,
+					sponsor: eth_sponsor,
+				}
+				.to_log(contract_address),
+			);
+
 			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);
+		pub fn remove_sponsor(
+			sender: &T::CrossAccountId,
+			contract_address: H160,
+		) -> DispatchResult {
+			Self::ensure_owner(contract_address, *sender.as_eth())?;
+			Self::force_remove_sponsor(contract_address)
+		}
+
+		/// TO-DO
+		///
+		///
+		pub fn force_remove_sponsor(contract_address: H160) -> DispatchResult {
+			Sponsoring::<T>::remove(contract_address);
+
+			Self::deposit_event(Event::<T>::ContractSponsorRemoved(contract_address));
+			<PalletEvm<T>>::deposit_log(
+				ContractHelpersEvents::ContractSponsorRemoved { contract_address }
+					.to_log(contract_address),
+			);
+
 			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) {
+		pub fn confirm_sponsorship(
+			sender: &T::CrossAccountId,
+			contract_address: H160,
+		) -> DispatchResult {
+			match Sponsoring::<T>::get(contract_address) {
 				SponsorshipState::Unconfirmed(sponsor) => {
 					ensure!(sponsor == *sender, Error::<T>::NoPermission);
+					let eth_sponsor = *sponsor.as_eth();
+					let sub_sponsor = sponsor.as_sub().clone();
 					Sponsoring::<T>::insert(
-						contract,
+						contract_address,
 						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),
 					);
+
+					<Pallet<T>>::deposit_event(Event::<T>::ContractSponsorshipConfirmed(
+						contract_address,
+						sub_sponsor,
+					));
+					<PalletEvm<T>>::deposit_log(
+						ContractHelpersEvents::ContractSponsorshipConfirmed {
+							contract_address,
+							sponsor: eth_sponsor,
+						}
+						.to_log(contract_address),
+					);
+
 					Ok(())
 				}
 				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth
21 }21 }
22}22}
23
24/// @dev inlined interface
25contract ContractHelpersEvents {
26 event ContractSponsorSet(address indexed contractAddress, address sponsor);
27 event ContractSponsorshipConfirmed(
28 address indexed contractAddress,
29 address sponsor
30 );
31 event ContractSponsorRemoved(address indexed contractAddress);
32}
2333
24/// @title Magic contract, which allows users to reconfigure other contracts34/// @title Magic contract, which allows users to reconfigure other contracts
25/// @dev the ERC-165 identifier for this interface is 0xd77fab7035/// @dev the ERC-165 identifier for this interface is 0xd77fab70
26contract ContractHelpers is Dummy, ERC165 {36contract ContractHelpers is Dummy, ERC165, ContractHelpersEvents {
27 /// Get user, which deployed specified contract37 /// Get user, which deployed specified contract
28 /// @dev May return zero address in case if contract is deployed38 /// @dev May return zero address in case if contract is deployed
29 /// using uniquenetwork evm-migration pallet, or using other terms not39 /// using uniquenetwork evm-migration pallet, or using other terms not
modifiedpallets/unique/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/unique/CHANGELOG.md
+++ b/pallets/unique/CHANGELOG.md
@@ -3,22 +3,29 @@
 All notable changes to this project will be documented in this file.
 
 <!-- bureaucrate goes here -->
+
+## [v0.1.4] 2022-09-5
+
+### Added
+
+-   Methods `force_set_sponsor` , `force_remove_collection_sponsor` to be able to administer sponsorships with other pallets. Added to implement `AppPromotion` pallet logic.
+
 ## [v0.1.3] 2022-08-16
 
 ### Other changes
 
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+-   build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
 
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+-   build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
 
-- refactor: Remove `#[transactional]` from extrinsics 7fd36cea2f6e00c02c67ccc1de9649ae404efd31
+-   refactor: Remove `#[transactional]` from extrinsics 7fd36cea2f6e00c02c67ccc1de9649ae404efd31
 
 Every extrinsic now runs in transaction implicitly, and
 `#[transactional]` on pallet dispatchable is now meaningless
 
 Upstream-Change: https://github.com/paritytech/substrate/issues/10806
 
-- refactor: Switch to new prefix removal methods 26734e9567589d75cdd99e404eabf11d5a97d975
+-   refactor: Switch to new prefix removal methods 26734e9567589d75cdd99e404eabf11d5a97d975
 
 New methods allows to call `remove_prefix` with limit multiple times
 in the same block
@@ -27,10 +34,12 @@
 
 Upstream-Change: https://github.com/paritytech/substrate/pull/11490
 
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+-   build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
 
 ## [v0.1.1] - 2022-07-25
+
 ### Added
-- Method for creating `ERC721Metadata` compatible NFT collection.
-- Method for creating `ERC721Metadata` compatible ReFungible collection.
-- Method for creating ReFungible collection.
+
+-   Method for creating `ERC721Metadata` compatible NFT collection.
+-   Method for creating `ERC721Metadata` compatible ReFungible collection.
+-   Method for creating ReFungible collection.
modifiedpallets/unique/Cargo.tomldiffbeforeafterboth
--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -9,7 +9,7 @@
 license = 'GPLv3'
 name = 'pallet-unique'
 repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = "0.1.3"
+version = "0.1.4"
 
 [package.metadata.docs.rs]
 targets = ['x86_64-unknown-linux-gnu']
modifiedpallets/unique/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/unique/src/benchmarking.rs
+++ b/pallets/unique/src/benchmarking.rs
@@ -46,7 +46,9 @@
 	)?;
 	Ok(<pallet_common::CreatedCollectionCount<T>>::get())
 }
-fn create_nft_collection<T: Config>(owner: T::AccountId) -> Result<CollectionId, DispatchError> {
+pub fn create_nft_collection<T: Config>(
+	owner: T::AccountId,
+) -> Result<CollectionId, DispatchError> {
 	create_collection_helper::<T>(owner, CollectionMode::NFT)
 }
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -98,7 +98,7 @@
 pub mod eth;
 
 #[cfg(feature = "runtime-benchmarks")]
-mod benchmarking;
+pub mod benchmarking;
 pub mod weights;
 use weights::WeightInfo;
 
@@ -277,7 +277,7 @@
 	{
 		type Error = Error<T>;
 
-		fn deposit_event() = default;
+		pub fn deposit_event() = default;
 
 		fn on_initialize(_now: T::BlockNumber) -> Weight {
 			0
@@ -1103,3 +1103,35 @@
 		}
 	}
 }
+
+impl<T: Config> Pallet<T> {
+	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {
+		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+		target_collection.check_is_internal()?;
+		target_collection.set_sponsor(sponsor.clone())?;
+
+		Self::deposit_event(Event::<T>::CollectionSponsorSet(
+			collection_id,
+			sponsor.clone(),
+		));
+
+		ensure!(
+			target_collection.confirm_sponsorship(&sponsor)?,
+			Error::<T>::ConfirmUnsetSponsorFail
+		);
+
+		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));
+
+		target_collection.save()
+	}
+
+	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {
+		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+		target_collection.check_is_internal()?;
+		target_collection.sponsorship = SponsorshipState::Disabled;
+
+		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));
+
+		target_collection.save()
+	}
+}
addedprimitives/app_promotion_rpc/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/primitives/app_promotion_rpc/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+<!-- bureaucrate goes here -->
addedprimitives/app_promotion_rpc/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/primitives/app_promotion_rpc/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "app-promotion-rpc"
+version = "0.1.0"
+license = "GPLv3"
+edition = "2021"
+
+[dependencies]
+pallet-common = { default-features = false, path = '../../pallets/common' }
+up-data-structs = { default-features = false, path = '../data-structs' }
+codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false, features = [
+	"derive",
+] }
+sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+sp-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
+pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"sp-core/std",
+	"sp-std/std",
+	"sp-api/std",
+	"sp-runtime/std",
+	"pallet-common/std",
+	"up-data-structs/std",
+]
addedprimitives/app_promotion_rpc/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/app_promotion_rpc/src/lib.rs
@@ -0,0 +1,41 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use sp_std::vec::Vec;
+use codec::Decode;
+use sp_runtime::{
+	DispatchError,
+	traits::{AtLeast32BitUnsigned, Member},
+};
+
+type Result<T> = core::result::Result<T, DispatchError>;
+
+sp_api::decl_runtime_apis! {
+	#[api_version(2)]
+	/// Trait for generate rpc.
+	pub trait AppPromotionApi<BlockNumber ,CrossAccountId, AccountId> where
+		BlockNumber: Decode + Member + AtLeast32BitUnsigned,
+		AccountId: Decode,
+		CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,
+	{
+		fn total_staked(staker: Option<CrossAccountId>) -> Result<u128>;
+		fn total_staked_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>>;
+		fn pending_unstake(staker: Option<CrossAccountId>) -> Result<u128>;
+		fn pending_unstake_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>>;
+	}
+}
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -22,6 +22,7 @@
 use crate::types::{BlockNumber, Balance};
 
 pub const MILLISECS_PER_BLOCK: u64 = 12000;
+pub const MILLISECS_PER_RELAY_BLOCK: u64 = 6000;
 
 pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
 
@@ -30,6 +31,11 @@
 pub const HOURS: BlockNumber = MINUTES * 60;
 pub const DAYS: BlockNumber = HOURS * 24;
 
+// These time units are defined in number of relay blocks.
+pub const RELAY_MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_RELAY_BLOCK as BlockNumber);
+pub const RELAY_HOURS: BlockNumber = RELAY_MINUTES * 60;
+pub const RELAY_DAYS: BlockNumber = RELAY_HOURS * 24;
+
 pub const MICROUNIQUE: Balance = 1_000_000_000_000;
 pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
 pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -20,9 +20,13 @@
 	CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,
 	PropertyKeyPermission, TokenData, TokenChild,
 };
+
 use sp_std::vec::Vec;
 use codec::Decode;
-use sp_runtime::DispatchError;
+use sp_runtime::{
+	DispatchError,
+	traits::{AtLeast32BitUnsigned, Member},
+};
 
 type Result<T> = core::result::Result<T, DispatchError>;
 
@@ -120,6 +124,7 @@
 
 		/// Get total pieces of token.
 		fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result<Option<u128>>;
+
 		fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec<CrossAccountId>>;
 	}
 }
modifiedruntime/common/config/ethereum.rsdiffbeforeafterboth
--- a/runtime/common/config/ethereum.rs
+++ b/runtime/common/config/ethereum.rs
@@ -112,6 +112,7 @@
 }
 
 impl pallet_evm_contract_helpers::Config for Runtime {
+	type Event = Event;
 	type ContractAddress = HelpersContractAddress;
 	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
 }
addedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -0,0 +1,63 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+use crate::{
+	runtime_common::config::pallets::{TreasuryAccountId, RelayChainBlockNumberProvider},
+	Runtime, Balances, BlockNumber, Unique, Event, EvmContractHelpers,
+};
+
+use frame_support::{parameter_types, PalletId};
+use sp_arithmetic::Perbill;
+use up_common::{
+	constants::{UNIQUE, RELAY_DAYS},
+	types::Balance,
+};
+
+#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]
+parameter_types! {
+	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
+	pub const RecalculationInterval: BlockNumber = 20;
+	pub const PendingInterval: BlockNumber = 10;
+	pub const Nominal: Balance = UNIQUE;
+	// pub const Day: BlockNumber = DAYS;
+	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000);
+}
+
+#[cfg(any(feature = "unique-runtime", feature = "quartz-runtime"))]
+parameter_types! {
+	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
+	pub const RecalculationInterval: BlockNumber = RELAY_DAYS;
+	pub const PendingInterval: BlockNumber = 7 * RELAY_DAYS;
+	pub const Nominal: Balance = UNIQUE;
+	// pub const Day: BlockNumber = RELAY_DAYS;
+	pub IntervalIncome: Perbill = Perbill::from_rational(5u32, 10_000);
+}
+
+impl pallet_app_promotion::Config for Runtime {
+	type PalletId = AppPromotionId;
+	type CollectionHandler = Unique;
+	type ContractHandler = EvmContractHelpers;
+	type Currency = Balances;
+	type WeightInfo = pallet_app_promotion::weights::SubstrateWeight<Self>;
+	type TreasuryAccountId = TreasuryAccountId;
+	type RelayBlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
+	type RecalculationInterval = RecalculationInterval;
+	type PendingInterval = PendingInterval;
+	// type Day = Day;
+	type Nominal = Nominal;
+	type IntervalIncome = IntervalIncome;
+	type Event = Event;
+}
modifiedruntime/common/config/pallets/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -40,6 +40,9 @@
 #[cfg(feature = "scheduler")]
 pub mod scheduler;
 
+#[cfg(feature = "app-promotion")]
+pub mod app_promotion;
+
 parameter_types! {
 	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
 	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
modifiedruntime/common/construct_runtime/mod.rsdiffbeforeafterboth
--- a/runtime/common/construct_runtime/mod.rs
+++ b/runtime/common/construct_runtime/mod.rs
@@ -77,12 +77,15 @@
                 #[runtimes(opal)]
                 RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
 
+                #[runtimes(opal)]
+                AppPromotion: pallet_app_promotion::{Pallet, Call, Storage, Event<T>} = 73,
+
                 // Frontier
                 EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 100,
                 Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101,
 
                 EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150,
-                EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151,
+                EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage, Event<T>} = 151,
                 EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152,
                 EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153,
             }
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -189,6 +189,40 @@
                 }
             }
 
+            impl app_promotion_rpc::AppPromotionApi<Block, BlockNumber, CrossAccountId, AccountId> for Runtime {
+                fn total_staked(staker: Option<CrossAccountId>) -> Result<u128, DispatchError> {
+                    #[cfg(not(feature = "app-promotion"))]
+                    return unsupported!();
+
+                    #[cfg(feature = "app-promotion")]
+                    return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_total_staked(staker).unwrap_or_default());
+                }
+
+                fn total_staked_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>, DispatchError> {
+                    #[cfg(not(feature = "app-promotion"))]
+                    return unsupported!();
+
+                    #[cfg(feature = "app-promotion")]
+                    return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_total_staked_per_block(staker));
+                }
+
+                fn pending_unstake(staker: Option<CrossAccountId>) -> Result<u128, DispatchError> {
+                    #[cfg(not(feature = "app-promotion"))]
+                    return unsupported!();
+
+                    #[cfg(feature = "app-promotion")]
+                    return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_pending_unstake(staker));
+                }
+
+                fn pending_unstake_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>, DispatchError> {
+                    #[cfg(not(feature = "app-promotion"))]
+                    return unsupported!();
+
+                    #[cfg(feature = "app-promotion")]
+                    return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_pending_unstake_per_block(staker))
+                }
+            }
+
             impl rmrk_rpc::RmrkApi<
                 Block,
                 AccountId,
@@ -638,6 +672,7 @@
                     list_benchmark!(list, extra, pallet_unique, Unique);
                     list_benchmark!(list, extra, pallet_structure, Structure);
                     list_benchmark!(list, extra, pallet_inflation, Inflation);
+                    list_benchmark!(list, extra, pallet_app_promotion, AppPromotion);
                     list_benchmark!(list, extra, pallet_fungible, Fungible);
                     list_benchmark!(list, extra, pallet_nonfungible, Nonfungible);
 
@@ -693,6 +728,7 @@
                     add_benchmark!(params, batches, pallet_unique, Unique);
                     add_benchmark!(params, batches, pallet_structure, Structure);
                     add_benchmark!(params, batches, pallet_inflation, Inflation);
+                    add_benchmark!(params, batches, pallet_app_promotion, AppPromotion);
                     add_benchmark!(params, batches, pallet_fungible, Fungible);
                     add_benchmark!(params, batches, pallet_nonfungible, Nonfungible);
 
modifiedruntime/opal/Cargo.tomldiffbeforeafterboth
--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -37,6 +37,7 @@
     'pallet-proxy-rmrk-equip/runtime-benchmarks',
     'pallet-unique/runtime-benchmarks',
     'pallet-inflation/runtime-benchmarks',
+    'pallet-app-promotion/runtime-benchmarks',
     'pallet-unique-scheduler/runtime-benchmarks',
     'pallet-xcm/runtime-benchmarks',
     'sp-runtime/runtime-benchmarks',
@@ -82,12 +83,14 @@
     'pallet-base-fee/std',
     'fp-rpc/std',
     'up-rpc/std',
+    'app-promotion-rpc/std',
     'fp-evm-mapping/std',
     'fp-self-contained/std',
     'parachain-info/std',
     'serde',
     'pallet-inflation/std',
     'pallet-configuration/std',
+    'pallet-app-promotion/std',
     'pallet-common/std',
     'pallet-structure/std',
     'pallet-fungible/std',
@@ -122,11 +125,12 @@
     "orml-vesting/std",
 ]
 limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
-opal-runtime = ['refungible', 'scheduler', 'rmrk']
+opal-runtime = ['refungible', 'scheduler', 'rmrk', 'app-promotion']
 
 refungible = []
 scheduler = []
 rmrk = []
+app-promotion = []
 
 ################################################################################
 # Substrate Dependencies
@@ -411,9 +415,11 @@
 derivative = "2.2.0"
 pallet-unique = { path = '../../pallets/unique', default-features = false }
 up-rpc = { path = "../../primitives/rpc", default-features = false }
+app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false}
 rmrk-rpc = { path = "../../primitives/rmrk-rpc", default-features = false }
 fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" }
 pallet-inflation = { path = '../../pallets/inflation', default-features = false }
+pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false }
 up-data-structs = { path = '../../primitives/data-structs', default-features = false }
 pallet-configuration = { default-features = false, path = "../../pallets/configuration" }
 pallet-common = { default-features = false, path = "../../pallets/common" }
modifiedruntime/quartz/Cargo.tomldiffbeforeafterboth
--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -82,6 +82,7 @@
     'pallet-base-fee/std',
     'fp-rpc/std',
     'up-rpc/std',
+    'app-promotion-rpc/std',
     'fp-evm-mapping/std',
     'fp-self-contained/std',
     'parachain-info/std',
@@ -416,8 +417,10 @@
 derivative = "2.2.0"
 pallet-unique = { path = '../../pallets/unique', default-features = false }
 up-rpc = { path = "../../primitives/rpc", default-features = false }
+app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false}
 fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27" }
 pallet-inflation = { path = '../../pallets/inflation', default-features = false }
+pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false }
 up-data-structs = { path = '../../primitives/data-structs', default-features = false }
 pallet-configuration = { default-features = false, path = "../../pallets/configuration" }
 pallet-common = { default-features = false, path = "../../pallets/common" }
modifiedruntime/unique/Cargo.tomldiffbeforeafterboth
--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -83,6 +83,7 @@
     'pallet-base-fee/std',
     'fp-rpc/std',
     'up-rpc/std',
+    'app-promotion-rpc/std',
     'fp-evm-mapping/std',
     'fp-self-contained/std',
     'parachain-info/std',
@@ -409,8 +410,10 @@
 derivative = "2.2.0"
 pallet-unique = { path = '../../pallets/unique', default-features = false }
 up-rpc = { path = "../../primitives/rpc", default-features = false }
+app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false}
 rmrk-rpc = { path = "../../primitives/rmrk-rpc", default-features = false }
 pallet-inflation = { path = '../../pallets/inflation', default-features = false }
+pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false }
 up-data-structs = { path = '../../primitives/data-structs', default-features = false }
 pallet-configuration = { default-features = false, path = "../../pallets/configuration" }
 pallet-common = { default-features = false, path = "../../pallets/common" }
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -30,6 +30,7 @@
     "testEth": "mocha --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'",
     "testEthMarketplace": "mocha --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'",
     "testEthNesting": "mocha --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'",
+    "testEthPayable": "mocha --timeout 9999999 -r ts-node/register './**/eth/payable.test.ts'",
     "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",
     "loadTransfer": "ts-node src/transfer.nload.ts",
     "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",
@@ -64,6 +65,7 @@
     "testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts",
     "testAdminTransferAndBurn": "mocha --timeout 9999999 -r ts-node/register ./**/adminTransferAndBurn.test.ts",
     "testSetMintPermission": "mocha --timeout 9999999 -r ts-node/register ./**/setMintPermission.test.ts",
+    "testSetPublicAccessMode": "mocha --timeout 9999999 -r ts-node/register ./**/setPublicAccessMode.test.ts",
     "testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts",
     "testContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/contractSponsoring.test.ts",
     "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts",
@@ -71,7 +73,6 @@
     "testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts",
     "testSetOffchainSchema": "mocha --timeout 9999999 -r ts-node/register ./**/setOffchainSchema.test.ts",
     "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",
-    "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",
     "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",
     "testScheduler": "mocha --timeout 9999999 -r ts-node/register ./**/scheduler.test.ts",
     "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts",
@@ -80,10 +81,12 @@
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
     "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
     "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",
-    "testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts",
+    "testEthCreateNFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createNFTCollection.test.ts",
+    "testEthCreateRFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createRFTCollection.test.ts",
     "testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts",
     "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts",
     "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",
+    "testPromotion": "mocha --timeout 9999999 -r ts-node/register ./**/app-promotion.test.ts",
     "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",
     "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
addedtests/src/app-promotion.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/app-promotion.test.ts
@@ -0,0 +1,800 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {
+  normalizeAccountId,
+  getModuleNames,
+  Pallets,
+} from './util/helpers';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import {usingPlaygrounds} from './util/playgrounds';
+
+import {encodeAddress} from '@polkadot/util-crypto';
+import {stringToU8a} from '@polkadot/util';
+import {SponsoringMode, contractHelpers, createEthAccountWithBalance, deployFlipper, itWeb3, transferBalanceToEth} from './eth/util/helpers';
+import {DevUniqueHelper} from './util/playgrounds/unique.dev';
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let palletAdmin: IKeyringPair;
+let nominal: bigint;
+const palletAddress = calculatePalleteAddress('appstake');
+let accounts: IKeyringPair[] = [];
+const LOCKING_PERIOD = 20n; // 20 blocks of relay
+const UNLOCKING_PERIOD = 10n; // 20 blocks of parachain
+const rewardAvailableInBlock = (stakedInBlock: bigint) => (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);
+
+const beforeEach = async (context: Mocha.Context) => {
+  await usingPlaygrounds(async (helper, privateKey) => {
+    if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) context.skip();
+    alice = privateKey('//Alice');
+    palletAdmin = privateKey('//Charlie'); // TODO use custom address
+    await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));
+    nominal = helper.balance.getOneTokenNominal();
+    await helper.balance.transferToSubstrate(alice, palletAdmin.address, 1000n * nominal);
+    await helper.balance.transferToSubstrate(alice, palletAddress, 1000n * nominal);
+    accounts = await helper.arrange.createCrowd(100, 1000n, alice); // create accounts-pool to speed up tests
+  });
+};
+
+describe('app-promotions.stake extrinsic', () => {
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  it('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];
+      const totalStakedBefore = await helper.staking.getTotalStaked();
+
+      // Minimum stake amount is 100:
+      await expect(helper.staking.stake(staker, 100n * nominal - 1n)).to.be.eventually.rejected;
+      await helper.staking.stake(staker, 100n * nominal);
+
+      // Staker balance is: miscFrozen: 100, feeFrozen: 100, reserved: 0n...
+      // ...so he can not transfer 900
+      expect (await helper.balance.getSubstrateFull(staker.address)).to.contain({miscFrozen: 100n * nominal, feeFrozen: 100n * nominal, reserved: 0n});
+      await expect(helper.balance.transferToSubstrate(staker, recepient.address, 900n * nominal)).to.be.rejected;
+      
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(100n * nominal);
+      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
+      // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo? 
+      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased 
+
+      
+      await helper.staking.stake(staker, 200n * nominal);
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal);
+      expect((await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map((x) => x[1])).to.be.deep.equal([100n * nominal, 200n * nominal]);
+    });
+  });
+
+  it('should allow to create maximum 10 stakes for account', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [staker] = await helper.arrange.createAccounts([2000n], alice);
+      for (let i = 0; i < 10; i++) {
+        await helper.staking.stake(staker, 100n * nominal);
+      }
+
+      // can have 10 stakes
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);
+      expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);
+
+      await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejected;
+
+      // After unstake can stake again
+      await helper.staking.unstake(staker);
+      await helper.staking.stake(staker, 100n * nominal);
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);
+    });
+  });
+  
+  it('should reject transaction if stake amount is more than total free balance minus frozen', async () => {
+    await usingPlaygrounds(async helper => { 
+      const staker = accounts.pop()!;
+
+      // Can't stake full balance because Alice needs to pay some fee
+      await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.eventually.rejected;
+      await helper.staking.stake(staker, 500n * nominal);
+
+      // Can't stake 500 tkn because Alice has Less than 500 transferable;
+      await expect(helper.staking.stake(staker, 500n * nominal)).to.be.eventually.rejected; 
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(500n * nominal);
+    });
+  });
+  
+  it('for different accounts in one block is possible', async () => {
+    await usingPlaygrounds(async helper => {
+      const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];
+      
+      const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));
+      await expect(Promise.all(crowdStartsToStake)).to.be.eventually.fulfilled;
+
+      const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));
+      expect(crowdStakes).to.deep.equal([100n * nominal, 100n * nominal, 100n * nominal, 100n * nominal]);
+    });
+  });
+});
+
+describe('unstake balance extrinsic', () => {  
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  it('should change balance state from "frozen" to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async () => {
+    await usingPlaygrounds(async helper => {
+      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];
+      const totalStakedBefore = await helper.staking.getTotalStaked();
+      await helper.staking.stake(staker, 900n * nominal);
+      await helper.staking.unstake(staker);
+
+      // Right after unstake balance is reserved
+      // Staker can not transfer 
+      expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 900n * nominal, miscFrozen: 0n, feeFrozen: 0n});
+      await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejected;
+      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(900n * nominal);
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
+      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);
+    });
+  });
+
+  it('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const staker = accounts.pop()!;
+      await helper.staking.stake(staker, 100n * nominal);
+      await helper.staking.unstake(staker);
+      const unstakedInBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address}))[0][0];
+
+      // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n
+      await helper.wait.forParachainBlockNumber(unstakedInBlock);
+      expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});
+      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
+
+      // staker can transfer:
+      await helper.balance.transferToSubstrate(staker, alice.address, 998n * nominal);
+      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);
+    });
+  });
+
+  it('should successfully unstake multiple stakes', async () => {
+    await usingPlaygrounds(async helper => {
+      const staker = accounts.pop()!;
+      await helper.staking.stake(staker, 100n * nominal);
+      await helper.staking.stake(staker, 200n * nominal);
+      await helper.staking.stake(staker, 300n * nominal);
+
+      // staked: [100, 200, 300]; unstaked: 0
+      let pendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});
+      let unstakedPerBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).map(stake => stake[1]);
+      let stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(stake => stake[1]);
+      expect(pendingUnstake).to.be.deep.equal(0n);
+      expect(unstakedPerBlock).to.be.deep.equal([]);
+      expect(stakedPerBlock).to.be.deep.equal([100n * nominal, 200n * nominal, 300n * nominal]);
+     
+      // Can unstake multiple stakes
+      await helper.staking.unstake(staker);
+      const unstakingBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address}))[0][0];
+      pendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});
+      unstakedPerBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).map(stake => stake[1]);
+      stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(stake => stake[1]);
+      expect(pendingUnstake).to.be.equal(600n * nominal);
+      expect(stakedPerBlock).to.be.deep.equal([]);
+      expect(unstakedPerBlock).to.be.deep.equal([600n * nominal]);
+
+      expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 600n * nominal, feeFrozen: 0n, miscFrozen: 0n});
+      await helper.wait.forParachainBlockNumber(unstakingBlock);
+      expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});
+      expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
+    });
+  });
+
+  it('should not have any effects if no active stakes', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const staker = accounts.pop()!;
+      
+      // unstake has no effect if no stakes at all
+      await helper.staking.unstake(staker);
+      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);
+      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper
+
+      // TODO stake() unstake() waitUnstaked() unstake();
+
+      // can't unstake if there are only pendingUnstakes
+      await helper.staking.stake(staker, 100n * nominal);
+      await helper.staking.unstake(staker);
+      await helper.staking.unstake(staker);
+
+      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);
+      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
+    });
+  });
+
+  it('should keep different unlocking block for each unlocking stake', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const staker = accounts.pop()!;
+      await helper.staking.stake(staker, 100n * nominal);
+      await helper.staking.unstake(staker);
+      await helper.staking.stake(staker, 120n * nominal);
+      await helper.staking.unstake(staker);
+
+      const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
+      expect(unstakingPerBlock).has.length(2);
+      expect(unstakingPerBlock[0][1]).to.equal(100n * nominal);
+      expect(unstakingPerBlock[1][1]).to.equal(120n * nominal);
+    });
+  });
+
+  it('should be possible for different accounts in one block', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
+
+      await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
+      await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));
+
+      await Promise.all(stakers.map(async (staker) => {
+        expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);
+        expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
+      }));
+    });
+  });
+});
+
+describe('Admin adress', () => {
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  it('can be set by sudo only', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const nonAdmin = accounts.pop()!;
+      // nonAdmin can not set admin not from himself nor as a sudo
+      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.eventually.rejected;
+      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.eventually.rejected;
+
+      // Alice can
+      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.eventually.fulfilled;
+    });
+  });
+  
+  it('can be any valid CrossAccountId', async () => {
+    // We are not going to set an eth address as a sponsor,
+    // but we do want to check, it doesn't break anything;
+    await usingPlaygrounds(async (helper) => {
+      const account = accounts.pop()!;
+      const ethAccount = helper.address.substrateToEth(account.address); 
+      // Alice sets Ethereum address as a sudo. Then Substrate address back...
+      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.eventually.fulfilled;
+      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.eventually.fulfilled;
+      
+      // ...It doesn't break anything;
+      const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      await expect(helper.signTransaction(account, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.eventually.rejected;
+    });
+  });
+
+  it('can be reassigned', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
+      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      
+      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(oldAdmin))))).to.be.eventually.fulfilled;
+      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(newAdmin))))).to.be.eventually.fulfilled;
+      await expect(helper.signTransaction(oldAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.eventually.rejected;
+      
+      await expect(helper.signTransaction(newAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.eventually.fulfilled;
+    });
+  });
+});
+
+describe('App-promotion collection sponsoring', () => {
+  before(async function () {
+    await beforeEach(this);
+    await usingPlaygrounds(async (helper) => {
+      const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}));
+      await helper.signTransaction(alice, tx);
+    });
+  });
+
+  it('should actually sponsor transactions', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
+      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});
+      const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));
+      const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);
+
+      await token.transfer(tokenSender, {Substrate: receiver.address});
+      expect (await token.getOwner()).to.be.deep.equal({Substrate: receiver.address});
+      const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);
+
+      // senders balance the same, transaction has sponsored
+      expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);
+      expect (palletBalanceBefore > palletBalanceAfter).to.be.true;
+    });
+  });
+
+  it('can not be set by non admin', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];
+
+      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      
+      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.eventually.rejected;
+      expect((await collection.getData())?.raw.sponsorship).to.equal('Disabled');
+    });
+  });
+
+  it('should set pallet address as confirmed admin', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];
+      
+      // Can set sponsoring for collection without sponsor
+      const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.eventually.fulfilled;
+      expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});
+
+      // Can set sponsoring for collection with unconfirmed sponsor
+      const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});
+      expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address});
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.eventually.fulfilled;
+      expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});
+
+      // Can set sponsoring for collection with confirmed sponsor
+      const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});
+      await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor);
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.eventually.fulfilled;
+      expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});
+    });
+  });
+
+  it('can be overwritten by collection owner', async () => {    
+    await usingPlaygrounds(async (helper) => {
+      const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];
+      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      const collectionId = collection.collectionId;
+      
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;
+      
+      // Collection limits still can be changed by the owner
+      expect(await collection.setLimits(collectionOwner, {sponsorTransferTimeout: 0})).to.be.true;
+      expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);
+      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});
+
+      // Collection sponsor can be changed too
+      expect((await collection.setSponsor(collectionOwner, newSponsor.address))).to.be.true;
+      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: newSponsor.address});
+    });
+  });
+  
+  it('should not overwrite collection limits set by the owner earlier', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};
+      const collectionWithLimits = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});
+
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.eventually.fulfilled;
+      expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);
+    });
+  });
+  
+  it('should reject transaction if collection doesn\'t exist', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const collectionOwner = accounts.pop()!;
+      
+      // collection has never existed
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.eventually.rejected;
+      // collection has been burned
+      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      await collection.burn(collectionOwner);
+
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.eventually.rejected;
+    });
+  });
+});
+
+describe('app-promotion stopSponsoringCollection', () => {
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  it('can not be called by non-admin', async () => {    
+    await usingPlaygrounds(async (helper) => {
+      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];
+      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.eventually.fulfilled;
+      
+      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.eventually.rejected;
+      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});
+    });
+  });
+
+  it('should set sponsoring as disabled', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];
+      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});
+      const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});
+      
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId));
+      
+      expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');
+
+      // Transactions are not sponsored anymore:
+      const ownerBalanceBefore = await helper.balance.getSubstrate(collectionOwner.address);
+      await token.transfer(collectionOwner, {Substrate: recepient.address});
+      const ownerBalanceAfter = await helper.balance.getSubstrate(collectionOwner.address);
+      expect(ownerBalanceAfter < ownerBalanceBefore).to.be.equal(true);
+    });
+  });
+
+  it('should not affect collection which is not sponsored by pallete', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const collectionOwner = accounts.pop()!;
+      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});
+      await collection.confirmSponsorship(collectionOwner);
+      
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.eventually.rejected;
+      
+      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: collectionOwner.address});
+    });
+  });
+
+  it('should reject transaction if collection does not exist', async () => {    
+    await usingPlaygrounds(async (helper) => {
+      const collectionOwner = accounts.pop()!;
+      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
+      
+      await collection.burn(collectionOwner);
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.eventually.rejected;
+      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(999999999))).to.be.eventually.rejected;
+    });
+  });
+});
+
+describe('app-promotion contract sponsoring', () => {
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  itWeb3('should set palletes address as a sponsor', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      const contractMethods = contractHelpers(web3, contractOwner);
+
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address));
+      
+      expect(await contractMethods.methods.hasSponsor(flipper.options.address).call()).to.be.true;  
+      expect((await api.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  
+      expect((await api.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({
+        confirmed: {
+          substrate: palletAddress,
+        },
+      });
+    });
+  });
+
+  itWeb3('should overwrite sponsoring mode and existed sponsor', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      const contractMethods = contractHelpers(web3, contractOwner);
+
+      await expect(contractMethods.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;
+
+      // Contract is self sponsored
+      expect((await api.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.be.deep.equal({
+        confirmed: {
+          ethereum: flipper.options.address.toLowerCase(),
+        },
+      });
+
+      // set promotion sponsoring
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address));
+
+      // new sponsor is pallet address
+      expect(await contractMethods.methods.hasSponsor(flipper.options.address).call()).to.be.true;  
+      expect((await api.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  
+      expect((await api.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({
+        confirmed: {
+          substrate: palletAddress,
+        },
+      });
+    });
+  });
+
+  itWeb3('can be overwritten by contract owner', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {      
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      const contractMethods = contractHelpers(web3, contractOwner);
+
+      // contract sponsored by pallet
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address));
+
+      // owner sets self sponsoring
+      await expect(contractMethods.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;
+
+      expect(await contractMethods.methods.hasSponsor(flipper.options.address).call()).to.be.true;  
+      expect((await api.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  
+      expect((await api.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({
+        confirmed: {
+          ethereum: flipper.options.address.toLowerCase(),
+        },
+      });
+    });
+  });
+
+  itWeb3('can not be set by non admin', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {      
+      const nonAdmin = accounts.pop()!;
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      const contractMethods = contractHelpers(web3, contractOwner);
+
+      await expect(contractMethods.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;
+
+      // nonAdmin calls sponsorContract
+      await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address))).to.be.rejected;
+
+      // contract still self-sponsored 
+      expect((await api.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({
+        confirmed: {
+          ethereum: flipper.options.address.toLowerCase(),
+        },
+      });
+    });
+
+    itWeb3('should be rejected for non-contract address', async ({api, web3, privateKeyWrapper}) => {
+      await usingPlaygrounds(async (helper) => {
+
+      });
+    });
+  });
+
+  itWeb3('should actually sponsor transactions', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {      
+      const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      const contractHelper = contractHelpers(web3, contractOwner);
+      await contractHelper.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: contractOwner});
+      await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});
+      await transferBalanceToEth(api, alice, flipper.options.address, 1000n);
+
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address));
+      await flipper.methods.flip().send({from: caller});
+      expect(await flipper.methods.getValue().call()).to.be.true;
+
+      const callerBalance = await helper.balance.getEthereum(caller);
+      const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);
+
+      expect(callerBalance).to.be.equal(1000n * nominal);
+      expect(1000n * nominal > contractBalanceAfter).to.be.true;
+    });
+  });
+});
+
+describe('app-promotion stopSponsoringContract', () => {  
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  itWeb3('should remove pallet address from contract sponsors', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {      
+      const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      await transferBalanceToEth(api, alice, flipper.options.address);
+      const contractHelper = contractHelpers(web3, contractOwner);
+      await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address));
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.stopSponsoringContract(flipper.options.address));
+
+      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.false;  
+      expect((await api.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  
+      expect((await api.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({
+        disabled: null,
+      });
+
+      await flipper.methods.flip().send({from: caller});
+      expect(await flipper.methods.getValue().call()).to.be.true;
+
+      const callerBalance = await helper.balance.getEthereum(caller);
+      const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);
+
+      // caller payed for call
+      expect(1000n * nominal > callerBalance).to.be.true;
+      expect(contractBalanceAfter).to.be.equal(1000n * nominal);
+    });
+  });
+
+  itWeb3('can not be called by non-admin', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {      
+      const nonAdmin = accounts.pop()!;
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+
+      await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorContract(flipper.options.address));
+      await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.stopSponsoringContract(flipper.options.address))).to.be.rejected;
+    });
+  });
+
+  itWeb3('should not affect a contract which is not sponsored by pallete', async ({api, web3, privateKeyWrapper}) => {
+    await usingPlaygrounds(async (helper) => {      
+      const nonAdmin = accounts.pop()!;
+      const contractOwner = (await createEthAccountWithBalance(api, web3, privateKeyWrapper)).toLowerCase();
+      const flipper = await deployFlipper(web3, contractOwner);
+      const contractHelper = contractHelpers(web3, contractOwner);
+      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;
+
+      await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.stopSponsoringContract(flipper.options.address))).to.be.rejected;
+    });
+  });
+});
+
+describe('app-promotion rewards', () => {
+  before(async function () {
+    await beforeEach(this);
+  });
+
+  it('can not be called by non admin', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const nonAdmin = accounts.pop()!;
+      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.payoutStakers(100))).to.be.rejected;
+    });
+  });
+
+  it('should credit 0.05% for staking period', async () => {    
+    await usingPlaygrounds(async helper => {
+      const staker = accounts.pop()!;
+
+      await waitPromotionPeriodDoesntEnd(helper);
+      
+      await helper.staking.stake(staker, 100n * nominal);
+      await helper.staking.stake(staker, 200n * nominal);
+
+      // wait rewards are available:
+      const stakedInBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address}))[1][0];
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock));
+
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.payoutStakers(100));
+
+      const totalStakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(s => s[1]);
+      expect(totalStakedPerBlock).to.be.deep.equal([calculateIncome(100n * nominal, 10n), calculateIncome(200n * nominal, 10n)]);
+    });
+  });
+
+  it('shoud be paid for more than one period if payments was missed', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const staker = accounts.pop()!;
+
+      await helper.staking.stake(staker, 100n * nominal);
+      // wait for two rewards are available:
+      const stakedInBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address}))[0][0];
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock) + LOCKING_PERIOD);
+
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.payoutStakers(100));
+      const stakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
+      const frozenBalanceShouldBe = calculateIncome(100n * nominal, 10n, 2);
+      expect(stakedPerBlock[0][1]).to.be.equal(frozenBalanceShouldBe);
+
+      const stakerFullBalance = await helper.balance.getSubstrateFull(staker.address);
+
+      expect(stakerFullBalance).to.contain({reserved: 0n, feeFrozen: frozenBalanceShouldBe, miscFrozen: frozenBalanceShouldBe});
+    });
+  });
+  
+  it('should not be credited for unstaked (reserved) balance', async () => {
+    await usingPlaygrounds(async helper => {
+      // staker unstakes before rewards has been payed
+      const staker = accounts.pop()!;
+      await helper.staking.stake(staker, 100n * nominal);
+      const stakedInBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address}))[0][0];
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock) + LOCKING_PERIOD);
+      await helper.staking.unstake(staker);
+      
+      // so he did not receive any rewards
+      const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.payoutStakers(100));
+      const totalBalanceAfter = await helper.balance.getSubstrate(staker.address);
+
+      expect(totalBalanceBefore).to.be.equal(totalBalanceAfter);
+    });
+  });
+  
+  it('should bring compound interest', async () => {
+    await usingPlaygrounds(async helper => {
+      const staker = accounts.pop()!;
+            
+      await helper.staking.stake(staker, 100n * nominal);
+
+      const stakedInBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address}))[0][0];
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock));
+      
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.payoutStakers(100));
+      let totalStakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(s => s[1]);
+      expect(totalStakedPerBlock).to.deep.equal([calculateIncome(100n * nominal, 10n)]);
+      
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock) + LOCKING_PERIOD);
+      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.payoutStakers(100));
+      totalStakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(s => s[1]);
+      expect(totalStakedPerBlock).to.deep.equal([calculateIncome(100n * nominal, 10n, 2)]);      
+    });
+  });
+
+  it.skip('can be paid 1000 rewards in a time', async () => {
+    // all other stakes should be unstaked
+    await usingPlaygrounds(async (helper) => {
+      const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, alice);
+
+      // stakers stakes 10 times each
+      for (let i = 0; i < 10; i++) {
+        await Promise.all(oneHundredStakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
+      }
+      await helper.wait.newBlocks(40);
+      const result = await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.payoutStakers(100));
+    });
+  });
+
+  it.skip('can handle 40.000 rewards', async () => {
+    await usingPlaygrounds(async (helper) => {
+      const [donor] = await helper.arrange.createAccounts([7_000_000n], alice);
+      const crowdStakes = async () => {
+        // each account in the crowd stakes 2 times
+        const crowd = await helper.arrange.createCrowd(500, 300n, donor);
+        await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));
+        await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));
+        // 
+      };
+
+      for (let i = 0; i < 40; i++) {
+        await crowdStakes();
+      }
+
+      // TODO pay rewards for some period
+    });
+  });
+});
+
+function calculatePalleteAddress(palletId: any) {
+  const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));
+  return encodeAddress(address);
+}
+
+function calculateIncome(base: bigint, calcPeriod: bigint, iter = 0): bigint {
+  const DAY = 7200n;
+  const ACCURACY = 1_000_000_000n;
+  const income = base + base * (ACCURACY * (calcPeriod * 5n) / (10_000n * DAY)) / ACCURACY ;
+  
+  if (iter > 1) {
+    return calculateIncome(income, calcPeriod, iter - 1);
+  } else return income;
+}
+
+// Wait while promotion period less than specified block, to avoid boundary cases
+// 0 if this should be the beginning of the period.
+async function waitPromotionPeriodDoesntEnd(helper: DevUniqueHelper, waitBlockLessThan = LOCKING_PERIOD / 3n) {
+  const relayBlockNumber = (await helper.api!.query.parachainSystem.validationData()).value.relayParentNumber.toNumber(); // await helper.chain.getLatestBlockNumber();
+  const currentPeriodBlock = BigInt(relayBlockNumber) % LOCKING_PERIOD;
+
+  if (currentPeriodBlock > waitBlockLessThan) {
+    await helper.wait.forRelayBlockNumber(BigInt(relayBlockNumber) + LOCKING_PERIOD - currentPeriodBlock);
+  }
+}
modifiedtests/src/approve.test.tsdiffbeforeafterboth
--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -79,11 +79,12 @@
 
   it('[nft] Remove approval by using 0 amount', async () => {
     await usingPlaygrounds(async (helper) => {
-      const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+      const collection = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+      const collectionId = collection.collectionId;
       const {tokenId} = await helper.nft.mintToken(alice, {collectionId: collectionId, owner: alice.address});
       await helper.nft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address});
       expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.true;
-      await helper.nft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 0n);
+      await helper.api?.tx.unique.approve({Substrate: bob.address}, collectionId, tokenId, 0).signAndSend(alice);
       expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.false;
     });
   });
@@ -97,7 +98,7 @@
       const amountBefore = await helper.ft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountBefore).to.be.equal(BigInt(1));
 
-      await helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 0n);
+      await helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 0n);
       const amountAfter = await helper.ft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountAfter).to.be.equal(BigInt(0));
     });
@@ -111,7 +112,7 @@
       const amountBefore = await helper.rft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountBefore).to.be.equal(BigInt(100));
 
-      await helper.rft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 0n);
+      await helper.rft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 0n);
       const amountAfter = await helper.rft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountAfter).to.be.equal(BigInt(0));
     });
@@ -289,7 +290,7 @@
       const {collectionId} = await helper.ft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}, 0);
       await helper.ft.mintTokens(alice, collectionId, alice.address, 10n);
       const tokenId = await helper.ft.getLastTokenId(collectionId);
-      await helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 10n);
+      await helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 10n);
 
       const charlieBefore = await helper.ft.getBalance(collectionId, {Substrate: charlie.address});
       await helper.ft.transferTokenFrom(bob, collectionId, tokenId, {Substrate: alice.address}, {Substrate: charlie.address}, 2n);
@@ -321,7 +322,7 @@
       const {tokenId} = await helper.nft.mintToken(alice, {collectionId: collectionId, owner: alice.address});
       await helper.nft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address});
       expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.true;
-      await helper.nft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 0n);
+      await helper.api?.tx.unique.approve({Substrate: bob.address}, collectionId, tokenId, 0).signAndSend(alice);
       expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.false;
       const transferTokenFromTx = async () => helper.nft.transferTokenFrom(bob, collectionId, tokenId, {Substrate: bob.address}, {Substrate: bob.address});
       await expect(transferTokenFromTx()).to.be.rejected;
@@ -337,7 +338,7 @@
       const amountBefore = await helper.ft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountBefore).to.be.equal(BigInt(1));
 
-      await helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 0n);
+      await helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 0n);
       const amountAfter = await helper.ft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountAfter).to.be.equal(BigInt(0));
 
@@ -354,7 +355,7 @@
       const amountBefore = await helper.rft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountBefore).to.be.equal(BigInt(100));
 
-      await helper.rft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 0n);
+      await helper.rft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 0n);
       const amountAfter = await helper.rft.getTokenApprovedPieces(collectionId, tokenId, {Substrate: bob.address}, {Substrate: alice.address});
       expect(amountAfter).to.be.equal(BigInt(0));
 
@@ -379,8 +380,7 @@
     await usingPlaygrounds(async (helper) => {
       const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
       const {tokenId} = await helper.nft.mintToken(alice, {collectionId: collectionId, owner: bob.address});
-      const approveTx = async () => helper.nft.approveToken(bob, collectionId, tokenId, {Substrate: charlie.address}, undefined, 2n);
-      await expect(approveTx()).to.be.rejected;
+      await helper.api?.tx.unique.approve({Substrate: charlie.address}, collectionId, tokenId, 2).signAndSend(bob);
       expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: charlie.address})).to.be.false;
     });
   });
@@ -390,7 +390,7 @@
       const {collectionId} = await helper.ft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}, 0);
       await helper.ft.mintTokens(alice, collectionId, alice.address, 10n);
       const tokenId = await helper.ft.getLastTokenId(collectionId);
-      const approveTx = async () => helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 11n);
+      const approveTx = async () => helper.ft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 11n);
       await expect(approveTx()).to.be.rejected;
     });
   });
@@ -399,7 +399,7 @@
     await usingPlaygrounds(async (helper) => {
       const {collectionId} = await helper.rft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
       const {tokenId} = await helper.rft.mintToken(alice, {collectionId: collectionId, owner: alice.address, pieces: 100n});
-      const approveTx = async () => helper.rft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, undefined, 101n);
+      const approveTx = async () => helper.rft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}, 101n);
       await expect(approveTx()).to.be.rejected;
     });
   });
@@ -658,9 +658,9 @@
       const {collectionId} = await helper.rft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
       const {tokenId} = await helper.rft.mintToken(alice, {collectionId: collectionId, owner: alice.address, pieces: 100n});
       await helper.rft.transferToken(alice, collectionId, tokenId, {Substrate: bob.address}, 100n);
-      await helper.rft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, undefined, 100n);
+      await helper.rft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, 100n);
 
-      const approveTx = async () => helper.rft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, undefined, 101n);
+      const approveTx = async () => helper.rft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, 101n);
       await expect(approveTx()).to.be.rejected;
     });
   });
@@ -672,8 +672,8 @@
       const tokenId = await helper.ft.getLastTokenId(collectionId);
 
       await helper.ft.transferToken(alice, collectionId, tokenId, {Substrate: bob.address}, 10n);
-      await helper.ft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, undefined, 10n);
-      const approveTx = async () => helper.ft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, undefined, 11n);
+      await helper.ft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, 10n);
+      const approveTx = async () => helper.ft.approveToken(bob, collectionId, tokenId, {Substrate: alice.address}, 11n);
       await expect(approveTx()).to.be.rejected;
     });
   });
modifiedtests/src/eth/api/ContractHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -12,9 +12,19 @@
 	function supportsInterface(bytes4 interfaceID) external view returns (bool);
 }
 
+/// @dev inlined interface
+interface ContractHelpersEvents {
+	event ContractSponsorSet(address indexed contractAddress, address sponsor);
+	event ContractSponsorshipConfirmed(
+		address indexed contractAddress,
+		address sponsor
+	);
+	event ContractSponsorRemoved(address indexed contractAddress);
+}
+
 /// @title Magic contract, which allows users to reconfigure other contracts
 /// @dev the ERC-165 identifier for this interface is 0xd77fab70
-interface ContractHelpers is Dummy, ERC165 {
+interface ContractHelpers is Dummy, ERC165, ContractHelpersEvents {
 	/// Get user, which deployed specified contract
 	/// @dev May return zero address in case if contract is deployed
 	///  using uniquenetwork evm-migration pallet, or using other terms not
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -15,7 +15,7 @@
 
 import {expect} from 'chai';
 import privateKey from '../substrate/privateKey';
-import { UNIQUE } from '../util/helpers';
+import {UNIQUE} from '../util/helpers';
 import {
   createEthAccount,
   createEthAccountWithBalance, 
@@ -24,7 +24,6 @@
   getCollectionAddressFromResult, 
   itWeb3,
   recordEthFee,
-  subToEth,
 } from './util/helpers';
 
 describe('Add collection admins', () => {
@@ -37,7 +36,7 @@
       .send();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
 
-    const newAdmin = await createEthAccount(web3);
+    const newAdmin = createEthAccount(web3);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
     const adminList = await api.rpc.unique.adminlist(collectionId);
@@ -92,7 +91,7 @@
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     await collectionEvm.methods.addCollectionAdmin(admin).send();
     
-    const user = await createEthAccount(web3);
+    const user = createEthAccount(web3);
     await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: admin}))
       .to.be.rejectedWith('NoPermission');
 
@@ -114,7 +113,7 @@
     const notAdmin = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     
-    const user = await createEthAccount(web3);
+    const user = createEthAccount(web3);
     await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: notAdmin}))
       .to.be.rejectedWith('NoPermission');
 
@@ -175,7 +174,7 @@
       .send();
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
 
-    const newAdmin = await createEthAccount(web3);
+    const newAdmin = createEthAccount(web3);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
     await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
     {
@@ -226,7 +225,7 @@
 
     const admin0 = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     await collectionEvm.methods.addCollectionAdmin(admin0).send();
-    const admin1 = await createEthAccount(web3);
+    const admin1 = createEthAccount(web3);
     await collectionEvm.methods.addCollectionAdmin(admin1).send();
 
     await expect(collectionEvm.methods.removeCollectionAdmin(admin1).call({from: admin0}))
@@ -253,7 +252,7 @@
 
     const admin = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     await collectionEvm.methods.addCollectionAdmin(admin).send();
-    const notAdmin = await createEthAccount(web3);
+    const notAdmin = createEthAccount(web3);
 
     await expect(collectionEvm.methods.removeCollectionAdmin(admin).call({from: notAdmin}))
       .to.be.rejectedWith('NoPermission');
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -1,5 +1,5 @@
 import {addToAllowListExpectSuccess, bigIntToSub, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers';
-import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub, subToEth} 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';
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/contractSponsoring.test.ts
+++ b/tests/src/eth/contractSponsoring.test.ts
@@ -15,6 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {expect} from 'chai';
+import { expectSubstrateEventsAtBlock } from '../util/helpers';
 import {
   contractHelpers,
   createEthAccountWithBalance,
@@ -24,6 +25,7 @@
   SponsoringMode,
   createEthAccount,
   ethBalanceViaSub,
+  normalizeEvents,
 } from './util/helpers';
 
 describe('Sponsoring EVM contracts', () => {
@@ -36,6 +38,41 @@
     expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
   });
 
+  itWeb3('Set self sponsored events', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const flipper = await deployFlipper(web3, owner);
+    const helpers = contractHelpers(web3, owner);
+    
+    const result = await helpers.methods.selfSponsoredEnable(flipper.options.address).send();
+    // console.log(result);
+    const ethEvents = normalizeEvents(result.events);
+    expect(ethEvents).to.be.deep.equal([
+      {
+        address: flipper.options.address,
+        event: 'ContractSponsorSet',
+        args: {
+          contractAddress: flipper.options.address,
+          sponsor: flipper.options.address,
+        },
+      },
+      {
+        address: flipper.options.address,
+        event: 'ContractSponsorshipConfirmed',
+        args: {
+          contractAddress: flipper.options.address,
+          sponsor: flipper.options.address,
+        },
+      },
+    ]);
+
+    await expectSubstrateEventsAtBlock(
+      api, 
+      result.blockNumber,
+      'evmContractHelpers',
+      ['ContractSponsorSet','ContractSponsorshipConfirmed'],
+    );
+  });
+
   itWeb3('Self sponsored can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -75,6 +112,33 @@
     expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true;
   });
   
+  itWeb3('Set sponsor event', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const flipper = await deployFlipper(web3, owner);
+    const helpers = contractHelpers(web3, owner);
+    
+    const result = await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
+    const events = normalizeEvents(result.events);
+    expect(events).to.be.deep.equal([
+      {
+        address: flipper.options.address,
+        event: 'ContractSponsorSet',
+        args: {
+          contractAddress: flipper.options.address,
+          sponsor: sponsor,
+        },
+      },
+    ]);
+
+    await expectSubstrateEventsAtBlock(
+      api, 
+      result.blockNumber,
+      'evmContractHelpers',
+      ['ContractSponsorSet'],
+    );
+  });
+  
   itWeb3('Sponsor can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -97,6 +161,33 @@
     expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
   });
 
+  itWeb3('Confirm sponsorship event', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const flipper = await deployFlipper(web3, owner);
+    const helpers = contractHelpers(web3, owner);
+    await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;
+    const result = await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
+    const events = normalizeEvents(result.events);
+    expect(events).to.be.deep.equal([
+      {
+        address: flipper.options.address,
+        event: 'ContractSponsorshipConfirmed',
+        args: {
+          contractAddress: flipper.options.address,
+          sponsor: sponsor,
+        },
+      },
+    ]);
+
+    await expectSubstrateEventsAtBlock(
+      api, 
+      result.blockNumber,
+      'evmContractHelpers',
+      ['ContractSponsorshipConfirmed'],
+    );
+  });
+
   itWeb3('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -160,6 +251,35 @@
     expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
   });
 
+  itWeb3('Remove sponsor event', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const flipper = await deployFlipper(web3, owner);
+    const helpers = contractHelpers(web3, owner);
+
+    await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
+    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
+    
+    const result = await helpers.methods.removeSponsor(flipper.options.address).send();
+    const events = normalizeEvents(result.events);
+    expect(events).to.be.deep.equal([
+      {
+        address: flipper.options.address,
+        event: 'ContractSponsorRemoved',
+        args: {
+          contractAddress: flipper.options.address,
+        },
+      },
+    ]);
+
+    await expectSubstrateEventsAtBlock(
+      api, 
+      result.blockNumber,
+      'evmContractHelpers',
+      ['ContractSponsorRemoved'],
+    );
+  });
+
   itWeb3('Sponsor can not be removed by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -181,7 +181,7 @@
   });
   
   itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => {
-    const owner = await createEthAccount(web3);
+    const owner = createEthAccount(web3);
     const helper = evmCollectionHelpers(web3, owner);
     const collectionName = 'A';
     const description = 'A';
@@ -194,7 +194,7 @@
 
   itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const notOwner = await createEthAccount(web3);
+    const notOwner = createEthAccount(web3);
     const collectionHelpers = evmCollectionHelpers(web3, owner);
     const result = await collectionHelpers.methods.createNonfungibleCollection('A', 'A', 'A').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -189,7 +189,7 @@
   });
   
   itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => {
-    const owner = await createEthAccount(web3);
+    const owner = createEthAccount(web3);
     const helper = evmCollectionHelpers(web3, owner);
     const collectionName = 'A';
     const description = 'A';
@@ -202,7 +202,7 @@
 
   itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const notOwner = await createEthAccount(web3);
+    const notOwner = createEthAccount(web3);
     const collectionHelpers = evmCollectionHelpers(web3, owner);
     const result = await collectionHelpers.methods.createRefungibleCollection('A', 'A', 'A').send();
     const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
modifiedtests/src/eth/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -1,217 +1,220 @@
-import {ApiPromise} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
-import {expect} from 'chai';
-import Web3 from 'web3';
-import {createEthAccountWithBalance, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, tokenIdToAddress} from '../../eth/util/helpers';
-import nonFungibleAbi from '../nonFungibleAbi.json';
 
+import {itEth, EthUniqueHelper, usingEthPlaygrounds, expect} from '../util/playgrounds';
+
 const createNestingCollection = async (
-  api: ApiPromise,
-  web3: Web3,
+  helper: EthUniqueHelper,
   owner: string,
 ): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {
-  const collectionHelper = evmCollectionHelpers(web3, owner);
-        
-  const result = await collectionHelper.methods
-    .createNonfungibleCollection('A', 'B', 'C')
-    .send();
-  const {collectionIdAddress: collectionAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+  const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
 
-  const contract = new web3.eth.Contract(nonFungibleAbi as any, collectionAddress, {from: owner, ...GAS_ARGS});
+  const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
   await contract.methods.setCollectionNesting(true).send({from: owner});
 
   return {collectionId, collectionAddress, contract};
 };
 
-describe('Integration Test: EVM Nesting', () => {
-  itWeb3('NFT: allows an Owner to nest/unnest their token', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const {collectionId, contract} = await createNestingCollection(api, web3, owner);
 
-    // Create a token to be nested
-    const targetNFTTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      targetNFTTokenId,
-    ).send({from: owner});
-
-    const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNFTTokenId);
-
-    // Create a nested token
-    const firstTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      targetNftTokenAddress,
-      firstTokenId,
-    ).send({from: owner});
+describe('EVM nesting tests group', () => {
+  let donor: IKeyringPair;
 
-    expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);
-
-    // Create a token to be nested and nest
-    const secondTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      secondTokenId,
-    ).send({from: owner});
-
-    await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});
-
-    expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);
-
-    // Unnest token back
-    await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});
-    expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = privateKey('//Alice');
+    });
   });
 
-  itWeb3('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner);
-    const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(api, web3, owner);
-    await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
-
-    // Create a token to nest into
-    const targetNftTokenId = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      targetNftTokenId,
-    ).send({from: owner});
-    const nftTokenAddressA1 = tokenIdToAddress(collectionIdA, targetNftTokenId);
-
-    // Create a token for nesting in the same collection as the target
-    const nftTokenIdA = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      nftTokenIdA,
-    ).send({from: owner});
-
-    // Create a token for nesting in a different collection
-    const nftTokenIdB = await contractB.methods.nextTokenId().call();
-    await contractB.methods.mint(
-      owner,
-      nftTokenIdB,
-    ).send({from: owner});
-
-    // Nest
-    await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});
-    expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);
-
-    await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});
-    expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);
-  });
-});
-
-describe('Negative Test: EVM Nesting', async() => {
-  itWeb3('NFT: disallows to nest token if nesting is disabled', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId, contract} = await createNestingCollection(api, web3, owner);
-    await contract.methods.setCollectionNesting(false).send({from: owner});
-
-    // Create a token to nest into
-    const targetNftTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      targetNftTokenId,
-    ).send({from: owner});
-
-    const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNftTokenId);
-
-    // Create a token to nest
-    const nftTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      nftTokenId,
-    ).send({from: owner});
-
-    // Try to nest
-    await expect(contract.methods
-      .transfer(targetNftTokenAddress, nftTokenId)
-      .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
-  });
-
-  itWeb3('NFT: disallows a non-Owner to nest someone else\'s token', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId, contract} = await createNestingCollection(api, web3, owner);
-
-    // Mint a token
-    const targetTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      targetTokenId,
-    ).send({from: owner});
-    const targetTokenAddress = tokenIdToAddress(collectionId, targetTokenId);
-
-    // Mint a token belonging to a different account
-    const tokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      malignant,
-      tokenId,
-    ).send({from: owner});
-
-    // Try to nest one token in another as a non-owner account
-    await expect(contract.methods
-      .transfer(targetTokenAddress, tokenId)
-      .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
-  });
-
-  itWeb3('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner);
-    const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(api, web3, owner);
-
-    await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
-
-    // Create a token in one collection
-    const nftTokenIdA = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      nftTokenIdA,
-    ).send({from: owner});
-    const nftTokenAddressA = tokenIdToAddress(collectionIdA, nftTokenIdA);
-
-    // Create a token in another collection belonging to someone else
-    const nftTokenIdB = await contractB.methods.nextTokenId().call();
-    await contractB.methods.mint(
-      malignant,
-      nftTokenIdB,
-    ).send({from: owner});
-
-    // Try to drag someone else's token into the other collection and nest
-    await expect(contractB.methods
-      .transfer(nftTokenAddressA, nftTokenIdB)
-      .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
+  describe('Integration Test: EVM Nesting', () => {
+    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const {collectionId, contract} = await createNestingCollection(helper, owner);
+  
+      // Create a token to be nested
+      const targetNFTTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        targetNFTTokenId,
+      ).send({from: owner});
+  
+      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);
+  
+      // Create a nested token
+      const firstTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        targetNftTokenAddress,
+        firstTokenId,
+      ).send({from: owner});
+  
+      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);
+  
+      // Create a token to be nested and nest
+      const secondTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        secondTokenId,
+      ).send({from: owner});
+  
+      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});
+  
+      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);
+  
+      // Unnest token back
+      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});
+      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);
+    });
+  
+    itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);
+      const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner);
+      await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
+  
+      // Create a token to nest into
+      const targetNftTokenId = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        targetNftTokenId,
+      ).send({from: owner});
+      const nftTokenAddressA1 = helper.ethAddress.fromTokenId(collectionIdA, targetNftTokenId);
+  
+      // Create a token for nesting in the same collection as the target
+      const nftTokenIdA = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        nftTokenIdA,
+      ).send({from: owner});
+  
+      // Create a token for nesting in a different collection
+      const nftTokenIdB = await contractB.methods.nextTokenId().call();
+      await contractB.methods.mint(
+        owner,
+        nftTokenIdB,
+      ).send({from: owner});
+  
+      // Nest
+      await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});
+      expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);
+  
+      await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});
+      expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);
+    });
   });
 
-  itWeb3('NFT: disallows to nest token in an unlisted collection', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner);
-    const {contract: contractB} = await createNestingCollection(api, web3, owner);
-
-    await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner});
-
-    // Create a token in one collection
-    const nftTokenIdA = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      nftTokenIdA,
-    ).send({from: owner});
-    const nftTokenAddressA = tokenIdToAddress(collectionIdA, nftTokenIdA);
-
-    // Create a token in another collection
-    const nftTokenIdB = await contractB.methods.nextTokenId().call();
-    await contractB.methods.mint(
-      owner,
-      nftTokenIdB,
-    ).send({from: owner});
-
-    // Try to nest into a token in the other collection, disallowed in the first
-    await expect(contractB.methods
-      .transfer(nftTokenAddressA, nftTokenIdB)
-      .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
+  describe('Negative Test: EVM Nesting', async() => {
+    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId, contract} = await createNestingCollection(helper, owner);
+      await contract.methods.setCollectionNesting(false).send({from: owner});
+  
+      // Create a token to nest into
+      const targetNftTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        targetNftTokenId,
+      ).send({from: owner});
+  
+      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNftTokenId);
+  
+      // Create a token to nest
+      const nftTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        nftTokenId,
+      ).send({from: owner});
+  
+      // Try to nest
+      await expect(contract.methods
+        .transfer(targetNftTokenAddress, nftTokenId)
+        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
+    });
+  
+    itEth('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const malignant = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId, contract} = await createNestingCollection(helper, owner);
+  
+      // Mint a token
+      const targetTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        targetTokenId,
+      ).send({from: owner});
+      const targetTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);
+  
+      // Mint a token belonging to a different account
+      const tokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        malignant,
+        tokenId,
+      ).send({from: owner});
+  
+      // Try to nest one token in another as a non-owner account
+      await expect(contract.methods
+        .transfer(targetTokenAddress, tokenId)
+        .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
+    });
+  
+    itEth('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const malignant = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);
+      const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner);
+  
+      await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
+  
+      // Create a token in one collection
+      const nftTokenIdA = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        nftTokenIdA,
+      ).send({from: owner});
+      const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);
+  
+      // Create a token in another collection belonging to someone else
+      const nftTokenIdB = await contractB.methods.nextTokenId().call();
+      await contractB.methods.mint(
+        malignant,
+        nftTokenIdB,
+      ).send({from: owner});
+  
+      // Try to drag someone else's token into the other collection and nest
+      await expect(contractB.methods
+        .transfer(nftTokenAddressA, nftTokenIdB)
+        .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
+    });
+  
+    itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);
+      const {contract: contractB} = await createNestingCollection(helper, owner);
+  
+      await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner});
+  
+      // Create a token in one collection
+      const nftTokenIdA = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        nftTokenIdA,
+      ).send({from: owner});
+      const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);
+  
+      // Create a token in another collection
+      const nftTokenIdB = await contractB.methods.nextTokenId().call();
+      await contractB.methods.mint(
+        owner,
+        nftTokenIdB,
+      ).send({from: owner});
+  
+      // Try to nest into a token in the other collection, disallowed in the first
+      await expect(contractB.methods
+        .transfer(nftTokenAddressA, nftTokenIdB)
+        .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
+    });
   });
 });
modifiedtests/src/eth/payable.test.tsdiffbeforeafterboth
--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -14,97 +14,91 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {expect} from 'chai';
-import {submitTransactionAsync} from '../substrate/substrate-api';
-import {createEthAccountWithBalance, deployCollector, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers';
-import {evmToAddress} from '@polkadot/util-crypto';
-import {getGenericResult, UNIQUE} from '../util/helpers';
-import {getBalanceSingle, transferBalanceExpectSuccess} from '../substrate/get-balance';
+import {IKeyringPair} from '@polkadot/types/types';
+
+import {itEth, expect, usingEthPlaygrounds} from './util/playgrounds';
 
 describe('EVM payable contracts', () => {
-  itWeb3('Evm contract can receive wei from eth account', async ({api, web3, privateKeyWrapper}) => {
-    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const contract = await deployCollector(web3, deployer);
+  let donor: IKeyringPair;
 
-    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', ...GAS_ARGS});
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = privateKey('//Alice');
+    });
+  });
+
+  itEth('Evm contract can receive wei from eth account', async ({helper}) => {
+    const deployer = await helper.eth.createAccountWithBalance(donor);
+    const contract = await helper.eth.deployCollectorContract(deployer);
+
+    const web3 = helper.getWeb3();
+
+    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS});
 
     expect(await contract.methods.getCollected().call()).to.be.equal('10000');
   });
 
-  itWeb3('Evm contract can receive wei from substrate account', async ({api, web3, privateKeyWrapper}) => {
-    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const contract = await deployCollector(web3, deployer);
-    const alice = privateKeyWrapper('//Alice');
+  itEth('Evm contract can receive wei from substrate account', async ({helper}) => {
+    const deployer = await helper.eth.createAccountWithBalance(donor);
+    const contract = await helper.eth.deployCollectorContract(deployer);
+    const [alice] = await helper.arrange.createAccounts([10n], donor);
+
+    const weiCount = '10000';
 
     // Transaction fee/value will be payed from subToEth(sender) evm balance,
     // which is backed by evmToAddress(subToEth(sender)) substrate balance
-    await transferBalanceToEth(api, alice, subToEth(alice.address));
+    await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n);
+
 
-    {
-      const tx = api.tx.evm.call(
-        subToEth(alice.address),
-        contract.options.address,
-        contract.methods.giveMoney().encodeABI(),
-        '10000',
-        GAS_ARGS.gas,
-        await web3.eth.getGasPrice(),
-        null,
-        null,
-        [],
-      );
-      const events = await submitTransactionAsync(alice, tx);
-      const result = getGenericResult(events);
-      expect(result.success).to.be.true;
-    }
+    await helper.eth.callEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount);
 
-    expect(await contract.methods.getCollected().call()).to.be.equal('10000');
+    expect(await contract.methods.getCollected().call()).to.be.equal(weiCount);
   });
 
   // We can't handle sending balance to backing storage of evm balance, because evmToAddress operation is irreversible
-  itWeb3('Wei sent directly to backing storage of evm contract balance is unaccounted', async({api, web3, privateKeyWrapper}) => {
-    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const contract = await deployCollector(web3, deployer);
-    const alice = privateKeyWrapper('//Alice');
+  itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => {
+    const deployer = await helper.eth.createAccountWithBalance(donor);
+    const contract = await helper.eth.deployCollectorContract(deployer);
+    const [alice] = await helper.arrange.createAccounts([10n], donor);
+
+    const weiCount = 10_000n;
 
-    await transferBalanceExpectSuccess(api, alice, evmToAddress(contract.options.address), '10000');
+    await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false);
 
-    expect(await contract.methods.getUnaccounted().call()).to.be.equal('10000');
+    expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());
   });
 
-  itWeb3('Balance can be retrieved from evm contract', async({api, web3, privateKeyWrapper}) => {
-    const FEE_BALANCE = 1000n * UNIQUE;
-    const CONTRACT_BALANCE = 1n * UNIQUE;
+  itEth('Balance can be retrieved from evm contract', async({helper, privateKey}) => {
+    const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();
+    const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();
+
+    const deployer = await helper.eth.createAccountWithBalance(donor);
+    const contract = await helper.eth.deployCollectorContract(deployer);
+    const [alice] = await helper.arrange.createAccounts([20n], donor);
 
-    const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const contract = await deployCollector(web3, deployer);
-    const alice = privateKeyWrapper('//Alice');
+    const web3 = helper.getWeb3();
 
-    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), ...GAS_ARGS});
+    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});
 
-    const receiver = privateKeyWrapper(`//Receiver${Date.now()}`);
+    const receiver = privateKey(`//Receiver${Date.now()}`);
 
     // First receive balance on eth balance of bob
     {
-      const ethReceiver = subToEth(receiver.address);
+      const ethReceiver = helper.address.substrateToEth(receiver.address);
       expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0');
       await contract.methods.withdraw(ethReceiver).send({from: deployer});
       expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString());
     }
 
     // Some balance is required to pay fee for evm.withdraw call
-    await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString());
+    await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE);
+    // await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString());
 
     // Withdraw balance from eth to substrate
     {
-      const initialReceiverBalance = await getBalanceSingle(api, receiver.address);
-      const tx = api.tx.evm.withdraw(
-        subToEth(receiver.address),
-        CONTRACT_BALANCE.toString(),
-      );
-      const events = await submitTransactionAsync(receiver, tx);
-      const result = getGenericResult(events);
-      expect(result.success).to.be.true;
-      const finalReceiverBalance = await getBalanceSingle(api, receiver.address);
+      const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address);
+      await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true);
+      const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address);
 
       expect(finalReceiverBalance > initialReceiverBalance).to.be.true;
     }
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers';
-import {collectionIdFromAddress, collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from './util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers';
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
modifiedtests/src/eth/util/contractHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/util/contractHelpersAbi.json
+++ b/tests/src/eth/util/contractHelpersAbi.json
@@ -1,5 +1,56 @@
 [
   {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "ContractSponsorRemoved",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "sponsor",
+        "type": "address"
+      }
+    ],
+    "name": "ContractSponsorSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "sponsor",
+        "type": "address"
+      }
+    ],
+    "name": "ContractSponsorshipConfirmed",
+    "type": "event"
+  },
+  {
     "inputs": [
       {
         "internalType": "address",
addedtests/src/eth/util/playgrounds/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/util/playgrounds/index.ts
@@ -0,0 +1,49 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import {IKeyringPair} from '@polkadot/types/types';
+
+import config from '../../../config';
+
+import {EthUniqueHelper} from './unique.dev';
+import {SilentLogger, SilentConsole} from '../../../util/playgrounds/unique.dev';
+
+export {EthUniqueHelper} from './unique.dev';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+chai.use(chaiAsPromised);
+export const expect = chai.expect;
+
+export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
+  const silentConsole = new SilentConsole();
+  silentConsole.enable();
+
+  const helper = new EthUniqueHelper(new SilentLogger());
+
+  try {
+    await helper.connect(config.substrateUrl);
+    await helper.connectWeb3(config.substrateUrl);
+    const ss58Format = helper.chain.getChainProperties().ss58Format;
+    const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format);
+    await code(helper, privateKey);
+  }
+  finally {
+    await helper.disconnect();
+    await helper.disconnectWeb3();
+    silentConsole.disable();
+  }
+};
+  
+export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) {
+  let i: any = it;
+  if (opts.only) i = i.only;
+  else if (opts.skip) i = i.skip;
+  i(name, async () => {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      await cb({helper, privateKey});
+    });
+  });
+}
+itEth.only = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {only: true});
+itEth.skip = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {skip: true});
\ No newline at end of file
addedtests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -0,0 +1,9 @@
+export interface ContractImports {
+  solPath: string;
+  fsPath: string;
+}
+
+export interface CompiledContract {
+  abi: any;
+  object: string;
+}
\ No newline at end of file
addedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -0,0 +1,268 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+/* eslint-disable function-call-argument-newline */
+
+import {readFile} from 'fs/promises';
+
+import Web3 from 'web3';
+import {WebsocketProvider} from 'web3-core';
+import {Contract} from 'web3-eth-contract';
+
+import * as solc from 'solc';
+
+import {evmToAddress} from '@polkadot/util-crypto';
+import {IKeyringPair} from '@polkadot/types/types';
+
+import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
+
+import {ContractImports, CompiledContract} from './types';
+
+// Native contracts ABI
+import collectionHelpersAbi from '../../collectionHelpersAbi.json';
+import fungibleAbi from '../../fungibleAbi.json';
+import nonFungibleAbi from '../../nonFungibleAbi.json';
+import refungibleAbi from '../../reFungibleAbi.json';
+import refungibleTokenAbi from '../../reFungibleTokenAbi.json';
+import contractHelpersAbi from './../contractHelpersAbi.json';
+
+class EthGroupBase {
+  helper: EthUniqueHelper;
+
+  constructor(helper: EthUniqueHelper) {
+    this.helper = helper;
+  }
+}
+
+
+class ContractGroup extends EthGroupBase {
+  async findImports(imports?: ContractImports[]){
+    if(!imports) return function(path: string) {
+      return {error: `File not found: ${path}`};
+    };
+  
+    const knownImports = {} as any;
+    for(const imp of imports) {
+      knownImports[imp.solPath] = (await readFile(imp.fsPath)).toString();
+    }
+  
+    return function(path: string) {
+      if(knownImports.hasOwnPropertyDescriptor(path)) return {contents: knownImports[path]};
+      return {error: `File not found: ${path}`};
+    };
+  }
+
+  async compile(name: string, src: string, imports?: ContractImports[]): Promise<CompiledContract> {
+    const out = JSON.parse(solc.compile(JSON.stringify({
+      language: 'Solidity',
+      sources: {
+        [`${name}.sol`]: {
+          content: src,
+        },
+      },
+      settings: {
+        outputSelection: {
+          '*': {
+            '*': ['*'],
+          },
+        },
+      },
+    }), {import: await this.findImports(imports)})).contracts[`${name}.sol`][name];
+  
+    return {
+      abi: out.abi,
+      object: '0x' + out.evm.bytecode.object,
+    };
+  }
+
+  async deployByCode(signer: string, name: string, src: string, imports?: ContractImports[]): Promise<Contract> {
+    const compiledContract = await this.compile(name, src, imports);
+    return this.deployByAbi(signer, compiledContract.abi, compiledContract.object);
+  }
+
+  async deployByAbi(signer: string, abi: any, object: string): Promise<Contract> {
+    const web3 = this.helper.getWeb3();
+    const contract = new web3.eth.Contract(abi, undefined, {
+      data: object,
+      from: signer,
+      gas: this.helper.eth.DEFAULT_GAS,
+    });
+    return await contract.deploy({data: object}).send({from: signer});
+  }
+
+}
+  
+class NativeContractGroup extends EthGroupBase {
+
+  contractHelpers(caller: string): Contract {
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
+  }
+
+  collectionHelpers(caller: string) {
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
+  }
+
+  collection(address: string, mode: 'nft' | 'rft' | 'ft', caller?: string): Contract {
+    const abi = {
+      'nft': nonFungibleAbi,
+      'rft': refungibleAbi,
+      'ft': fungibleAbi,
+    }[mode];
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(abi as any, address, {gas: this.helper.eth.DEFAULT_GAS, ...(caller ? {from: caller} : {})});
+  }
+
+  rftTokenByAddress(address: string, caller?: string): Contract {
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(refungibleTokenAbi as any, address, {gas: this.helper.eth.DEFAULT_GAS, ...(caller ? {from: caller} : {})});
+  }
+
+  rftToken(collectionId: number, tokenId: number, caller?: string): Contract {
+    return this.rftTokenByAddress(this.helper.ethAddress.fromTokenId(collectionId, tokenId), caller);
+  }
+}
+
+  
+class EthGroup extends EthGroupBase {
+  DEFAULT_GAS = 2_500_000;
+
+  createAccount() {
+    const web3 = this.helper.getWeb3();
+    const account = web3.eth.accounts.create();
+    web3.eth.accounts.wallet.add(account.privateKey);
+    return account.address;
+  }
+
+  async createAccountWithBalance(donor: IKeyringPair, amount=1000n) {
+    const account = this.createAccount();
+    await this.transferBalanceFromSubstrate(donor, account, amount);
+  
+    return account;
+  }
+
+  async transferBalanceFromSubstrate(donor: IKeyringPair, recepient: string, amount=1000n, inTokens=true) {
+    return await this.helper.balance.transferToSubstrate(donor, evmToAddress(recepient), amount * (inTokens ? this.helper.balance.getOneTokenNominal() : 1n));
+  }
+
+  async callEVM(signer: IKeyringPair, contractAddress: string, abi: any, value: string, gasLimit?: number) {
+    if(!gasLimit) gasLimit = this.DEFAULT_GAS;
+    const web3 = this.helper.getWeb3();
+    const gasPrice = await web3.eth.getGasPrice();
+    // TODO: check execution status
+    await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.evm.call', [this.helper.address.substrateToEth(signer.address), contractAddress, abi, value, gasLimit, gasPrice, null, null, []],
+      true,
+    );
+  }
+
+  async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
+    const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+        
+    const result = await collectionHelper.methods.createNonfungibleCollection(name, description, tokenPrefix).send();
+
+    const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
+    const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
+
+    return {collectionId, collectionAddress};
+  }
+
+  async deployCollectorContract(signer: string): Promise<Contract> {
+    return await this.helper.ethContract.deployByCode(signer, 'Collector', `
+    // SPDX-License-Identifier: UNLICENSED
+    pragma solidity ^0.8.6;
+
+    contract Collector {
+      uint256 collected;
+      fallback() external payable {
+        giveMoney();
+      }
+      function giveMoney() public payable {
+        collected += msg.value;
+      }
+      function getCollected() public view returns (uint256) {
+        return collected;
+      }
+      function getUnaccounted() public view returns (uint256) {
+        return address(this).balance - collected;
+      }
+
+      function withdraw(address payable target) public {
+        target.transfer(collected);
+        collected = 0;
+      }
+    }
+  `);
+  }
+}
+  
+  
+class EthAddressGroup extends EthGroupBase {
+  extractCollectionId(address: string): number {
+    if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format');
+    return parseInt(address.substr(address.length - 8), 16);
+  }
+
+  fromCollectionId(collectionId: number): string {
+    if (collectionId >= 0xffffffff || collectionId < 0) throw new Error('collectionId overflow');
+    return Web3.utils.toChecksumAddress(`0x17c4e6453cc49aaaaeaca894e6d9683e${collectionId.toString(16).padStart(8, '0')}`);
+  }
+
+  extractTokenId(address: string): {collectionId: number, tokenId: number} {
+    if (!address.startsWith('0x'))
+      throw 'address not starts with "0x"';
+    if (address.length > 42)
+      throw 'address length is more than 20 bytes';
+    return {
+      collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)),
+      tokenId: Number('0x' + address.substring(address.length - 8)),
+    };
+  }
+
+  fromTokenId(collectionId: number, tokenId: number): string  {
+    return this.helper.util.getNestingTokenAddress(collectionId, tokenId);
+  }
+
+  normalizeAddress(address: string): string {
+    return '0x' + address.substring(address.length - 40);
+  }
+}  
+ 
+
+export class EthUniqueHelper extends DevUniqueHelper {
+  web3: Web3 | null = null;
+  web3Provider: WebsocketProvider | null = null;
+
+  eth: EthGroup;
+  ethAddress: EthAddressGroup;
+  ethNativeContract: NativeContractGroup;
+  ethContract: ContractGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }) {
+    super(logger);
+    this.eth = new EthGroup(this);
+    this.ethAddress = new EthAddressGroup(this);
+    this.ethNativeContract = new NativeContractGroup(this);
+    this.ethContract = new ContractGroup(this);
+  }
+
+  getWeb3(): Web3 {
+    if(this.web3 === null) throw Error('Web3 not connected');
+    return this.web3;
+  }
+
+  async connectWeb3(wsEndpoint: string) {
+    if(this.web3 !== null) return;
+    this.web3Provider = new Web3.providers.WebsocketProvider(wsEndpoint);
+    this.web3 = new Web3(this.web3Provider);
+  }
+
+  async disconnectWeb3() {
+    if(this.web3 === null) return;
+    this.web3Provider?.connection.close();
+    this.web3 = null;
+  }
+}
+  
\ No newline at end of file
addedtests/src/interfaces/appPromotion/definitions.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/interfaces/appPromotion/definitions.ts
@@ -0,0 +1,58 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+type RpcParam = {
+  name: string;
+  type: string;
+  isOptional?: true;
+};
+
+const CROSS_ACCOUNT_ID_TYPE = 'PalletEvmAccountBasicCrossAccountIdRepr';
+
+const crossAccountParam = (name = 'account') => ({name, type: CROSS_ACCOUNT_ID_TYPE});
+const atParam = {name: 'at', type: 'Hash', isOptional: true};
+
+const fun = (description: string, params: RpcParam[], type: string) => ({
+  description,
+  params: [...params, atParam],
+  type,
+});
+
+export default {
+  types: {},
+  rpc: {
+    totalStaked: fun(
+      'Returns the total amount of staked tokens',
+      [{name: 'staker', type: CROSS_ACCOUNT_ID_TYPE, isOptional: true}],
+      'u128',
+    ),
+    totalStakedPerBlock: fun(
+      'Returns the total amount of staked tokens per block when staked',
+      [crossAccountParam('staker')],
+      'Vec<(u32, u128)>',
+    ),
+    pendingUnstake: fun(
+      'Returns the total amount of unstaked tokens',
+      [{name: 'staker', type: CROSS_ACCOUNT_ID_TYPE, isOptional: true}],
+      'u128',
+    ),
+    pendingUnstakePerBlock: fun(
+      'Returns the total amount of unstaked tokens per block',
+      [crossAccountParam('staker')],
+      'Vec<(u32, u128)>',
+    ),
+  },
+};
addedtests/src/interfaces/appPromotion/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/interfaces/appPromotion/index.ts
@@ -0,0 +1,4 @@
+// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
+/* eslint-disable */
+
+export * from './types';
addedtests/src/interfaces/appPromotion/types.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/interfaces/appPromotion/types.ts
@@ -0,0 +1,4 @@
+// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
+/* eslint-disable */
+
+export type PHANTOM_APPPROMOTION = 'appPromotion';
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -8,13 +8,39 @@
 import type { ApiTypes, AugmentedConst } from '@polkadot/api-base/types';
 import type { Option, u128, u16, u32, u64, u8 } from '@polkadot/types-codec';
 import type { Codec } from '@polkadot/types-codec/types';
-import type { Permill } from '@polkadot/types/interfaces/runtime';
+import type { Perbill, Permill } from '@polkadot/types/interfaces/runtime';
 import type { FrameSupportPalletId, FrameSupportWeightsRuntimeDbWeight, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion } from '@polkadot/types/lookup';
 
 export type __AugmentedConst<ApiType extends ApiTypes> = AugmentedConst<ApiType>;
 
 declare module '@polkadot/api-base/types/consts' {
   interface AugmentedConsts<ApiType extends ApiTypes> {
+    appPromotion: {
+      /**
+       * Rate of return for interval in blocks defined in `RecalculationInterval`.
+       **/
+      intervalIncome: Perbill & AugmentedConst<ApiType>;
+      /**
+       * Decimals for the `Currency`.
+       **/
+      nominal: u128 & AugmentedConst<ApiType>;
+      /**
+       * The app's pallet id, used for deriving its sovereign account ID.
+       **/
+      palletId: FrameSupportPalletId & AugmentedConst<ApiType>;
+      /**
+       * In parachain blocks.
+       **/
+      pendingInterval: u32 & AugmentedConst<ApiType>;
+      /**
+       * In relay blocks.
+       **/
+      recalculationInterval: u32 & AugmentedConst<ApiType>;
+      /**
+       * Generic const
+       **/
+      [key: string]: Codec;
+    };
     balances: {
       /**
        * The minimum amount required to keep an account open.
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -11,6 +11,36 @@
 
 declare module '@polkadot/api-base/types/errors' {
   interface AugmentedErrors<ApiType extends ApiTypes> {
+    appPromotion: {
+      /**
+       * Error due to action requiring admin to be set.
+       **/
+      AdminNotSet: AugmentedError<ApiType>;
+      /**
+       * Errors caused by incorrect actions with a locked balance.
+       **/
+      IncorrectLockedBalanceOperation: AugmentedError<ApiType>;
+      /**
+       * No permission to perform an action.
+       **/
+      NoPermission: AugmentedError<ApiType>;
+      /**
+       * Insufficient funds to perform an action.
+       **/
+      NotSufficientFunds: AugmentedError<ApiType>;
+      /**
+       * Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.
+       **/
+      PendingForBlockOverflow: AugmentedError<ApiType>;
+      /**
+       * The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.
+       **/
+      SponsorNotSet: AugmentedError<ApiType>;
+      /**
+       * Generic error
+       **/
+      [key: string]: AugmentedError<ApiType>;
+    };
     balances: {
       /**
        * Beneficiary account must pre-exist
@@ -265,8 +295,12 @@
     };
     evmContractHelpers: {
       /**
-       * This method is only executable by owner
+       * No pending sponsor for contract.
        **/
+      NoPendingSponsor: AugmentedError<ApiType>;
+      /**
+       * This method is only executable by contract owner
+       **/
       NoPermission: AugmentedError<ApiType>;
       /**
        * Generic error
@@ -274,7 +308,13 @@
       [key: string]: AugmentedError<ApiType>;
     };
     evmMigration: {
+      /**
+       * Migration of this account is not yet started, or already finished.
+       **/
       AccountIsNotMigrating: AugmentedError<ApiType>;
+      /**
+       * Can only migrate to empty address.
+       **/
       AccountNotEmpty: AugmentedError<ApiType>;
       /**
        * Generic error
modifiedtests/src/interfaces/augment-api-events.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-events.ts
+++ b/tests/src/interfaces/augment-api-events.ts
@@ -15,6 +15,44 @@
 
 declare module '@polkadot/api-base/types/events' {
   interface AugmentedEvents<ApiType extends ApiTypes> {
+    appPromotion: {
+      /**
+       * The admin was set
+       * 
+       * # Arguments
+       * * AccountId: ID of the admin
+       **/
+      SetAdmin: AugmentedEvent<ApiType, [AccountId32]>;
+      /**
+       * Staking was performed
+       * 
+       * # Arguments
+       * * AccountId: ID of the staker
+       * * Balance : staking amount
+       **/
+      Stake: AugmentedEvent<ApiType, [AccountId32, u128]>;
+      /**
+       * Staking recalculation was performed
+       * 
+       * # Arguments
+       * * AccountId: ID of the staker.
+       * * Balance : recalculation base
+       * * Balance : total income
+       **/
+      StakingRecalculation: AugmentedEvent<ApiType, [AccountId32, u128, u128]>;
+      /**
+       * Unstaking was performed
+       * 
+       * # Arguments
+       * * AccountId: ID of the staker
+       * * Balance : unstaking amount
+       **/
+      Unstake: AugmentedEvent<ApiType, [AccountId32, u128]>;
+      /**
+       * Generic event
+       **/
+      [key: string]: AugmentedEvent<ApiType>;
+    };
     balances: {
       /**
        * A balance was set by root.
@@ -208,6 +246,24 @@
        **/
       [key: string]: AugmentedEvent<ApiType>;
     };
+    evmContractHelpers: {
+      /**
+       * Collection sponsor was removed.
+       **/
+      ContractSponsorRemoved: AugmentedEvent<ApiType, [H160]>;
+      /**
+       * Contract sponsor was set.
+       **/
+      ContractSponsorSet: AugmentedEvent<ApiType, [H160, AccountId32]>;
+      /**
+       * New sponsor was confirm.
+       **/
+      ContractSponsorshipConfirmed: AugmentedEvent<ApiType, [H160, AccountId32]>;
+      /**
+       * Generic event
+       **/
+      [key: string]: AugmentedEvent<ApiType>;
+    };
     parachainSystem: {
       /**
        * Downward messages were processed using the given weight.
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -6,10 +6,10 @@
 import '@polkadot/api-base/types/storage';
 
 import type { ApiTypes, AugmentedQuery, QueryableStorageEntry } from '@polkadot/api-base/types';
-import type { BTreeMap, Bytes, Option, U256, U8aFixed, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types-codec';
+import type { BTreeMap, Bytes, Option, U256, U8aFixed, Vec, bool, u128, u16, u32, u64, u8 } from '@polkadot/types-codec';
 import type { AnyNumber, ITuple } from '@polkadot/types-codec/types';
 import type { AccountId32, H160, H256 } from '@polkadot/types/interfaces/runtime';
-import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueQueueConfigData, EthereumBlock, EthereumLog, EthereumReceiptReceiptV3, EthereumTransactionTransactionV2, FpRpcTransactionStatus, FrameSupportWeightsPerDispatchClassU64, FrameSystemAccountInfo, FrameSystemEventRecord, FrameSystemLastRuntimeUpgradeInfo, FrameSystemPhase, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesReleases, PalletBalancesReserveData, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmContractHelpersSponsoringModeT, PalletNonfungibleItemData, PalletRefungibleItemData, PalletTransactionPaymentReleases, PalletTreasuryProposal, PalletUniqueSchedulerScheduledV3, PhantomTypeUpDataStructs, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpRuntimeDigest, SpTrieStorageProof, UpDataStructsCollection, UpDataStructsCollectionStats, UpDataStructsProperties, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsTokenChild } from '@polkadot/types/lookup';
+import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueQueueConfigData, EthereumBlock, EthereumLog, EthereumReceiptReceiptV3, EthereumTransactionTransactionV2, FpRpcTransactionStatus, FrameSupportWeightsPerDispatchClassU64, FrameSystemAccountInfo, FrameSystemEventRecord, FrameSystemLastRuntimeUpgradeInfo, FrameSystemPhase, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesReleases, PalletBalancesReserveData, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmContractHelpersSponsoringModeT, PalletNonfungibleItemData, PalletRefungibleItemData, PalletTransactionPaymentReleases, PalletTreasuryProposal, PalletUniqueSchedulerScheduledV3, PhantomTypeUpDataStructs, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpRuntimeDigest, SpTrieStorageProof, UpDataStructsCollection, UpDataStructsCollectionStats, UpDataStructsProperties, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild } from '@polkadot/types/lookup';
 import type { Observable } from '@polkadot/types/types';
 
 export type __AugmentedQuery<ApiType extends ApiTypes> = AugmentedQuery<ApiType, () => unknown>;
@@ -17,6 +17,28 @@
 
 declare module '@polkadot/api-base/types/storage' {
   interface AugmentedQueries<ApiType extends ApiTypes> {
+    appPromotion: {
+      admin: AugmentedQuery<ApiType, () => Observable<Option<AccountId32>>, []> & QueryableStorageEntry<ApiType, []>;
+      /**
+       * Stores a key for record for which the next revenue recalculation would be performed.
+       * If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
+       **/
+      nextCalculatedRecord: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[AccountId32, u32]>>>, []> & QueryableStorageEntry<ApiType, []>;
+      pendingUnstake: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Vec<ITuple<[AccountId32, u128]>>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+      /**
+       * Amount of tokens staked by account in the blocknumber.
+       **/
+      staked: AugmentedQuery<ApiType, (arg1: AccountId32 | string | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<ITuple<[u128, u32]>>, [AccountId32, u32]> & QueryableStorageEntry<ApiType, [AccountId32, u32]>;
+      /**
+       * Amount of stakes for an Account
+       **/
+      stakesPerAccount: AugmentedQuery<ApiType, (arg: AccountId32 | string | Uint8Array) => Observable<u8>, [AccountId32]> & QueryableStorageEntry<ApiType, [AccountId32]>;
+      totalStaked: AugmentedQuery<ApiType, () => Observable<u128>, []> & QueryableStorageEntry<ApiType, []>;
+      /**
+       * Generic query
+       **/
+      [key: string]: QueryableStorageEntry<ApiType>;
+    };
     balances: {
       /**
        * The Balances pallet example of storing the balance of an account.
@@ -194,12 +216,66 @@
       [key: string]: QueryableStorageEntry<ApiType>;
     };
     evmContractHelpers: {
+      /**
+       * 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.
+       **/
       allowlist: AugmentedQuery<ApiType, (arg1: H160 | string | Uint8Array, arg2: H160 | string | Uint8Array) => Observable<bool>, [H160, H160]> & QueryableStorageEntry<ApiType, [H160, H160]>;
+      /**
+       * 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.
+       **/
       allowlistEnabled: AugmentedQuery<ApiType, (arg: H160 | string | Uint8Array) => Observable<bool>, [H160]> & QueryableStorageEntry<ApiType, [H160]>;
+      /**
+       * Store owner for contract.
+       * 
+       * * **Key** - contract address.
+       * * **Value** - owner for contract.
+       **/
       owner: AugmentedQuery<ApiType, (arg: H160 | string | Uint8Array) => Observable<H160>, [H160]> & QueryableStorageEntry<ApiType, [H160]>;
       selfSponsoring: AugmentedQuery<ApiType, (arg: H160 | string | Uint8Array) => Observable<bool>, [H160]> & QueryableStorageEntry<ApiType, [H160]>;
+      /**
+       * Storage for last sponsored block.
+       * 
+       * * **Key1** - contract address.
+       * * **Key2** - sponsored user address.
+       * * **Value** - last sponsored block number.
+       **/
       sponsorBasket: AugmentedQuery<ApiType, (arg1: H160 | string | Uint8Array, arg2: H160 | string | Uint8Array) => Observable<Option<u32>>, [H160, H160]> & QueryableStorageEntry<ApiType, [H160, H160]>;
+      /**
+       * Store for contract sponsorship state.
+       * 
+       * * **Key** - contract address.
+       * * **Value** - sponsorship state.
+       **/
+      sponsoring: AugmentedQuery<ApiType, (arg: H160 | string | Uint8Array) => Observable<UpDataStructsSponsorshipStateBasicCrossAccountIdRepr>, [H160]> & QueryableStorageEntry<ApiType, [H160]>;
+      /**
+       * 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).
+       **/
       sponsoringMode: AugmentedQuery<ApiType, (arg: H160 | string | Uint8Array) => Observable<Option<PalletEvmContractHelpersSponsoringModeT>>, [H160]> & QueryableStorageEntry<ApiType, [H160]>;
+      /**
+       * Storage for sponsoring rate limit in blocks.
+       * 
+       * * **Key** - contract address.
+       * * **Value** - amount of sponsored blocks.
+       **/
       sponsoringRateLimit: AugmentedQuery<ApiType, (arg: H160 | string | Uint8Array) => Observable<u32>, [H160]> & QueryableStorageEntry<ApiType, [H160]>;
       /**
        * Generic query
@@ -281,7 +357,7 @@
        * 
        * Currently used to store RMRK data.
        **/
-      tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | 'Eth' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
+      tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
       /**
        * Used to enumerate token's children.
        **/
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -9,7 +9,7 @@
 import type { AugmentedRpc } from '@polkadot/rpc-core/types';
 import type { Metadata, StorageKey } from '@polkadot/types';
 import type { Bytes, HashMap, Json, Null, Option, Text, U256, U64, Vec, bool, f64, u128, u32, u64 } from '@polkadot/types-codec';
-import type { AnyNumber, Codec } from '@polkadot/types-codec/types';
+import type { AnyNumber, Codec, ITuple } from '@polkadot/types-codec/types';
 import type { ExtrinsicOrHash, ExtrinsicStatus } from '@polkadot/types/interfaces/author';
 import type { EpochAuthorship } from '@polkadot/types/interfaces/babe';
 import type { BeefySignedCommitment } from '@polkadot/types/interfaces/beefy';
@@ -35,6 +35,24 @@
 
 declare module '@polkadot/rpc-core/types/jsonrpc' {
   interface RpcInterface {
+    appPromotion: {
+      /**
+       * Returns the total amount of unstaked tokens
+       **/
+      pendingUnstake: AugmentedRpc<(staker?: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u128>>;
+      /**
+       * Returns the total amount of unstaked tokens per block
+       **/
+      pendingUnstakePerBlock: AugmentedRpc<(staker: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<ITuple<[u32, u128]>>>>;
+      /**
+       * Returns the total amount of staked tokens
+       **/
+      totalStaked: AugmentedRpc<(staker?: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u128>>;
+      /**
+       * Returns the total amount of staked tokens per block when staked
+       **/
+      totalStakedPerBlock: AugmentedRpc<(staker: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<ITuple<[u32, u128]>>>>;
+    };
     author: {
       /**
        * Returns true if the keystore has private keys for the given public key and key type.
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -17,6 +17,20 @@
 
 declare module '@polkadot/api-base/types/submittable' {
   interface AugmentedSubmittables<ApiType extends ApiTypes> {
+    appPromotion: {
+      payoutStakers: AugmentedSubmittable<(stakersNumber: Option<u8> | null | Uint8Array | u8 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u8>]>;
+      setAdminAddress: AugmentedSubmittable<(admin: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr]>;
+      sponsorCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+      sponsorContract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
+      stake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
+      stopSponsoringCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+      stopSponsoringContract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
+      unstake: AugmentedSubmittable<() => SubmittableExtrinsic<ApiType>, []>;
+      /**
+       * Generic tx
+       **/
+      [key: string]: SubmittableExtrinsicFunction<ApiType>;
+    };
     balances: {
       /**
        * Exactly as `transfer`, except the origin must be root and the source account may be
@@ -181,8 +195,21 @@
       [key: string]: SubmittableExtrinsicFunction<ApiType>;
     };
     evmMigration: {
+      /**
+       * Start contract migration, inserts contract stub at target address,
+       * and marks account as pending, allowing to insert storage
+       **/
       begin: AugmentedSubmittable<(address: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
+      /**
+       * Finish contract migration, allows it to be called.
+       * It is not possible to alter contract storage via [`Self::set_data`]
+       * after this call.
+       **/
       finish: AugmentedSubmittable<(address: H160 | string | Uint8Array, code: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160, Bytes]>;
+      /**
+       * Insert items into contract storage, this method can be called
+       * multiple times
+       **/
       setData: AugmentedSubmittable<(address: H160 | string | Uint8Array, data: Vec<ITuple<[H256, H256]>> | ([H256 | string | Uint8Array, H256 | string | Uint8Array])[]) => SubmittableExtrinsic<ApiType>, [H160, Vec<ITuple<[H256, H256]>>]>;
       /**
        * Generic tx
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -5,7 +5,7 @@
 // this is required to allow for ambient/previous definitions
 import '@polkadot/types/types/registry';
 
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationCall, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUniqueSchedulerCall, PalletUniqueSchedulerError, PalletUniqueSchedulerEvent, PalletUniqueSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default';
+import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletAppPromotionCall, PalletAppPromotionError, PalletAppPromotionEvent, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationCall, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersEvent, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUniqueSchedulerCall, PalletUniqueSchedulerError, PalletUniqueSchedulerEvent, PalletUniqueSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipStateAccountId32, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default';
 import type { Data, StorageKey } from '@polkadot/types';
 import type { BitVec, Bool, Bytes, F32, F64, I128, I16, I256, I32, I64, I8, Json, Null, OptionBool, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, f32, f64, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
@@ -804,6 +804,9 @@
     Owner: Owner;
     PageCounter: PageCounter;
     PageIndexData: PageIndexData;
+    PalletAppPromotionCall: PalletAppPromotionCall;
+    PalletAppPromotionError: PalletAppPromotionError;
+    PalletAppPromotionEvent: PalletAppPromotionEvent;
     PalletBalancesAccountData: PalletBalancesAccountData;
     PalletBalancesBalanceLock: PalletBalancesBalanceLock;
     PalletBalancesCall: PalletBalancesCall;
@@ -832,6 +835,7 @@
     PalletEvmCall: PalletEvmCall;
     PalletEvmCoderSubstrateError: PalletEvmCoderSubstrateError;
     PalletEvmContractHelpersError: PalletEvmContractHelpersError;
+    PalletEvmContractHelpersEvent: PalletEvmContractHelpersEvent;
     PalletEvmContractHelpersSponsoringModeT: PalletEvmContractHelpersSponsoringModeT;
     PalletEvmError: PalletEvmError;
     PalletEvmEvent: PalletEvmEvent;
@@ -1295,7 +1299,8 @@
     UpDataStructsPropertyScope: UpDataStructsPropertyScope;
     UpDataStructsRpcCollection: UpDataStructsRpcCollection;
     UpDataStructsSponsoringRateLimit: UpDataStructsSponsoringRateLimit;
-    UpDataStructsSponsorshipState: UpDataStructsSponsorshipState;
+    UpDataStructsSponsorshipStateAccountId32: UpDataStructsSponsorshipStateAccountId32;
+    UpDataStructsSponsorshipStateBasicCrossAccountIdRepr: UpDataStructsSponsorshipStateBasicCrossAccountIdRepr;
     UpDataStructsTokenChild: UpDataStructsTokenChild;
     UpDataStructsTokenData: UpDataStructsTokenData;
     UpgradeGoAhead: UpgradeGoAhead;
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -806,6 +806,64 @@
   readonly perPeriod: Compact<u128>;
 }
 
+/** @name PalletAppPromotionCall */
+export interface PalletAppPromotionCall extends Enum {
+  readonly isSetAdminAddress: boolean;
+  readonly asSetAdminAddress: {
+    readonly admin: PalletEvmAccountBasicCrossAccountIdRepr;
+  } & Struct;
+  readonly isStake: boolean;
+  readonly asStake: {
+    readonly amount: u128;
+  } & Struct;
+  readonly isUnstake: boolean;
+  readonly isSponsorCollection: boolean;
+  readonly asSponsorCollection: {
+    readonly collectionId: u32;
+  } & Struct;
+  readonly isStopSponsoringCollection: boolean;
+  readonly asStopSponsoringCollection: {
+    readonly collectionId: u32;
+  } & Struct;
+  readonly isSponsorContract: boolean;
+  readonly asSponsorContract: {
+    readonly contractId: H160;
+  } & Struct;
+  readonly isStopSponsoringContract: boolean;
+  readonly asStopSponsoringContract: {
+    readonly contractId: H160;
+  } & Struct;
+  readonly isPayoutStakers: boolean;
+  readonly asPayoutStakers: {
+    readonly stakersNumber: Option<u8>;
+  } & Struct;
+  readonly type: 'SetAdminAddress' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsoringCollection' | 'SponsorContract' | 'StopSponsoringContract' | 'PayoutStakers';
+}
+
+/** @name PalletAppPromotionError */
+export interface PalletAppPromotionError extends Enum {
+  readonly isAdminNotSet: boolean;
+  readonly isNoPermission: boolean;
+  readonly isNotSufficientFunds: boolean;
+  readonly isPendingForBlockOverflow: boolean;
+  readonly isSponsorNotSet: boolean;
+  readonly isIncorrectLockedBalanceOperation: boolean;
+  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation';
+}
+
+/** @name PalletAppPromotionEvent */
+export interface PalletAppPromotionEvent extends Enum {
+  readonly isStakingRecalculation: boolean;
+  readonly asStakingRecalculation: ITuple<[AccountId32, u128, u128]>;
+  readonly isStake: boolean;
+  readonly asStake: ITuple<[AccountId32, u128]>;
+  readonly isUnstake: boolean;
+  readonly asUnstake: ITuple<[AccountId32, u128]>;
+  readonly isSetAdmin: boolean;
+  readonly asSetAdmin: AccountId32;
+  readonly type: 'StakingRecalculation' | 'Stake' | 'Unstake' | 'SetAdmin';
+}
+
 /** @name PalletBalancesAccountData */
 export interface PalletBalancesAccountData extends Struct {
   readonly free: u128;
@@ -1127,7 +1185,19 @@
 /** @name PalletEvmContractHelpersError */
 export interface PalletEvmContractHelpersError extends Enum {
   readonly isNoPermission: boolean;
-  readonly type: 'NoPermission';
+  readonly isNoPendingSponsor: boolean;
+  readonly type: 'NoPermission' | 'NoPendingSponsor';
+}
+
+/** @name PalletEvmContractHelpersEvent */
+export interface PalletEvmContractHelpersEvent extends Enum {
+  readonly isContractSponsorSet: boolean;
+  readonly asContractSponsorSet: ITuple<[H160, AccountId32]>;
+  readonly isContractSponsorshipConfirmed: boolean;
+  readonly asContractSponsorshipConfirmed: ITuple<[H160, AccountId32]>;
+  readonly isContractSponsorRemoved: boolean;
+  readonly asContractSponsorRemoved: H160;
+  readonly type: 'ContractSponsorSet' | 'ContractSponsorshipConfirmed' | 'ContractSponsorRemoved';
 }
 
 /** @name PalletEvmContractHelpersSponsoringModeT */
@@ -2419,7 +2489,7 @@
   readonly name: Vec<u16>;
   readonly description: Vec<u16>;
   readonly tokenPrefix: Bytes;
-  readonly sponsorship: UpDataStructsSponsorshipState;
+  readonly sponsorship: UpDataStructsSponsorshipStateAccountId32;
   readonly limits: UpDataStructsCollectionLimits;
   readonly permissions: UpDataStructsCollectionPermissions;
   readonly externalCollection: bool;
@@ -2580,8 +2650,7 @@
 export interface UpDataStructsPropertyScope extends Enum {
   readonly isNone: boolean;
   readonly isRmrk: boolean;
-  readonly isEth: boolean;
-  readonly type: 'None' | 'Rmrk' | 'Eth';
+  readonly type: 'None' | 'Rmrk';
 }
 
 /** @name UpDataStructsRpcCollection */
@@ -2591,7 +2660,7 @@
   readonly name: Vec<u16>;
   readonly description: Vec<u16>;
   readonly tokenPrefix: Bytes;
-  readonly sponsorship: UpDataStructsSponsorshipState;
+  readonly sponsorship: UpDataStructsSponsorshipStateAccountId32;
   readonly limits: UpDataStructsCollectionLimits;
   readonly permissions: UpDataStructsCollectionPermissions;
   readonly tokenPropertyPermissions: Vec<UpDataStructsPropertyKeyPermission>;
@@ -2607,8 +2676,8 @@
   readonly type: 'SponsoringDisabled' | 'Blocks';
 }
 
-/** @name UpDataStructsSponsorshipState */
-export interface UpDataStructsSponsorshipState extends Enum {
+/** @name UpDataStructsSponsorshipStateAccountId32 */
+export interface UpDataStructsSponsorshipStateAccountId32 extends Enum {
   readonly isDisabled: boolean;
   readonly isUnconfirmed: boolean;
   readonly asUnconfirmed: AccountId32;
@@ -2617,6 +2686,16 @@
   readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed';
 }
 
+/** @name UpDataStructsSponsorshipStateBasicCrossAccountIdRepr */
+export interface UpDataStructsSponsorshipStateBasicCrossAccountIdRepr extends Enum {
+  readonly isDisabled: boolean;
+  readonly isUnconfirmed: boolean;
+  readonly asUnconfirmed: PalletEvmAccountBasicCrossAccountIdRepr;
+  readonly isConfirmed: boolean;
+  readonly asConfirmed: PalletEvmAccountBasicCrossAccountIdRepr;
+  readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed';
+}
+
 /** @name UpDataStructsTokenChild */
 export interface UpDataStructsTokenChild extends Struct {
   readonly token: u32;
modifiedtests/src/interfaces/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/definitions.ts
+++ b/tests/src/interfaces/definitions.ts
@@ -15,5 +15,6 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 export {default as unique} from './unique/definitions';
+export {default as appPromotion} from './appPromotion/definitions';
 export {default as rmrk} from './rmrk/definitions';
 export {default as default} from './default/definitions';
\ No newline at end of file
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1057,7 +1057,18 @@
     }
   },
   /**
-   * Lookup103: pallet_evm::pallet::Event<T>
+   * Lookup103: pallet_app_promotion::pallet::Event<T>
+   **/
+  PalletAppPromotionEvent: {
+    _enum: {
+      StakingRecalculation: '(AccountId32,u128,u128)',
+      Stake: '(AccountId32,u128)',
+      Unstake: '(AccountId32,u128)',
+      SetAdmin: 'AccountId32'
+    }
+  },
+  /**
+   * Lookup104: pallet_evm::pallet::Event<T>
    **/
   PalletEvmEvent: {
     _enum: {
@@ -1071,7 +1082,7 @@
     }
   },
   /**
-   * Lookup104: ethereum::log::Log
+   * Lookup105: ethereum::log::Log
    **/
   EthereumLog: {
     address: 'H160',
@@ -1079,7 +1090,7 @@
     data: 'Bytes'
   },
   /**
-   * Lookup108: pallet_ethereum::pallet::Event
+   * Lookup109: pallet_ethereum::pallet::Event
    **/
   PalletEthereumEvent: {
     _enum: {
@@ -1087,7 +1098,7 @@
     }
   },
   /**
-   * Lookup109: evm_core::error::ExitReason
+   * Lookup110: evm_core::error::ExitReason
    **/
   EvmCoreErrorExitReason: {
     _enum: {
@@ -1098,13 +1109,13 @@
     }
   },
   /**
-   * Lookup110: evm_core::error::ExitSucceed
+   * Lookup111: evm_core::error::ExitSucceed
    **/
   EvmCoreErrorExitSucceed: {
     _enum: ['Stopped', 'Returned', 'Suicided']
   },
   /**
-   * Lookup111: evm_core::error::ExitError
+   * Lookup112: evm_core::error::ExitError
    **/
   EvmCoreErrorExitError: {
     _enum: {
@@ -1126,13 +1137,13 @@
     }
   },
   /**
-   * Lookup114: evm_core::error::ExitRevert
+   * Lookup115: evm_core::error::ExitRevert
    **/
   EvmCoreErrorExitRevert: {
     _enum: ['Reverted']
   },
   /**
-   * Lookup115: evm_core::error::ExitFatal
+   * Lookup116: evm_core::error::ExitFatal
    **/
   EvmCoreErrorExitFatal: {
     _enum: {
@@ -1143,7 +1154,17 @@
     }
   },
   /**
-   * Lookup116: frame_system::Phase
+   * Lookup117: pallet_evm_contract_helpers::pallet::Event<T>
+   **/
+  PalletEvmContractHelpersEvent: {
+    _enum: {
+      ContractSponsorSet: '(H160,AccountId32)',
+      ContractSponsorshipConfirmed: '(H160,AccountId32)',
+      ContractSponsorRemoved: 'H160'
+    }
+  },
+  /**
+   * Lookup118: frame_system::Phase
    **/
   FrameSystemPhase: {
     _enum: {
@@ -1153,14 +1174,14 @@
     }
   },
   /**
-   * Lookup118: frame_system::LastRuntimeUpgradeInfo
+   * Lookup120: frame_system::LastRuntimeUpgradeInfo
    **/
   FrameSystemLastRuntimeUpgradeInfo: {
     specVersion: 'Compact<u32>',
     specName: 'Text'
   },
   /**
-   * Lookup119: frame_system::pallet::Call<T>
+   * Lookup121: frame_system::pallet::Call<T>
    **/
   FrameSystemCall: {
     _enum: {
@@ -1198,7 +1219,7 @@
     }
   },
   /**
-   * Lookup124: frame_system::limits::BlockWeights
+   * Lookup126: frame_system::limits::BlockWeights
    **/
   FrameSystemLimitsBlockWeights: {
     baseBlock: 'u64',
@@ -1206,7 +1227,7 @@
     perClass: 'FrameSupportWeightsPerDispatchClassWeightsPerClass'
   },
   /**
-   * Lookup125: frame_support::weights::PerDispatchClass<frame_system::limits::WeightsPerClass>
+   * Lookup127: frame_support::weights::PerDispatchClass<frame_system::limits::WeightsPerClass>
    **/
   FrameSupportWeightsPerDispatchClassWeightsPerClass: {
     normal: 'FrameSystemLimitsWeightsPerClass',
@@ -1214,7 +1235,7 @@
     mandatory: 'FrameSystemLimitsWeightsPerClass'
   },
   /**
-   * Lookup126: frame_system::limits::WeightsPerClass
+   * Lookup128: frame_system::limits::WeightsPerClass
    **/
   FrameSystemLimitsWeightsPerClass: {
     baseExtrinsic: 'u64',
@@ -1223,13 +1244,13 @@
     reserved: 'Option<u64>'
   },
   /**
-   * Lookup128: frame_system::limits::BlockLength
+   * Lookup130: frame_system::limits::BlockLength
    **/
   FrameSystemLimitsBlockLength: {
     max: 'FrameSupportWeightsPerDispatchClassU32'
   },
   /**
-   * Lookup129: frame_support::weights::PerDispatchClass<T>
+   * Lookup131: frame_support::weights::PerDispatchClass<T>
    **/
   FrameSupportWeightsPerDispatchClassU32: {
     normal: 'u32',
@@ -1237,14 +1258,14 @@
     mandatory: 'u32'
   },
   /**
-   * Lookup130: frame_support::weights::RuntimeDbWeight
+   * Lookup132: frame_support::weights::RuntimeDbWeight
    **/
   FrameSupportWeightsRuntimeDbWeight: {
     read: 'u64',
     write: 'u64'
   },
   /**
-   * Lookup131: sp_version::RuntimeVersion
+   * Lookup133: sp_version::RuntimeVersion
    **/
   SpVersionRuntimeVersion: {
     specName: 'Text',
@@ -1257,13 +1278,13 @@
     stateVersion: 'u8'
   },
   /**
-   * Lookup136: frame_system::pallet::Error<T>
+   * Lookup138: frame_system::pallet::Error<T>
    **/
   FrameSystemError: {
     _enum: ['InvalidSpecName', 'SpecVersionNeedsToIncrease', 'FailedToExtractRuntimeVersion', 'NonDefaultComposite', 'NonZeroRefCount', 'CallFiltered']
   },
   /**
-   * Lookup137: polkadot_primitives::v2::PersistedValidationData<primitive_types::H256, N>
+   * Lookup139: polkadot_primitives::v2::PersistedValidationData<primitive_types::H256, N>
    **/
   PolkadotPrimitivesV2PersistedValidationData: {
     parentHead: 'Bytes',
@@ -1272,19 +1293,19 @@
     maxPovSize: 'u32'
   },
   /**
-   * Lookup140: polkadot_primitives::v2::UpgradeRestriction
+   * Lookup142: polkadot_primitives::v2::UpgradeRestriction
    **/
   PolkadotPrimitivesV2UpgradeRestriction: {
     _enum: ['Present']
   },
   /**
-   * Lookup141: sp_trie::storage_proof::StorageProof
+   * Lookup143: sp_trie::storage_proof::StorageProof
    **/
   SpTrieStorageProof: {
     trieNodes: 'BTreeSet<Bytes>'
   },
   /**
-   * Lookup143: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot
+   * Lookup145: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot
    **/
   CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot: {
     dmqMqcHead: 'H256',
@@ -1293,7 +1314,7 @@
     egressChannels: 'Vec<(u32,PolkadotPrimitivesV2AbridgedHrmpChannel)>'
   },
   /**
-   * Lookup146: polkadot_primitives::v2::AbridgedHrmpChannel
+   * Lookup148: polkadot_primitives::v2::AbridgedHrmpChannel
    **/
   PolkadotPrimitivesV2AbridgedHrmpChannel: {
     maxCapacity: 'u32',
@@ -1304,7 +1325,7 @@
     mqcHead: 'Option<H256>'
   },
   /**
-   * Lookup147: polkadot_primitives::v2::AbridgedHostConfiguration
+   * Lookup149: polkadot_primitives::v2::AbridgedHostConfiguration
    **/
   PolkadotPrimitivesV2AbridgedHostConfiguration: {
     maxCodeSize: 'u32',
@@ -1318,14 +1339,14 @@
     validationUpgradeDelay: 'u32'
   },
   /**
-   * Lookup153: polkadot_core_primitives::OutboundHrmpMessage<polkadot_parachain::primitives::Id>
+   * Lookup155: polkadot_core_primitives::OutboundHrmpMessage<polkadot_parachain::primitives::Id>
    **/
   PolkadotCorePrimitivesOutboundHrmpMessage: {
     recipient: 'u32',
     data: 'Bytes'
   },
   /**
-   * Lookup154: cumulus_pallet_parachain_system::pallet::Call<T>
+   * Lookup156: cumulus_pallet_parachain_system::pallet::Call<T>
    **/
   CumulusPalletParachainSystemCall: {
     _enum: {
@@ -1344,7 +1365,7 @@
     }
   },
   /**
-   * Lookup155: cumulus_primitives_parachain_inherent::ParachainInherentData
+   * Lookup157: cumulus_primitives_parachain_inherent::ParachainInherentData
    **/
   CumulusPrimitivesParachainInherentParachainInherentData: {
     validationData: 'PolkadotPrimitivesV2PersistedValidationData',
@@ -1353,27 +1374,27 @@
     horizontalMessages: 'BTreeMap<u32, Vec<PolkadotCorePrimitivesInboundHrmpMessage>>'
   },
   /**
-   * Lookup157: polkadot_core_primitives::InboundDownwardMessage<BlockNumber>
+   * Lookup159: polkadot_core_primitives::InboundDownwardMessage<BlockNumber>
    **/
   PolkadotCorePrimitivesInboundDownwardMessage: {
     sentAt: 'u32',
     msg: 'Bytes'
   },
   /**
-   * Lookup160: polkadot_core_primitives::InboundHrmpMessage<BlockNumber>
+   * Lookup162: polkadot_core_primitives::InboundHrmpMessage<BlockNumber>
    **/
   PolkadotCorePrimitivesInboundHrmpMessage: {
     sentAt: 'u32',
     data: 'Bytes'
   },
   /**
-   * Lookup163: cumulus_pallet_parachain_system::pallet::Error<T>
+   * Lookup165: cumulus_pallet_parachain_system::pallet::Error<T>
    **/
   CumulusPalletParachainSystemError: {
     _enum: ['OverlappingUpgrades', 'ProhibitedByPolkadot', 'TooBig', 'ValidationDataNotAvailable', 'HostConfigurationNotAvailable', 'NotScheduled', 'NothingAuthorized', 'Unauthorized']
   },
   /**
-   * Lookup165: pallet_balances::BalanceLock<Balance>
+   * Lookup167: pallet_balances::BalanceLock<Balance>
    **/
   PalletBalancesBalanceLock: {
     id: '[u8;8]',
@@ -1381,26 +1402,26 @@
     reasons: 'PalletBalancesReasons'
   },
   /**
-   * Lookup166: pallet_balances::Reasons
+   * Lookup168: pallet_balances::Reasons
    **/
   PalletBalancesReasons: {
     _enum: ['Fee', 'Misc', 'All']
   },
   /**
-   * Lookup169: pallet_balances::ReserveData<ReserveIdentifier, Balance>
+   * Lookup171: pallet_balances::ReserveData<ReserveIdentifier, Balance>
    **/
   PalletBalancesReserveData: {
     id: '[u8;16]',
     amount: 'u128'
   },
   /**
-   * Lookup171: pallet_balances::Releases
+   * Lookup173: pallet_balances::Releases
    **/
   PalletBalancesReleases: {
     _enum: ['V1_0_0', 'V2_0_0']
   },
   /**
-   * Lookup172: pallet_balances::pallet::Call<T, I>
+   * Lookup174: pallet_balances::pallet::Call<T, I>
    **/
   PalletBalancesCall: {
     _enum: {
@@ -1433,13 +1454,13 @@
     }
   },
   /**
-   * Lookup175: pallet_balances::pallet::Error<T, I>
+   * Lookup177: pallet_balances::pallet::Error<T, I>
    **/
   PalletBalancesError: {
     _enum: ['VestingBalance', 'LiquidityRestrictions', 'InsufficientBalance', 'ExistentialDeposit', 'KeepAlive', 'ExistingVestingSchedule', 'DeadAccount', 'TooManyReserves']
   },
   /**
-   * Lookup177: pallet_timestamp::pallet::Call<T>
+   * Lookup179: pallet_timestamp::pallet::Call<T>
    **/
   PalletTimestampCall: {
     _enum: {
@@ -1449,13 +1470,13 @@
     }
   },
   /**
-   * Lookup179: pallet_transaction_payment::Releases
+   * Lookup181: pallet_transaction_payment::Releases
    **/
   PalletTransactionPaymentReleases: {
     _enum: ['V1Ancient', 'V2']
   },
   /**
-   * Lookup180: pallet_treasury::Proposal<sp_core::crypto::AccountId32, Balance>
+   * Lookup182: pallet_treasury::Proposal<sp_core::crypto::AccountId32, Balance>
    **/
   PalletTreasuryProposal: {
     proposer: 'AccountId32',
@@ -1464,7 +1485,7 @@
     bond: 'u128'
   },
   /**
-   * Lookup183: pallet_treasury::pallet::Call<T, I>
+   * Lookup185: pallet_treasury::pallet::Call<T, I>
    **/
   PalletTreasuryCall: {
     _enum: {
@@ -1488,17 +1509,17 @@
     }
   },
   /**
-   * Lookup186: frame_support::PalletId
+   * Lookup188: frame_support::PalletId
    **/
   FrameSupportPalletId: '[u8;8]',
   /**
-   * Lookup187: pallet_treasury::pallet::Error<T, I>
+   * Lookup189: pallet_treasury::pallet::Error<T, I>
    **/
   PalletTreasuryError: {
     _enum: ['InsufficientProposersBalance', 'InvalidIndex', 'TooManyApprovals', 'InsufficientPermission', 'ProposalNotApproved']
   },
   /**
-   * Lookup188: pallet_sudo::pallet::Call<T>
+   * Lookup190: pallet_sudo::pallet::Call<T>
    **/
   PalletSudoCall: {
     _enum: {
@@ -1522,7 +1543,7 @@
     }
   },
   /**
-   * Lookup190: orml_vesting::module::Call<T>
+   * Lookup192: orml_vesting::module::Call<T>
    **/
   OrmlVestingModuleCall: {
     _enum: {
@@ -1541,7 +1562,7 @@
     }
   },
   /**
-   * Lookup192: cumulus_pallet_xcmp_queue::pallet::Call<T>
+   * Lookup194: cumulus_pallet_xcmp_queue::pallet::Call<T>
    **/
   CumulusPalletXcmpQueueCall: {
     _enum: {
@@ -1590,7 +1611,7 @@
     }
   },
   /**
-   * Lookup193: pallet_xcm::pallet::Call<T>
+   * Lookup195: pallet_xcm::pallet::Call<T>
    **/
   PalletXcmCall: {
     _enum: {
@@ -1644,7 +1665,7 @@
     }
   },
   /**
-   * Lookup194: xcm::VersionedXcm<Call>
+   * Lookup196: xcm::VersionedXcm<Call>
    **/
   XcmVersionedXcm: {
     _enum: {
@@ -1654,7 +1675,7 @@
     }
   },
   /**
-   * Lookup195: xcm::v0::Xcm<Call>
+   * Lookup197: xcm::v0::Xcm<Call>
    **/
   XcmV0Xcm: {
     _enum: {
@@ -1708,7 +1729,7 @@
     }
   },
   /**
-   * Lookup197: xcm::v0::order::Order<Call>
+   * Lookup199: xcm::v0::order::Order<Call>
    **/
   XcmV0Order: {
     _enum: {
@@ -1751,7 +1772,7 @@
     }
   },
   /**
-   * Lookup199: xcm::v0::Response
+   * Lookup201: xcm::v0::Response
    **/
   XcmV0Response: {
     _enum: {
@@ -1759,7 +1780,7 @@
     }
   },
   /**
-   * Lookup200: xcm::v1::Xcm<Call>
+   * Lookup202: xcm::v1::Xcm<Call>
    **/
   XcmV1Xcm: {
     _enum: {
@@ -1818,7 +1839,7 @@
     }
   },
   /**
-   * Lookup202: xcm::v1::order::Order<Call>
+   * Lookup204: xcm::v1::order::Order<Call>
    **/
   XcmV1Order: {
     _enum: {
@@ -1863,7 +1884,7 @@
     }
   },
   /**
-   * Lookup204: xcm::v1::Response
+   * Lookup206: xcm::v1::Response
    **/
   XcmV1Response: {
     _enum: {
@@ -1872,11 +1893,11 @@
     }
   },
   /**
-   * Lookup218: cumulus_pallet_xcm::pallet::Call<T>
+   * Lookup220: cumulus_pallet_xcm::pallet::Call<T>
    **/
   CumulusPalletXcmCall: 'Null',
   /**
-   * Lookup219: cumulus_pallet_dmp_queue::pallet::Call<T>
+   * Lookup221: cumulus_pallet_dmp_queue::pallet::Call<T>
    **/
   CumulusPalletDmpQueueCall: {
     _enum: {
@@ -1887,7 +1908,7 @@
     }
   },
   /**
-   * Lookup220: pallet_inflation::pallet::Call<T>
+   * Lookup222: pallet_inflation::pallet::Call<T>
    **/
   PalletInflationCall: {
     _enum: {
@@ -1897,7 +1918,7 @@
     }
   },
   /**
-   * Lookup221: pallet_unique::Call<T>
+   * Lookup223: pallet_unique::Call<T>
    **/
   PalletUniqueCall: {
     _enum: {
@@ -2029,7 +2050,7 @@
     }
   },
   /**
-   * Lookup226: up_data_structs::CollectionMode
+   * Lookup228: up_data_structs::CollectionMode
    **/
   UpDataStructsCollectionMode: {
     _enum: {
@@ -2039,7 +2060,7 @@
     }
   },
   /**
-   * Lookup227: up_data_structs::CreateCollectionData<sp_core::crypto::AccountId32>
+   * Lookup229: up_data_structs::CreateCollectionData<sp_core::crypto::AccountId32>
    **/
   UpDataStructsCreateCollectionData: {
     mode: 'UpDataStructsCollectionMode',
@@ -2054,13 +2075,13 @@
     properties: 'Vec<UpDataStructsProperty>'
   },
   /**
-   * Lookup229: up_data_structs::AccessMode
+   * Lookup231: up_data_structs::AccessMode
    **/
   UpDataStructsAccessMode: {
     _enum: ['Normal', 'AllowList']
   },
   /**
-   * Lookup231: up_data_structs::CollectionLimits
+   * Lookup233: up_data_structs::CollectionLimits
    **/
   UpDataStructsCollectionLimits: {
     accountTokenOwnershipLimit: 'Option<u32>',
@@ -2074,7 +2095,7 @@
     transfersEnabled: 'Option<bool>'
   },
   /**
-   * Lookup233: up_data_structs::SponsoringRateLimit
+   * Lookup235: up_data_structs::SponsoringRateLimit
    **/
   UpDataStructsSponsoringRateLimit: {
     _enum: {
@@ -2083,7 +2104,7 @@
     }
   },
   /**
-   * Lookup236: up_data_structs::CollectionPermissions
+   * Lookup238: up_data_structs::CollectionPermissions
    **/
   UpDataStructsCollectionPermissions: {
     access: 'Option<UpDataStructsAccessMode>',
@@ -2091,7 +2112,7 @@
     nesting: 'Option<UpDataStructsNestingPermissions>'
   },
   /**
-   * Lookup238: up_data_structs::NestingPermissions
+   * Lookup240: up_data_structs::NestingPermissions
    **/
   UpDataStructsNestingPermissions: {
     tokenOwner: 'bool',
@@ -2099,18 +2120,18 @@
     restricted: 'Option<UpDataStructsOwnerRestrictedSet>'
   },
   /**
-   * Lookup240: up_data_structs::OwnerRestrictedSet
+   * Lookup242: up_data_structs::OwnerRestrictedSet
    **/
   UpDataStructsOwnerRestrictedSet: 'BTreeSet<u32>',
   /**
-   * Lookup245: up_data_structs::PropertyKeyPermission
+   * Lookup247: up_data_structs::PropertyKeyPermission
    **/
   UpDataStructsPropertyKeyPermission: {
     key: 'Bytes',
     permission: 'UpDataStructsPropertyPermission'
   },
   /**
-   * Lookup246: up_data_structs::PropertyPermission
+   * Lookup248: up_data_structs::PropertyPermission
    **/
   UpDataStructsPropertyPermission: {
     mutable: 'bool',
@@ -2118,14 +2139,14 @@
     tokenOwner: 'bool'
   },
   /**
-   * Lookup249: up_data_structs::Property
+   * Lookup251: up_data_structs::Property
    **/
   UpDataStructsProperty: {
     key: 'Bytes',
     value: 'Bytes'
   },
   /**
-   * Lookup252: up_data_structs::CreateItemData
+   * Lookup254: up_data_structs::CreateItemData
    **/
   UpDataStructsCreateItemData: {
     _enum: {
@@ -2135,26 +2156,26 @@
     }
   },
   /**
-   * Lookup253: up_data_structs::CreateNftData
+   * Lookup255: up_data_structs::CreateNftData
    **/
   UpDataStructsCreateNftData: {
     properties: 'Vec<UpDataStructsProperty>'
   },
   /**
-   * Lookup254: up_data_structs::CreateFungibleData
+   * Lookup256: up_data_structs::CreateFungibleData
    **/
   UpDataStructsCreateFungibleData: {
     value: 'u128'
   },
   /**
-   * Lookup255: up_data_structs::CreateReFungibleData
+   * Lookup257: up_data_structs::CreateReFungibleData
    **/
   UpDataStructsCreateReFungibleData: {
     pieces: 'u128',
     properties: 'Vec<UpDataStructsProperty>'
   },
   /**
-   * Lookup258: up_data_structs::CreateItemExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup260: up_data_structs::CreateItemExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   UpDataStructsCreateItemExData: {
     _enum: {
@@ -2165,14 +2186,14 @@
     }
   },
   /**
-   * Lookup260: up_data_structs::CreateNftExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup262: up_data_structs::CreateNftExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   UpDataStructsCreateNftExData: {
     properties: 'Vec<UpDataStructsProperty>',
     owner: 'PalletEvmAccountBasicCrossAccountIdRepr'
   },
   /**
-   * Lookup267: up_data_structs::CreateRefungibleExSingleOwner<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup269: up_data_structs::CreateRefungibleExSingleOwner<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   UpDataStructsCreateRefungibleExSingleOwner: {
     user: 'PalletEvmAccountBasicCrossAccountIdRepr',
@@ -2180,14 +2201,14 @@
     properties: 'Vec<UpDataStructsProperty>'
   },
   /**
-   * Lookup269: up_data_structs::CreateRefungibleExMultipleOwners<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup271: up_data_structs::CreateRefungibleExMultipleOwners<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   UpDataStructsCreateRefungibleExMultipleOwners: {
     users: 'BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>',
     properties: 'Vec<UpDataStructsProperty>'
   },
   /**
-   * Lookup270: pallet_unique_scheduler::pallet::Call<T>
+   * Lookup272: pallet_unique_scheduler::pallet::Call<T>
    **/
   PalletUniqueSchedulerCall: {
     _enum: {
@@ -2211,7 +2232,7 @@
     }
   },
   /**
-   * Lookup272: frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>
+   * Lookup274: frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>
    **/
   FrameSupportScheduleMaybeHashed: {
     _enum: {
@@ -2220,7 +2241,7 @@
     }
   },
   /**
-   * Lookup273: pallet_configuration::pallet::Call<T>
+   * Lookup275: pallet_configuration::pallet::Call<T>
    **/
   PalletConfigurationCall: {
     _enum: {
@@ -2233,15 +2254,15 @@
     }
   },
   /**
-   * Lookup274: pallet_template_transaction_payment::Call<T>
+   * Lookup276: pallet_template_transaction_payment::Call<T>
    **/
   PalletTemplateTransactionPaymentCall: 'Null',
   /**
-   * Lookup275: pallet_structure::pallet::Call<T>
+   * Lookup277: pallet_structure::pallet::Call<T>
    **/
   PalletStructureCall: 'Null',
   /**
-   * Lookup276: pallet_rmrk_core::pallet::Call<T>
+   * Lookup278: pallet_rmrk_core::pallet::Call<T>
    **/
   PalletRmrkCoreCall: {
     _enum: {
@@ -2332,7 +2353,7 @@
     }
   },
   /**
-   * Lookup282: rmrk_traits::resource::ResourceTypes<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup284: rmrk_traits::resource::ResourceTypes<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsResourceResourceTypes: {
     _enum: {
@@ -2342,7 +2363,7 @@
     }
   },
   /**
-   * Lookup284: rmrk_traits::resource::BasicResource<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup286: rmrk_traits::resource::BasicResource<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsResourceBasicResource: {
     src: 'Option<Bytes>',
@@ -2351,7 +2372,7 @@
     thumb: 'Option<Bytes>'
   },
   /**
-   * Lookup286: rmrk_traits::resource::ComposableResource<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup288: rmrk_traits::resource::ComposableResource<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsResourceComposableResource: {
     parts: 'Vec<u32>',
@@ -2362,7 +2383,7 @@
     thumb: 'Option<Bytes>'
   },
   /**
-   * Lookup287: rmrk_traits::resource::SlotResource<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup289: rmrk_traits::resource::SlotResource<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsResourceSlotResource: {
     base: 'u32',
@@ -2373,7 +2394,7 @@
     thumb: 'Option<Bytes>'
   },
   /**
-   * Lookup290: pallet_rmrk_equip::pallet::Call<T>
+   * Lookup292: pallet_rmrk_equip::pallet::Call<T>
    **/
   PalletRmrkEquipCall: {
     _enum: {
@@ -2394,7 +2415,7 @@
     }
   },
   /**
-   * Lookup293: rmrk_traits::part::PartType<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup295: rmrk_traits::part::PartType<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsPartPartType: {
     _enum: {
@@ -2403,7 +2424,7 @@
     }
   },
   /**
-   * Lookup295: rmrk_traits::part::FixedPart<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup297: rmrk_traits::part::FixedPart<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsPartFixedPart: {
     id: 'u32',
@@ -2411,7 +2432,7 @@
     src: 'Bytes'
   },
   /**
-   * Lookup296: rmrk_traits::part::SlotPart<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup298: rmrk_traits::part::SlotPart<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsPartSlotPart: {
     id: 'u32',
@@ -2420,7 +2441,7 @@
     z: 'u32'
   },
   /**
-   * Lookup297: rmrk_traits::part::EquippableList<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup299: rmrk_traits::part::EquippableList<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsPartEquippableList: {
     _enum: {
@@ -2430,7 +2451,7 @@
     }
   },
   /**
-   * Lookup299: rmrk_traits::theme::Theme<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<rmrk_traits::theme::ThemeProperty<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>, S>>
+   * Lookup301: rmrk_traits::theme::Theme<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<rmrk_traits::theme::ThemeProperty<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>, S>>
    **/
   RmrkTraitsTheme: {
     name: 'Bytes',
@@ -2438,14 +2459,43 @@
     inherit: 'bool'
   },
   /**
-   * Lookup301: rmrk_traits::theme::ThemeProperty<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup303: rmrk_traits::theme::ThemeProperty<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsThemeThemeProperty: {
     key: 'Bytes',
     value: 'Bytes'
   },
   /**
-   * Lookup303: pallet_evm::pallet::Call<T>
+   * Lookup305: pallet_app_promotion::pallet::Call<T>
+   **/
+  PalletAppPromotionCall: {
+    _enum: {
+      set_admin_address: {
+        admin: 'PalletEvmAccountBasicCrossAccountIdRepr',
+      },
+      stake: {
+        amount: 'u128',
+      },
+      unstake: 'Null',
+      sponsor_collection: {
+        collectionId: 'u32',
+      },
+      stop_sponsoring_collection: {
+        collectionId: 'u32',
+      },
+      sponsor_contract: {
+        contractId: 'H160',
+      },
+      stop_sponsoring_contract: {
+        contractId: 'H160',
+      },
+      payout_stakers: {
+        stakersNumber: 'Option<u8>'
+      }
+    }
+  },
+  /**
+   * Lookup307: pallet_evm::pallet::Call<T>
    **/
   PalletEvmCall: {
     _enum: {
@@ -2488,7 +2538,7 @@
     }
   },
   /**
-   * Lookup307: pallet_ethereum::pallet::Call<T>
+   * Lookup311: pallet_ethereum::pallet::Call<T>
    **/
   PalletEthereumCall: {
     _enum: {
@@ -2498,7 +2548,7 @@
     }
   },
   /**
-   * Lookup308: ethereum::transaction::TransactionV2
+   * Lookup312: ethereum::transaction::TransactionV2
    **/
   EthereumTransactionTransactionV2: {
     _enum: {
@@ -2508,7 +2558,7 @@
     }
   },
   /**
-   * Lookup309: ethereum::transaction::LegacyTransaction
+   * Lookup313: ethereum::transaction::LegacyTransaction
    **/
   EthereumTransactionLegacyTransaction: {
     nonce: 'U256',
@@ -2520,7 +2570,7 @@
     signature: 'EthereumTransactionTransactionSignature'
   },
   /**
-   * Lookup310: ethereum::transaction::TransactionAction
+   * Lookup314: ethereum::transaction::TransactionAction
    **/
   EthereumTransactionTransactionAction: {
     _enum: {
@@ -2529,7 +2579,7 @@
     }
   },
   /**
-   * Lookup311: ethereum::transaction::TransactionSignature
+   * Lookup315: ethereum::transaction::TransactionSignature
    **/
   EthereumTransactionTransactionSignature: {
     v: 'u64',
@@ -2537,7 +2587,7 @@
     s: 'H256'
   },
   /**
-   * Lookup313: ethereum::transaction::EIP2930Transaction
+   * Lookup317: ethereum::transaction::EIP2930Transaction
    **/
   EthereumTransactionEip2930Transaction: {
     chainId: 'u64',
@@ -2553,14 +2603,14 @@
     s: 'H256'
   },
   /**
-   * Lookup315: ethereum::transaction::AccessListItem
+   * Lookup319: ethereum::transaction::AccessListItem
    **/
   EthereumTransactionAccessListItem: {
     address: 'H160',
     storageKeys: 'Vec<H256>'
   },
   /**
-   * Lookup316: ethereum::transaction::EIP1559Transaction
+   * Lookup320: ethereum::transaction::EIP1559Transaction
    **/
   EthereumTransactionEip1559Transaction: {
     chainId: 'u64',
@@ -2577,7 +2627,7 @@
     s: 'H256'
   },
   /**
-   * Lookup317: pallet_evm_migration::pallet::Call<T>
+   * Lookup321: pallet_evm_migration::pallet::Call<T>
    **/
   PalletEvmMigrationCall: {
     _enum: {
@@ -2595,19 +2645,19 @@
     }
   },
   /**
-   * Lookup320: pallet_sudo::pallet::Error<T>
+   * Lookup324: pallet_sudo::pallet::Error<T>
    **/
   PalletSudoError: {
     _enum: ['RequireSudo']
   },
   /**
-   * Lookup322: orml_vesting::module::Error<T>
+   * Lookup326: orml_vesting::module::Error<T>
    **/
   OrmlVestingModuleError: {
     _enum: ['ZeroVestingPeriod', 'ZeroVestingPeriodCount', 'InsufficientBalanceToLock', 'TooManyVestingSchedules', 'AmountLow', 'MaxVestingSchedulesExceeded']
   },
   /**
-   * Lookup324: cumulus_pallet_xcmp_queue::InboundChannelDetails
+   * Lookup328: cumulus_pallet_xcmp_queue::InboundChannelDetails
    **/
   CumulusPalletXcmpQueueInboundChannelDetails: {
     sender: 'u32',
@@ -2615,19 +2665,19 @@
     messageMetadata: 'Vec<(u32,PolkadotParachainPrimitivesXcmpMessageFormat)>'
   },
   /**
-   * Lookup325: cumulus_pallet_xcmp_queue::InboundState
+   * Lookup329: cumulus_pallet_xcmp_queue::InboundState
    **/
   CumulusPalletXcmpQueueInboundState: {
     _enum: ['Ok', 'Suspended']
   },
   /**
-   * Lookup328: polkadot_parachain::primitives::XcmpMessageFormat
+   * Lookup332: polkadot_parachain::primitives::XcmpMessageFormat
    **/
   PolkadotParachainPrimitivesXcmpMessageFormat: {
     _enum: ['ConcatenatedVersionedXcm', 'ConcatenatedEncodedBlob', 'Signals']
   },
   /**
-   * Lookup331: cumulus_pallet_xcmp_queue::OutboundChannelDetails
+   * Lookup335: cumulus_pallet_xcmp_queue::OutboundChannelDetails
    **/
   CumulusPalletXcmpQueueOutboundChannelDetails: {
     recipient: 'u32',
@@ -2637,13 +2687,13 @@
     lastIndex: 'u16'
   },
   /**
-   * Lookup332: cumulus_pallet_xcmp_queue::OutboundState
+   * Lookup336: cumulus_pallet_xcmp_queue::OutboundState
    **/
   CumulusPalletXcmpQueueOutboundState: {
     _enum: ['Ok', 'Suspended']
   },
   /**
-   * Lookup334: cumulus_pallet_xcmp_queue::QueueConfigData
+   * Lookup338: cumulus_pallet_xcmp_queue::QueueConfigData
    **/
   CumulusPalletXcmpQueueQueueConfigData: {
     suspendThreshold: 'u32',
@@ -2654,29 +2704,29 @@
     xcmpMaxIndividualWeight: 'u64'
   },
   /**
-   * Lookup336: cumulus_pallet_xcmp_queue::pallet::Error<T>
+   * Lookup340: cumulus_pallet_xcmp_queue::pallet::Error<T>
    **/
   CumulusPalletXcmpQueueError: {
     _enum: ['FailedToSend', 'BadXcmOrigin', 'BadXcm', 'BadOverweightIndex', 'WeightOverLimit']
   },
   /**
-   * Lookup337: pallet_xcm::pallet::Error<T>
+   * Lookup341: pallet_xcm::pallet::Error<T>
    **/
   PalletXcmError: {
     _enum: ['Unreachable', 'SendFailure', 'Filtered', 'UnweighableMessage', 'DestinationNotInvertible', 'Empty', 'CannotReanchor', 'TooManyAssets', 'InvalidOrigin', 'BadVersion', 'BadLocation', 'NoSubscription', 'AlreadySubscribed']
   },
   /**
-   * Lookup338: cumulus_pallet_xcm::pallet::Error<T>
+   * Lookup342: cumulus_pallet_xcm::pallet::Error<T>
    **/
   CumulusPalletXcmError: 'Null',
   /**
-   * Lookup339: cumulus_pallet_dmp_queue::ConfigData
+   * Lookup343: cumulus_pallet_dmp_queue::ConfigData
    **/
   CumulusPalletDmpQueueConfigData: {
     maxIndividual: 'u64'
   },
   /**
-   * Lookup340: cumulus_pallet_dmp_queue::PageIndexData
+   * Lookup344: cumulus_pallet_dmp_queue::PageIndexData
    **/
   CumulusPalletDmpQueuePageIndexData: {
     beginUsed: 'u32',
@@ -2684,19 +2734,19 @@
     overweightCount: 'u64'
   },
   /**
-   * Lookup343: cumulus_pallet_dmp_queue::pallet::Error<T>
+   * Lookup347: cumulus_pallet_dmp_queue::pallet::Error<T>
    **/
   CumulusPalletDmpQueueError: {
     _enum: ['Unknown', 'OverLimit']
   },
   /**
-   * Lookup347: pallet_unique::Error<T>
+   * Lookup351: pallet_unique::Error<T>
    **/
   PalletUniqueError: {
     _enum: ['CollectionDecimalPointLimitExceeded', 'ConfirmUnsetSponsorFail', 'EmptyArgument', 'RepartitionCalledOnNonRefungibleCollection']
   },
   /**
-   * Lookup350: pallet_unique_scheduler::ScheduledV3<frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>, BlockNumber, opal_runtime::OriginCaller, sp_core::crypto::AccountId32>
+   * Lookup354: pallet_unique_scheduler::ScheduledV3<frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>, BlockNumber, opal_runtime::OriginCaller, sp_core::crypto::AccountId32>
    **/
   PalletUniqueSchedulerScheduledV3: {
     maybeId: 'Option<[u8;16]>',
@@ -2706,7 +2756,7 @@
     origin: 'OpalRuntimeOriginCaller'
   },
   /**
-   * Lookup351: opal_runtime::OriginCaller
+   * Lookup355: opal_runtime::OriginCaller
    **/
   OpalRuntimeOriginCaller: {
     _enum: {
@@ -2815,7 +2865,7 @@
     }
   },
   /**
-   * Lookup352: frame_support::dispatch::RawOrigin<sp_core::crypto::AccountId32>
+   * Lookup356: frame_support::dispatch::RawOrigin<sp_core::crypto::AccountId32>
    **/
   FrameSupportDispatchRawOrigin: {
     _enum: {
@@ -2825,7 +2875,7 @@
     }
   },
   /**
-   * Lookup353: pallet_xcm::pallet::Origin
+   * Lookup357: pallet_xcm::pallet::Origin
    **/
   PalletXcmOrigin: {
     _enum: {
@@ -2834,7 +2884,7 @@
     }
   },
   /**
-   * Lookup354: cumulus_pallet_xcm::pallet::Origin
+   * Lookup358: cumulus_pallet_xcm::pallet::Origin
    **/
   CumulusPalletXcmOrigin: {
     _enum: {
@@ -2843,7 +2893,7 @@
     }
   },
   /**
-   * Lookup355: pallet_ethereum::RawOrigin
+   * Lookup359: pallet_ethereum::RawOrigin
    **/
   PalletEthereumRawOrigin: {
     _enum: {
@@ -2851,17 +2901,17 @@
     }
   },
   /**
-   * Lookup356: sp_core::Void
+   * Lookup360: sp_core::Void
    **/
   SpCoreVoid: 'Null',
   /**
-   * Lookup357: pallet_unique_scheduler::pallet::Error<T>
+   * Lookup361: pallet_unique_scheduler::pallet::Error<T>
    **/
   PalletUniqueSchedulerError: {
     _enum: ['FailedToSchedule', 'NotFound', 'TargetBlockNumberInPast', 'RescheduleNoChange']
   },
   /**
-   * Lookup358: up_data_structs::Collection<sp_core::crypto::AccountId32>
+   * Lookup362: up_data_structs::Collection<sp_core::crypto::AccountId32>
    **/
   UpDataStructsCollection: {
     owner: 'AccountId32',
@@ -2869,15 +2919,15 @@
     name: 'Vec<u16>',
     description: 'Vec<u16>',
     tokenPrefix: 'Bytes',
-    sponsorship: 'UpDataStructsSponsorshipState',
+    sponsorship: 'UpDataStructsSponsorshipStateAccountId32',
     limits: 'UpDataStructsCollectionLimits',
     permissions: 'UpDataStructsCollectionPermissions',
     externalCollection: 'bool'
   },
   /**
-   * Lookup359: up_data_structs::SponsorshipState<sp_core::crypto::AccountId32>
+   * Lookup363: up_data_structs::SponsorshipState<sp_core::crypto::AccountId32>
    **/
-  UpDataStructsSponsorshipState: {
+  UpDataStructsSponsorshipStateAccountId32: {
     _enum: {
       Disabled: 'Null',
       Unconfirmed: 'AccountId32',
@@ -2885,7 +2935,7 @@
     }
   },
   /**
-   * Lookup360: up_data_structs::Properties
+   * Lookup364: up_data_structs::Properties
    **/
   UpDataStructsProperties: {
     map: 'UpDataStructsPropertiesMapBoundedVec',
@@ -2893,15 +2943,15 @@
     spaceLimit: 'u32'
   },
   /**
-   * Lookup361: up_data_structs::PropertiesMap<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup365: up_data_structs::PropertiesMap<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   UpDataStructsPropertiesMapBoundedVec: 'BTreeMap<Bytes, Bytes>',
   /**
-   * Lookup366: up_data_structs::PropertiesMap<up_data_structs::PropertyPermission>
+   * Lookup370: up_data_structs::PropertiesMap<up_data_structs::PropertyPermission>
    **/
   UpDataStructsPropertiesMapPropertyPermission: 'BTreeMap<Bytes, UpDataStructsPropertyPermission>',
   /**
-   * Lookup373: up_data_structs::CollectionStats
+   * Lookup377: up_data_structs::CollectionStats
    **/
   UpDataStructsCollectionStats: {
     created: 'u32',
@@ -2909,18 +2959,18 @@
     alive: 'u32'
   },
   /**
-   * Lookup374: up_data_structs::TokenChild
+   * Lookup378: up_data_structs::TokenChild
    **/
   UpDataStructsTokenChild: {
     token: 'u32',
     collection: 'u32'
   },
   /**
-   * Lookup375: PhantomType::up_data_structs<T>
+   * Lookup379: PhantomType::up_data_structs<T>
    **/
   PhantomTypeUpDataStructs: '[(UpDataStructsTokenData,UpDataStructsRpcCollection,RmrkTraitsCollectionCollectionInfo,RmrkTraitsNftNftInfo,RmrkTraitsResourceResourceInfo,RmrkTraitsPropertyPropertyInfo,RmrkTraitsBaseBaseInfo,RmrkTraitsPartPartType,RmrkTraitsTheme,RmrkTraitsNftNftChild);0]',
   /**
-   * Lookup377: up_data_structs::TokenData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup381: up_data_structs::TokenData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   UpDataStructsTokenData: {
     properties: 'Vec<UpDataStructsProperty>',
@@ -2928,7 +2978,7 @@
     pieces: 'u128'
   },
   /**
-   * Lookup379: up_data_structs::RpcCollection<sp_core::crypto::AccountId32>
+   * Lookup383: up_data_structs::RpcCollection<sp_core::crypto::AccountId32>
    **/
   UpDataStructsRpcCollection: {
     owner: 'AccountId32',
@@ -2936,7 +2986,7 @@
     name: 'Vec<u16>',
     description: 'Vec<u16>',
     tokenPrefix: 'Bytes',
-    sponsorship: 'UpDataStructsSponsorshipState',
+    sponsorship: 'UpDataStructsSponsorshipStateAccountId32',
     limits: 'UpDataStructsCollectionLimits',
     permissions: 'UpDataStructsCollectionPermissions',
     tokenPropertyPermissions: 'Vec<UpDataStructsPropertyKeyPermission>',
@@ -2944,7 +2994,7 @@
     readOnly: 'bool'
   },
   /**
-   * Lookup380: rmrk_traits::collection::CollectionInfo<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_core::crypto::AccountId32>
+   * Lookup384: rmrk_traits::collection::CollectionInfo<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_core::crypto::AccountId32>
    **/
   RmrkTraitsCollectionCollectionInfo: {
     issuer: 'AccountId32',
@@ -2954,7 +3004,7 @@
     nftsCount: 'u32'
   },
   /**
-   * Lookup381: rmrk_traits::nft::NftInfo<sp_core::crypto::AccountId32, sp_arithmetic::per_things::Permill, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup385: rmrk_traits::nft::NftInfo<sp_core::crypto::AccountId32, sp_arithmetic::per_things::Permill, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsNftNftInfo: {
     owner: 'RmrkTraitsNftAccountIdOrCollectionNftTuple',
@@ -2964,14 +3014,14 @@
     pending: 'bool'
   },
   /**
-   * Lookup383: rmrk_traits::nft::RoyaltyInfo<sp_core::crypto::AccountId32, sp_arithmetic::per_things::Permill>
+   * Lookup387: rmrk_traits::nft::RoyaltyInfo<sp_core::crypto::AccountId32, sp_arithmetic::per_things::Permill>
    **/
   RmrkTraitsNftRoyaltyInfo: {
     recipient: 'AccountId32',
     amount: 'Permill'
   },
   /**
-   * Lookup384: rmrk_traits::resource::ResourceInfo<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup388: rmrk_traits::resource::ResourceInfo<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsResourceResourceInfo: {
     id: 'u32',
@@ -2980,14 +3030,14 @@
     pendingRemoval: 'bool'
   },
   /**
-   * Lookup385: rmrk_traits::property::PropertyInfo<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup389: rmrk_traits::property::PropertyInfo<sp_runtime::bounded::bounded_vec::BoundedVec<T, S>, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsPropertyPropertyInfo: {
     key: 'Bytes',
     value: 'Bytes'
   },
   /**
-   * Lookup386: rmrk_traits::base::BaseInfo<sp_core::crypto::AccountId32, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
+   * Lookup390: rmrk_traits::base::BaseInfo<sp_core::crypto::AccountId32, sp_runtime::bounded::bounded_vec::BoundedVec<T, S>>
    **/
   RmrkTraitsBaseBaseInfo: {
     issuer: 'AccountId32',
@@ -2995,80 +3045,86 @@
     symbol: 'Bytes'
   },
   /**
-   * Lookup387: rmrk_traits::nft::NftChild
+   * Lookup391: rmrk_traits::nft::NftChild
    **/
   RmrkTraitsNftNftChild: {
     collectionId: 'u32',
     nftId: 'u32'
   },
   /**
-   * Lookup389: pallet_common::pallet::Error<T>
+   * Lookup393: pallet_common::pallet::Error<T>
    **/
   PalletCommonError: {
     _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'CantDestroyNotEmptyCollection', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsZero', 'UnsupportedOperation', 'NotSufficientFounds', 'UserIsNotAllowedToNest', 'SourceCollectionIsNotAllowedToNest', 'CollectionFieldSizeExceeded', 'NoSpaceForProperty', 'PropertyLimitReached', 'PropertyKeyIsTooLong', 'InvalidCharacterInPropertyKey', 'EmptyPropertyKey', 'CollectionIsExternal', 'CollectionIsInternal']
   },
   /**
-   * Lookup391: pallet_fungible::pallet::Error<T>
+   * Lookup395: pallet_fungible::pallet::Error<T>
    **/
   PalletFungibleError: {
     _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed']
   },
   /**
-   * Lookup392: pallet_refungible::ItemData
+   * Lookup396: pallet_refungible::ItemData
    **/
   PalletRefungibleItemData: {
     constData: 'Bytes'
   },
   /**
-   * Lookup397: pallet_refungible::pallet::Error<T>
+   * Lookup401: pallet_refungible::pallet::Error<T>
    **/
   PalletRefungibleError: {
     _enum: ['NotRefungibleDataUsedToMintFungibleCollectionToken', 'WrongRefungiblePieces', 'RepartitionWhileNotOwningAllPieces', 'RefungibleDisallowsNesting', 'SettingPropertiesNotAllowed']
   },
   /**
-   * Lookup398: pallet_nonfungible::ItemData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup402: pallet_nonfungible::ItemData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   PalletNonfungibleItemData: {
     owner: 'PalletEvmAccountBasicCrossAccountIdRepr'
   },
   /**
-   * Lookup400: up_data_structs::PropertyScope
+   * Lookup404: up_data_structs::PropertyScope
    **/
   UpDataStructsPropertyScope: {
-    _enum: ['None', 'Rmrk', 'Eth']
+    _enum: ['None', 'Rmrk']
   },
   /**
-   * Lookup402: pallet_nonfungible::pallet::Error<T>
+   * Lookup406: pallet_nonfungible::pallet::Error<T>
    **/
   PalletNonfungibleError: {
     _enum: ['NotNonfungibleDataUsedToMintFungibleCollectionToken', 'NonfungibleItemsHaveNoAmount', 'CantBurnNftWithChildren']
   },
   /**
-   * Lookup403: pallet_structure::pallet::Error<T>
+   * Lookup407: pallet_structure::pallet::Error<T>
    **/
   PalletStructureError: {
     _enum: ['OuroborosDetected', 'DepthLimit', 'BreadthLimit', 'TokenNotFound']
   },
   /**
-   * Lookup404: pallet_rmrk_core::pallet::Error<T>
+   * Lookup408: pallet_rmrk_core::pallet::Error<T>
    **/
   PalletRmrkCoreError: {
     _enum: ['CorruptedCollectionType', 'RmrkPropertyKeyIsTooLong', 'RmrkPropertyValueIsTooLong', 'RmrkPropertyIsNotFound', 'UnableToDecodeRmrkData', 'CollectionNotEmpty', 'NoAvailableCollectionId', 'NoAvailableNftId', 'CollectionUnknown', 'NoPermission', 'NonTransferable', 'CollectionFullOrLocked', 'ResourceDoesntExist', 'CannotSendToDescendentOrSelf', 'CannotAcceptNonOwnedNft', 'CannotRejectNonOwnedNft', 'CannotRejectNonPendingNft', 'ResourceNotPending', 'NoAvailableResourceId']
   },
   /**
-   * Lookup406: pallet_rmrk_equip::pallet::Error<T>
+   * Lookup410: pallet_rmrk_equip::pallet::Error<T>
    **/
   PalletRmrkEquipError: {
     _enum: ['PermissionError', 'NoAvailableBaseId', 'NoAvailablePartId', 'BaseDoesntExist', 'NeedsDefaultThemeFirst', 'PartDoesntExist', 'NoEquippableOnFixedPart']
   },
   /**
-   * Lookup409: pallet_evm::pallet::Error<T>
+   * Lookup416: pallet_app_promotion::pallet::Error<T>
    **/
+  PalletAppPromotionError: {
+    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'SponsorNotSet', 'IncorrectLockedBalanceOperation']
+  },
+  /**
+   * Lookup419: pallet_evm::pallet::Error<T>
+   **/
   PalletEvmError: {
     _enum: ['BalanceLow', 'FeeOverflow', 'PaymentOverflow', 'WithdrawFailed', 'GasPriceTooLow', 'InvalidNonce']
   },
   /**
-   * Lookup412: fp_rpc::TransactionStatus
+   * Lookup422: fp_rpc::TransactionStatus
    **/
   FpRpcTransactionStatus: {
     transactionHash: 'H256',
@@ -3080,11 +3136,11 @@
     logsBloom: 'EthbloomBloom'
   },
   /**
-   * Lookup414: ethbloom::Bloom
+   * Lookup424: ethbloom::Bloom
    **/
   EthbloomBloom: '[u8;256]',
   /**
-   * Lookup416: ethereum::receipt::ReceiptV3
+   * Lookup426: ethereum::receipt::ReceiptV3
    **/
   EthereumReceiptReceiptV3: {
     _enum: {
@@ -3094,7 +3150,7 @@
     }
   },
   /**
-   * Lookup417: ethereum::receipt::EIP658ReceiptData
+   * Lookup427: ethereum::receipt::EIP658ReceiptData
    **/
   EthereumReceiptEip658ReceiptData: {
     statusCode: 'u8',
@@ -3103,7 +3159,7 @@
     logs: 'Vec<EthereumLog>'
   },
   /**
-   * Lookup418: ethereum::block::Block<ethereum::transaction::TransactionV2>
+   * Lookup428: ethereum::block::Block<ethereum::transaction::TransactionV2>
    **/
   EthereumBlock: {
     header: 'EthereumHeader',
@@ -3111,7 +3167,7 @@
     ommers: 'Vec<EthereumHeader>'
   },
   /**
-   * Lookup419: ethereum::header::Header
+   * Lookup429: ethereum::header::Header
    **/
   EthereumHeader: {
     parentHash: 'H256',
@@ -3131,41 +3187,51 @@
     nonce: 'EthereumTypesHashH64'
   },
   /**
-   * Lookup420: ethereum_types::hash::H64
+   * Lookup430: ethereum_types::hash::H64
    **/
   EthereumTypesHashH64: '[u8;8]',
   /**
-   * Lookup425: pallet_ethereum::pallet::Error<T>
+   * Lookup435: pallet_ethereum::pallet::Error<T>
    **/
   PalletEthereumError: {
     _enum: ['InvalidSignature', 'PreLogExists']
   },
   /**
-   * Lookup426: pallet_evm_coder_substrate::pallet::Error<T>
+   * Lookup436: pallet_evm_coder_substrate::pallet::Error<T>
    **/
   PalletEvmCoderSubstrateError: {
     _enum: ['OutOfGas', 'OutOfFund']
   },
   /**
-   * Lookup427: pallet_evm_contract_helpers::SponsoringModeT
+   * Lookup437: up_data_structs::SponsorshipState<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
+  UpDataStructsSponsorshipStateBasicCrossAccountIdRepr: {
+    _enum: {
+      Disabled: 'Null',
+      Unconfirmed: 'PalletEvmAccountBasicCrossAccountIdRepr',
+      Confirmed: 'PalletEvmAccountBasicCrossAccountIdRepr'
+    }
+  },
+  /**
+   * Lookup438: pallet_evm_contract_helpers::SponsoringModeT
+   **/
   PalletEvmContractHelpersSponsoringModeT: {
     _enum: ['Disabled', 'Allowlisted', 'Generous']
   },
   /**
-   * Lookup429: pallet_evm_contract_helpers::pallet::Error<T>
+   * Lookup440: pallet_evm_contract_helpers::pallet::Error<T>
    **/
   PalletEvmContractHelpersError: {
-    _enum: ['NoPermission']
+    _enum: ['NoPermission', 'NoPendingSponsor']
   },
   /**
-   * Lookup430: pallet_evm_migration::pallet::Error<T>
+   * Lookup441: pallet_evm_migration::pallet::Error<T>
    **/
   PalletEvmMigrationError: {
     _enum: ['AccountNotEmpty', 'AccountIsNotMigrating']
   },
   /**
-   * Lookup432: sp_runtime::MultiSignature
+   * Lookup443: sp_runtime::MultiSignature
    **/
   SpRuntimeMultiSignature: {
     _enum: {
@@ -3175,43 +3241,43 @@
     }
   },
   /**
-   * Lookup433: sp_core::ed25519::Signature
+   * Lookup444: sp_core::ed25519::Signature
    **/
   SpCoreEd25519Signature: '[u8;64]',
   /**
-   * Lookup435: sp_core::sr25519::Signature
+   * Lookup446: sp_core::sr25519::Signature
    **/
   SpCoreSr25519Signature: '[u8;64]',
   /**
-   * Lookup436: sp_core::ecdsa::Signature
+   * Lookup447: sp_core::ecdsa::Signature
    **/
   SpCoreEcdsaSignature: '[u8;65]',
   /**
-   * Lookup439: frame_system::extensions::check_spec_version::CheckSpecVersion<T>
+   * Lookup450: frame_system::extensions::check_spec_version::CheckSpecVersion<T>
    **/
   FrameSystemExtensionsCheckSpecVersion: 'Null',
   /**
-   * Lookup440: frame_system::extensions::check_genesis::CheckGenesis<T>
+   * Lookup451: frame_system::extensions::check_genesis::CheckGenesis<T>
    **/
   FrameSystemExtensionsCheckGenesis: 'Null',
   /**
-   * Lookup443: frame_system::extensions::check_nonce::CheckNonce<T>
+   * Lookup454: frame_system::extensions::check_nonce::CheckNonce<T>
    **/
   FrameSystemExtensionsCheckNonce: 'Compact<u32>',
   /**
-   * Lookup444: frame_system::extensions::check_weight::CheckWeight<T>
+   * Lookup455: frame_system::extensions::check_weight::CheckWeight<T>
    **/
   FrameSystemExtensionsCheckWeight: 'Null',
   /**
-   * Lookup445: pallet_template_transaction_payment::ChargeTransactionPayment<opal_runtime::Runtime>
+   * Lookup456: pallet_template_transaction_payment::ChargeTransactionPayment<opal_runtime::Runtime>
    **/
   PalletTemplateTransactionPaymentChargeTransactionPayment: 'Compact<u128>',
   /**
-   * Lookup446: opal_runtime::Runtime
+   * Lookup457: opal_runtime::Runtime
    **/
   OpalRuntimeRuntime: 'Null',
   /**
-   * Lookup447: pallet_ethereum::FakeTransactionFinalizer<opal_runtime::Runtime>
+   * Lookup458: pallet_ethereum::FakeTransactionFinalizer<opal_runtime::Runtime>
    **/
   PalletEthereumFakeTransactionFinalizer: 'Null'
 };
modifiedtests/src/interfaces/registry.tsdiffbeforeafterboth
--- a/tests/src/interfaces/registry.ts
+++ b/tests/src/interfaces/registry.ts
@@ -5,7 +5,7 @@
 // this is required to allow for ambient/previous definitions
 import '@polkadot/types/types/registry';
 
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationCall, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUniqueSchedulerCall, PalletUniqueSchedulerError, PalletUniqueSchedulerEvent, PalletUniqueSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';
+import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmOrigin, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchRawOrigin, FrameSupportPalletId, FrameSupportScheduleLookupError, FrameSupportScheduleMaybeHashed, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeOriginCaller, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletAppPromotionCall, PalletAppPromotionError, PalletAppPromotionEvent, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationCall, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEthereumRawOrigin, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersEvent, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletUniqueSchedulerCall, PalletUniqueSchedulerError, PalletUniqueSchedulerEvent, PalletUniqueSchedulerScheduledV3, PalletXcmCall, PalletXcmError, PalletXcmEvent, PalletXcmOrigin, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpCoreVoid, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipStateAccountId32, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';
 
 declare module '@polkadot/types/types/registry' {
   interface InterfaceTypes {
@@ -83,6 +83,9 @@
     OrmlVestingModuleError: OrmlVestingModuleError;
     OrmlVestingModuleEvent: OrmlVestingModuleEvent;
     OrmlVestingVestingSchedule: OrmlVestingVestingSchedule;
+    PalletAppPromotionCall: PalletAppPromotionCall;
+    PalletAppPromotionError: PalletAppPromotionError;
+    PalletAppPromotionEvent: PalletAppPromotionEvent;
     PalletBalancesAccountData: PalletBalancesAccountData;
     PalletBalancesBalanceLock: PalletBalancesBalanceLock;
     PalletBalancesCall: PalletBalancesCall;
@@ -103,6 +106,7 @@
     PalletEvmCall: PalletEvmCall;
     PalletEvmCoderSubstrateError: PalletEvmCoderSubstrateError;
     PalletEvmContractHelpersError: PalletEvmContractHelpersError;
+    PalletEvmContractHelpersEvent: PalletEvmContractHelpersEvent;
     PalletEvmContractHelpersSponsoringModeT: PalletEvmContractHelpersSponsoringModeT;
     PalletEvmError: PalletEvmError;
     PalletEvmEvent: PalletEvmEvent;
@@ -213,7 +217,8 @@
     UpDataStructsPropertyScope: UpDataStructsPropertyScope;
     UpDataStructsRpcCollection: UpDataStructsRpcCollection;
     UpDataStructsSponsoringRateLimit: UpDataStructsSponsoringRateLimit;
-    UpDataStructsSponsorshipState: UpDataStructsSponsorshipState;
+    UpDataStructsSponsorshipStateAccountId32: UpDataStructsSponsorshipStateAccountId32;
+    UpDataStructsSponsorshipStateBasicCrossAccountIdRepr: UpDataStructsSponsorshipStateBasicCrossAccountIdRepr;
     UpDataStructsTokenChild: UpDataStructsTokenChild;
     UpDataStructsTokenData: UpDataStructsTokenData;
     XcmDoubleEncoded: XcmDoubleEncoded;
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1198,7 +1198,20 @@
     readonly type: 'BaseCreated' | 'EquippablesUpdated';
   }
 
-  /** @name PalletEvmEvent (103) */
+  /** @name PalletAppPromotionEvent (103) */
+  interface PalletAppPromotionEvent extends Enum {
+    readonly isStakingRecalculation: boolean;
+    readonly asStakingRecalculation: ITuple<[AccountId32, u128, u128]>;
+    readonly isStake: boolean;
+    readonly asStake: ITuple<[AccountId32, u128]>;
+    readonly isUnstake: boolean;
+    readonly asUnstake: ITuple<[AccountId32, u128]>;
+    readonly isSetAdmin: boolean;
+    readonly asSetAdmin: AccountId32;
+    readonly type: 'StakingRecalculation' | 'Stake' | 'Unstake' | 'SetAdmin';
+  }
+
+  /** @name PalletEvmEvent (104) */
   interface PalletEvmEvent extends Enum {
     readonly isLog: boolean;
     readonly asLog: EthereumLog;
@@ -1217,21 +1230,21 @@
     readonly type: 'Log' | 'Created' | 'CreatedFailed' | 'Executed' | 'ExecutedFailed' | 'BalanceDeposit' | 'BalanceWithdraw';
   }
 
-  /** @name EthereumLog (104) */
+  /** @name EthereumLog (105) */
   interface EthereumLog extends Struct {
     readonly address: H160;
     readonly topics: Vec<H256>;
     readonly data: Bytes;
   }
 
-  /** @name PalletEthereumEvent (108) */
+  /** @name PalletEthereumEvent (109) */
   interface PalletEthereumEvent extends Enum {
     readonly isExecuted: boolean;
     readonly asExecuted: ITuple<[H160, H160, H256, EvmCoreErrorExitReason]>;
     readonly type: 'Executed';
   }
 
-  /** @name EvmCoreErrorExitReason (109) */
+  /** @name EvmCoreErrorExitReason (110) */
   interface EvmCoreErrorExitReason extends Enum {
     readonly isSucceed: boolean;
     readonly asSucceed: EvmCoreErrorExitSucceed;
@@ -1244,7 +1257,7 @@
     readonly type: 'Succeed' | 'Error' | 'Revert' | 'Fatal';
   }
 
-  /** @name EvmCoreErrorExitSucceed (110) */
+  /** @name EvmCoreErrorExitSucceed (111) */
   interface EvmCoreErrorExitSucceed extends Enum {
     readonly isStopped: boolean;
     readonly isReturned: boolean;
@@ -1252,7 +1265,7 @@
     readonly type: 'Stopped' | 'Returned' | 'Suicided';
   }
 
-  /** @name EvmCoreErrorExitError (111) */
+  /** @name EvmCoreErrorExitError (112) */
   interface EvmCoreErrorExitError extends Enum {
     readonly isStackUnderflow: boolean;
     readonly isStackOverflow: boolean;
@@ -1273,13 +1286,13 @@
     readonly type: 'StackUnderflow' | 'StackOverflow' | 'InvalidJump' | 'InvalidRange' | 'DesignatedInvalid' | 'CallTooDeep' | 'CreateCollision' | 'CreateContractLimit' | 'OutOfOffset' | 'OutOfGas' | 'OutOfFund' | 'PcUnderflow' | 'CreateEmpty' | 'Other' | 'InvalidCode';
   }
 
-  /** @name EvmCoreErrorExitRevert (114) */
+  /** @name EvmCoreErrorExitRevert (115) */
   interface EvmCoreErrorExitRevert extends Enum {
     readonly isReverted: boolean;
     readonly type: 'Reverted';
   }
 
-  /** @name EvmCoreErrorExitFatal (115) */
+  /** @name EvmCoreErrorExitFatal (116) */
   interface EvmCoreErrorExitFatal extends Enum {
     readonly isNotSupported: boolean;
     readonly isUnhandledInterrupt: boolean;
@@ -1290,7 +1303,18 @@
     readonly type: 'NotSupported' | 'UnhandledInterrupt' | 'CallErrorAsFatal' | 'Other';
   }
 
-  /** @name FrameSystemPhase (116) */
+  /** @name PalletEvmContractHelpersEvent (117) */
+  interface PalletEvmContractHelpersEvent extends Enum {
+    readonly isContractSponsorSet: boolean;
+    readonly asContractSponsorSet: ITuple<[H160, AccountId32]>;
+    readonly isContractSponsorshipConfirmed: boolean;
+    readonly asContractSponsorshipConfirmed: ITuple<[H160, AccountId32]>;
+    readonly isContractSponsorRemoved: boolean;
+    readonly asContractSponsorRemoved: H160;
+    readonly type: 'ContractSponsorSet' | 'ContractSponsorshipConfirmed' | 'ContractSponsorRemoved';
+  }
+
+  /** @name FrameSystemPhase (118) */
   interface FrameSystemPhase extends Enum {
     readonly isApplyExtrinsic: boolean;
     readonly asApplyExtrinsic: u32;
@@ -1299,13 +1323,13 @@
     readonly type: 'ApplyExtrinsic' | 'Finalization' | 'Initialization';
   }
 
-  /** @name FrameSystemLastRuntimeUpgradeInfo (118) */
+  /** @name FrameSystemLastRuntimeUpgradeInfo (120) */
   interface FrameSystemLastRuntimeUpgradeInfo extends Struct {
     readonly specVersion: Compact<u32>;
     readonly specName: Text;
   }
 
-  /** @name FrameSystemCall (119) */
+  /** @name FrameSystemCall (121) */
   interface FrameSystemCall extends Enum {
     readonly isFillBlock: boolean;
     readonly asFillBlock: {
@@ -1347,21 +1371,21 @@
     readonly type: 'FillBlock' | 'Remark' | 'SetHeapPages' | 'SetCode' | 'SetCodeWithoutChecks' | 'SetStorage' | 'KillStorage' | 'KillPrefix' | 'RemarkWithEvent';
   }
 
-  /** @name FrameSystemLimitsBlockWeights (124) */
+  /** @name FrameSystemLimitsBlockWeights (126) */
   interface FrameSystemLimitsBlockWeights extends Struct {
     readonly baseBlock: u64;
     readonly maxBlock: u64;
     readonly perClass: FrameSupportWeightsPerDispatchClassWeightsPerClass;
   }
 
-  /** @name FrameSupportWeightsPerDispatchClassWeightsPerClass (125) */
+  /** @name FrameSupportWeightsPerDispatchClassWeightsPerClass (127) */
   interface FrameSupportWeightsPerDispatchClassWeightsPerClass extends Struct {
     readonly normal: FrameSystemLimitsWeightsPerClass;
     readonly operational: FrameSystemLimitsWeightsPerClass;
     readonly mandatory: FrameSystemLimitsWeightsPerClass;
   }
 
-  /** @name FrameSystemLimitsWeightsPerClass (126) */
+  /** @name FrameSystemLimitsWeightsPerClass (128) */
   interface FrameSystemLimitsWeightsPerClass extends Struct {
     readonly baseExtrinsic: u64;
     readonly maxExtrinsic: Option<u64>;
@@ -1369,25 +1393,25 @@
     readonly reserved: Option<u64>;
   }
 
-  /** @name FrameSystemLimitsBlockLength (128) */
+  /** @name FrameSystemLimitsBlockLength (130) */
   interface FrameSystemLimitsBlockLength extends Struct {
     readonly max: FrameSupportWeightsPerDispatchClassU32;
   }
 
-  /** @name FrameSupportWeightsPerDispatchClassU32 (129) */
+  /** @name FrameSupportWeightsPerDispatchClassU32 (131) */
   interface FrameSupportWeightsPerDispatchClassU32 extends Struct {
     readonly normal: u32;
     readonly operational: u32;
     readonly mandatory: u32;
   }
 
-  /** @name FrameSupportWeightsRuntimeDbWeight (130) */
+  /** @name FrameSupportWeightsRuntimeDbWeight (132) */
   interface FrameSupportWeightsRuntimeDbWeight extends Struct {
     readonly read: u64;
     readonly write: u64;
   }
 
-  /** @name SpVersionRuntimeVersion (131) */
+  /** @name SpVersionRuntimeVersion (133) */
   interface SpVersionRuntimeVersion extends Struct {
     readonly specName: Text;
     readonly implName: Text;
@@ -1399,7 +1423,7 @@
     readonly stateVersion: u8;
   }
 
-  /** @name FrameSystemError (136) */
+  /** @name FrameSystemError (138) */
   interface FrameSystemError extends Enum {
     readonly isInvalidSpecName: boolean;
     readonly isSpecVersionNeedsToIncrease: boolean;
@@ -1410,7 +1434,7 @@
     readonly type: 'InvalidSpecName' | 'SpecVersionNeedsToIncrease' | 'FailedToExtractRuntimeVersion' | 'NonDefaultComposite' | 'NonZeroRefCount' | 'CallFiltered';
   }
 
-  /** @name PolkadotPrimitivesV2PersistedValidationData (137) */
+  /** @name PolkadotPrimitivesV2PersistedValidationData (139) */
   interface PolkadotPrimitivesV2PersistedValidationData extends Struct {
     readonly parentHead: Bytes;
     readonly relayParentNumber: u32;
@@ -1418,18 +1442,18 @@
     readonly maxPovSize: u32;
   }
 
-  /** @name PolkadotPrimitivesV2UpgradeRestriction (140) */
+  /** @name PolkadotPrimitivesV2UpgradeRestriction (142) */
   interface PolkadotPrimitivesV2UpgradeRestriction extends Enum {
     readonly isPresent: boolean;
     readonly type: 'Present';
   }
 
-  /** @name SpTrieStorageProof (141) */
+  /** @name SpTrieStorageProof (143) */
   interface SpTrieStorageProof extends Struct {
     readonly trieNodes: BTreeSet<Bytes>;
   }
 
-  /** @name CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot (143) */
+  /** @name CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot (145) */
   interface CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot extends Struct {
     readonly dmqMqcHead: H256;
     readonly relayDispatchQueueSize: ITuple<[u32, u32]>;
@@ -1437,7 +1461,7 @@
     readonly egressChannels: Vec<ITuple<[u32, PolkadotPrimitivesV2AbridgedHrmpChannel]>>;
   }
 
-  /** @name PolkadotPrimitivesV2AbridgedHrmpChannel (146) */
+  /** @name PolkadotPrimitivesV2AbridgedHrmpChannel (148) */
   interface PolkadotPrimitivesV2AbridgedHrmpChannel extends Struct {
     readonly maxCapacity: u32;
     readonly maxTotalSize: u32;
@@ -1447,7 +1471,7 @@
     readonly mqcHead: Option<H256>;
   }
 
-  /** @name PolkadotPrimitivesV2AbridgedHostConfiguration (147) */
+  /** @name PolkadotPrimitivesV2AbridgedHostConfiguration (149) */
   interface PolkadotPrimitivesV2AbridgedHostConfiguration extends Struct {
     readonly maxCodeSize: u32;
     readonly maxHeadDataSize: u32;
@@ -1460,13 +1484,13 @@
     readonly validationUpgradeDelay: u32;
   }
 
-  /** @name PolkadotCorePrimitivesOutboundHrmpMessage (153) */
+  /** @name PolkadotCorePrimitivesOutboundHrmpMessage (155) */
   interface PolkadotCorePrimitivesOutboundHrmpMessage extends Struct {
     readonly recipient: u32;
     readonly data: Bytes;
   }
 
-  /** @name CumulusPalletParachainSystemCall (154) */
+  /** @name CumulusPalletParachainSystemCall (156) */
   interface CumulusPalletParachainSystemCall extends Enum {
     readonly isSetValidationData: boolean;
     readonly asSetValidationData: {
@@ -1487,7 +1511,7 @@
     readonly type: 'SetValidationData' | 'SudoSendUpwardMessage' | 'AuthorizeUpgrade' | 'EnactAuthorizedUpgrade';
   }
 
-  /** @name CumulusPrimitivesParachainInherentParachainInherentData (155) */
+  /** @name CumulusPrimitivesParachainInherentParachainInherentData (157) */
   interface CumulusPrimitivesParachainInherentParachainInherentData extends Struct {
     readonly validationData: PolkadotPrimitivesV2PersistedValidationData;
     readonly relayChainState: SpTrieStorageProof;
@@ -1495,19 +1519,19 @@
     readonly horizontalMessages: BTreeMap<u32, Vec<PolkadotCorePrimitivesInboundHrmpMessage>>;
   }
 
-  /** @name PolkadotCorePrimitivesInboundDownwardMessage (157) */
+  /** @name PolkadotCorePrimitivesInboundDownwardMessage (159) */
   interface PolkadotCorePrimitivesInboundDownwardMessage extends Struct {
     readonly sentAt: u32;
     readonly msg: Bytes;
   }
 
-  /** @name PolkadotCorePrimitivesInboundHrmpMessage (160) */
+  /** @name PolkadotCorePrimitivesInboundHrmpMessage (162) */
   interface PolkadotCorePrimitivesInboundHrmpMessage extends Struct {
     readonly sentAt: u32;
     readonly data: Bytes;
   }
 
-  /** @name CumulusPalletParachainSystemError (163) */
+  /** @name CumulusPalletParachainSystemError (165) */
   interface CumulusPalletParachainSystemError extends Enum {
     readonly isOverlappingUpgrades: boolean;
     readonly isProhibitedByPolkadot: boolean;
@@ -1520,14 +1544,14 @@
     readonly type: 'OverlappingUpgrades' | 'ProhibitedByPolkadot' | 'TooBig' | 'ValidationDataNotAvailable' | 'HostConfigurationNotAvailable' | 'NotScheduled' | 'NothingAuthorized' | 'Unauthorized';
   }
 
-  /** @name PalletBalancesBalanceLock (165) */
+  /** @name PalletBalancesBalanceLock (167) */
   interface PalletBalancesBalanceLock extends Struct {
     readonly id: U8aFixed;
     readonly amount: u128;
     readonly reasons: PalletBalancesReasons;
   }
 
-  /** @name PalletBalancesReasons (166) */
+  /** @name PalletBalancesReasons (168) */
   interface PalletBalancesReasons extends Enum {
     readonly isFee: boolean;
     readonly isMisc: boolean;
@@ -1535,20 +1559,20 @@
     readonly type: 'Fee' | 'Misc' | 'All';
   }
 
-  /** @name PalletBalancesReserveData (169) */
+  /** @name PalletBalancesReserveData (171) */
   interface PalletBalancesReserveData extends Struct {
     readonly id: U8aFixed;
     readonly amount: u128;
   }
 
-  /** @name PalletBalancesReleases (171) */
+  /** @name PalletBalancesReleases (173) */
   interface PalletBalancesReleases extends Enum {
     readonly isV100: boolean;
     readonly isV200: boolean;
     readonly type: 'V100' | 'V200';
   }
 
-  /** @name PalletBalancesCall (172) */
+  /** @name PalletBalancesCall (174) */
   interface PalletBalancesCall extends Enum {
     readonly isTransfer: boolean;
     readonly asTransfer: {
@@ -1585,7 +1609,7 @@
     readonly type: 'Transfer' | 'SetBalance' | 'ForceTransfer' | 'TransferKeepAlive' | 'TransferAll' | 'ForceUnreserve';
   }
 
-  /** @name PalletBalancesError (175) */
+  /** @name PalletBalancesError (177) */
   interface PalletBalancesError extends Enum {
     readonly isVestingBalance: boolean;
     readonly isLiquidityRestrictions: boolean;
@@ -1598,7 +1622,7 @@
     readonly type: 'VestingBalance' | 'LiquidityRestrictions' | 'InsufficientBalance' | 'ExistentialDeposit' | 'KeepAlive' | 'ExistingVestingSchedule' | 'DeadAccount' | 'TooManyReserves';
   }
 
-  /** @name PalletTimestampCall (177) */
+  /** @name PalletTimestampCall (179) */
   interface PalletTimestampCall extends Enum {
     readonly isSet: boolean;
     readonly asSet: {
@@ -1607,14 +1631,14 @@
     readonly type: 'Set';
   }
 
-  /** @name PalletTransactionPaymentReleases (179) */
+  /** @name PalletTransactionPaymentReleases (181) */
   interface PalletTransactionPaymentReleases extends Enum {
     readonly isV1Ancient: boolean;
     readonly isV2: boolean;
     readonly type: 'V1Ancient' | 'V2';
   }
 
-  /** @name PalletTreasuryProposal (180) */
+  /** @name PalletTreasuryProposal (182) */
   interface PalletTreasuryProposal extends Struct {
     readonly proposer: AccountId32;
     readonly value: u128;
@@ -1622,7 +1646,7 @@
     readonly bond: u128;
   }
 
-  /** @name PalletTreasuryCall (183) */
+  /** @name PalletTreasuryCall (185) */
   interface PalletTreasuryCall extends Enum {
     readonly isProposeSpend: boolean;
     readonly asProposeSpend: {
@@ -1649,10 +1673,10 @@
     readonly type: 'ProposeSpend' | 'RejectProposal' | 'ApproveProposal' | 'Spend' | 'RemoveApproval';
   }
 
-  /** @name FrameSupportPalletId (186) */
+  /** @name FrameSupportPalletId (188) */
   interface FrameSupportPalletId extends U8aFixed {}
 
-  /** @name PalletTreasuryError (187) */
+  /** @name PalletTreasuryError (189) */
   interface PalletTreasuryError extends Enum {
     readonly isInsufficientProposersBalance: boolean;
     readonly isInvalidIndex: boolean;
@@ -1662,7 +1686,7 @@
     readonly type: 'InsufficientProposersBalance' | 'InvalidIndex' | 'TooManyApprovals' | 'InsufficientPermission' | 'ProposalNotApproved';
   }
 
-  /** @name PalletSudoCall (188) */
+  /** @name PalletSudoCall (190) */
   interface PalletSudoCall extends Enum {
     readonly isSudo: boolean;
     readonly asSudo: {
@@ -1685,7 +1709,7 @@
     readonly type: 'Sudo' | 'SudoUncheckedWeight' | 'SetKey' | 'SudoAs';
   }
 
-  /** @name OrmlVestingModuleCall (190) */
+  /** @name OrmlVestingModuleCall (192) */
   interface OrmlVestingModuleCall extends Enum {
     readonly isClaim: boolean;
     readonly isVestedTransfer: boolean;
@@ -1705,7 +1729,7 @@
     readonly type: 'Claim' | 'VestedTransfer' | 'UpdateVestingSchedules' | 'ClaimFor';
   }
 
-  /** @name CumulusPalletXcmpQueueCall (192) */
+  /** @name CumulusPalletXcmpQueueCall (194) */
   interface CumulusPalletXcmpQueueCall extends Enum {
     readonly isServiceOverweight: boolean;
     readonly asServiceOverweight: {
@@ -1741,7 +1765,7 @@
     readonly type: 'ServiceOverweight' | 'SuspendXcmExecution' | 'ResumeXcmExecution' | 'UpdateSuspendThreshold' | 'UpdateDropThreshold' | 'UpdateResumeThreshold' | 'UpdateThresholdWeight' | 'UpdateWeightRestrictDecay' | 'UpdateXcmpMaxIndividualWeight';
   }
 
-  /** @name PalletXcmCall (193) */
+  /** @name PalletXcmCall (195) */
   interface PalletXcmCall extends Enum {
     readonly isSend: boolean;
     readonly asSend: {
@@ -1803,7 +1827,7 @@
     readonly type: 'Send' | 'TeleportAssets' | 'ReserveTransferAssets' | 'Execute' | 'ForceXcmVersion' | 'ForceDefaultXcmVersion' | 'ForceSubscribeVersionNotify' | 'ForceUnsubscribeVersionNotify' | 'LimitedReserveTransferAssets' | 'LimitedTeleportAssets';
   }
 
-  /** @name XcmVersionedXcm (194) */
+  /** @name XcmVersionedXcm (196) */
   interface XcmVersionedXcm extends Enum {
     readonly isV0: boolean;
     readonly asV0: XcmV0Xcm;
@@ -1814,7 +1838,7 @@
     readonly type: 'V0' | 'V1' | 'V2';
   }
 
-  /** @name XcmV0Xcm (195) */
+  /** @name XcmV0Xcm (197) */
   interface XcmV0Xcm extends Enum {
     readonly isWithdrawAsset: boolean;
     readonly asWithdrawAsset: {
@@ -1877,7 +1901,7 @@
     readonly type: 'WithdrawAsset' | 'ReserveAssetDeposit' | 'TeleportAsset' | 'QueryResponse' | 'TransferAsset' | 'TransferReserveAsset' | 'Transact' | 'HrmpNewChannelOpenRequest' | 'HrmpChannelAccepted' | 'HrmpChannelClosing' | 'RelayedFrom';
   }
 
-  /** @name XcmV0Order (197) */
+  /** @name XcmV0Order (199) */
   interface XcmV0Order extends Enum {
     readonly isNull: boolean;
     readonly isDepositAsset: boolean;
@@ -1925,14 +1949,14 @@
     readonly type: 'Null' | 'DepositAsset' | 'DepositReserveAsset' | 'ExchangeAsset' | 'InitiateReserveWithdraw' | 'InitiateTeleport' | 'QueryHolding' | 'BuyExecution';
   }
 
-  /** @name XcmV0Response (199) */
+  /** @name XcmV0Response (201) */
   interface XcmV0Response extends Enum {
     readonly isAssets: boolean;
     readonly asAssets: Vec<XcmV0MultiAsset>;
     readonly type: 'Assets';
   }
 
-  /** @name XcmV1Xcm (200) */
+  /** @name XcmV1Xcm (202) */
   interface XcmV1Xcm extends Enum {
     readonly isWithdrawAsset: boolean;
     readonly asWithdrawAsset: {
@@ -2001,7 +2025,7 @@
     readonly type: 'WithdrawAsset' | 'ReserveAssetDeposited' | 'ReceiveTeleportedAsset' | 'QueryResponse' | 'TransferAsset' | 'TransferReserveAsset' | 'Transact' | 'HrmpNewChannelOpenRequest' | 'HrmpChannelAccepted' | 'HrmpChannelClosing' | 'RelayedFrom' | 'SubscribeVersion' | 'UnsubscribeVersion';
   }
 
-  /** @name XcmV1Order (202) */
+  /** @name XcmV1Order (204) */
   interface XcmV1Order extends Enum {
     readonly isNoop: boolean;
     readonly isDepositAsset: boolean;
@@ -2051,7 +2075,7 @@
     readonly type: 'Noop' | 'DepositAsset' | 'DepositReserveAsset' | 'ExchangeAsset' | 'InitiateReserveWithdraw' | 'InitiateTeleport' | 'QueryHolding' | 'BuyExecution';
   }
 
-  /** @name XcmV1Response (204) */
+  /** @name XcmV1Response (206) */
   interface XcmV1Response extends Enum {
     readonly isAssets: boolean;
     readonly asAssets: XcmV1MultiassetMultiAssets;
@@ -2060,10 +2084,10 @@
     readonly type: 'Assets' | 'Version';
   }
 
-  /** @name CumulusPalletXcmCall (218) */
+  /** @name CumulusPalletXcmCall (220) */
   type CumulusPalletXcmCall = Null;
 
-  /** @name CumulusPalletDmpQueueCall (219) */
+  /** @name CumulusPalletDmpQueueCall (221) */
   interface CumulusPalletDmpQueueCall extends Enum {
     readonly isServiceOverweight: boolean;
     readonly asServiceOverweight: {
@@ -2073,7 +2097,7 @@
     readonly type: 'ServiceOverweight';
   }
 
-  /** @name PalletInflationCall (220) */
+  /** @name PalletInflationCall (222) */
   interface PalletInflationCall extends Enum {
     readonly isStartInflation: boolean;
     readonly asStartInflation: {
@@ -2082,7 +2106,7 @@
     readonly type: 'StartInflation';
   }
 
-  /** @name PalletUniqueCall (221) */
+  /** @name PalletUniqueCall (223) */
   interface PalletUniqueCall extends Enum {
     readonly isCreateCollection: boolean;
     readonly asCreateCollection: {
@@ -2240,7 +2264,7 @@
     readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition';
   }
 
-  /** @name UpDataStructsCollectionMode (226) */
+  /** @name UpDataStructsCollectionMode (228) */
   interface UpDataStructsCollectionMode extends Enum {
     readonly isNft: boolean;
     readonly isFungible: boolean;
@@ -2249,7 +2273,7 @@
     readonly type: 'Nft' | 'Fungible' | 'ReFungible';
   }
 
-  /** @name UpDataStructsCreateCollectionData (227) */
+  /** @name UpDataStructsCreateCollectionData (229) */
   interface UpDataStructsCreateCollectionData extends Struct {
     readonly mode: UpDataStructsCollectionMode;
     readonly access: Option<UpDataStructsAccessMode>;
@@ -2263,14 +2287,14 @@
     readonly properties: Vec<UpDataStructsProperty>;
   }
 
-  /** @name UpDataStructsAccessMode (229) */
+  /** @name UpDataStructsAccessMode (231) */
   interface UpDataStructsAccessMode extends Enum {
     readonly isNormal: boolean;
     readonly isAllowList: boolean;
     readonly type: 'Normal' | 'AllowList';
   }
 
-  /** @name UpDataStructsCollectionLimits (231) */
+  /** @name UpDataStructsCollectionLimits (233) */
   interface UpDataStructsCollectionLimits extends Struct {
     readonly accountTokenOwnershipLimit: Option<u32>;
     readonly sponsoredDataSize: Option<u32>;
@@ -2283,7 +2307,7 @@
     readonly transfersEnabled: Option<bool>;
   }
 
-  /** @name UpDataStructsSponsoringRateLimit (233) */
+  /** @name UpDataStructsSponsoringRateLimit (235) */
   interface UpDataStructsSponsoringRateLimit extends Enum {
     readonly isSponsoringDisabled: boolean;
     readonly isBlocks: boolean;
@@ -2291,43 +2315,43 @@
     readonly type: 'SponsoringDisabled' | 'Blocks';
   }
 
-  /** @name UpDataStructsCollectionPermissions (236) */
+  /** @name UpDataStructsCollectionPermissions (238) */
   interface UpDataStructsCollectionPermissions extends Struct {
     readonly access: Option<UpDataStructsAccessMode>;
     readonly mintMode: Option<bool>;
     readonly nesting: Option<UpDataStructsNestingPermissions>;
   }
 
-  /** @name UpDataStructsNestingPermissions (238) */
+  /** @name UpDataStructsNestingPermissions (240) */
   interface UpDataStructsNestingPermissions extends Struct {
     readonly tokenOwner: bool;
     readonly collectionAdmin: bool;
     readonly restricted: Option<UpDataStructsOwnerRestrictedSet>;
   }
 
-  /** @name UpDataStructsOwnerRestrictedSet (240) */
+  /** @name UpDataStructsOwnerRestrictedSet (242) */
   interface UpDataStructsOwnerRestrictedSet extends BTreeSet<u32> {}
 
-  /** @name UpDataStructsPropertyKeyPermission (245) */
+  /** @name UpDataStructsPropertyKeyPermission (247) */
   interface UpDataStructsPropertyKeyPermission extends Struct {
     readonly key: Bytes;
     readonly permission: UpDataStructsPropertyPermission;
   }
 
-  /** @name UpDataStructsPropertyPermission (246) */
+  /** @name UpDataStructsPropertyPermission (248) */
   interface UpDataStructsPropertyPermission extends Struct {
     readonly mutable: bool;
     readonly collectionAdmin: bool;
     readonly tokenOwner: bool;
   }
 
-  /** @name UpDataStructsProperty (249) */
+  /** @name UpDataStructsProperty (251) */
   interface UpDataStructsProperty extends Struct {
     readonly key: Bytes;
     readonly value: Bytes;
   }
 
-  /** @name UpDataStructsCreateItemData (252) */
+  /** @name UpDataStructsCreateItemData (254) */
   interface UpDataStructsCreateItemData extends Enum {
     readonly isNft: boolean;
     readonly asNft: UpDataStructsCreateNftData;
@@ -2338,23 +2362,23 @@
     readonly type: 'Nft' | 'Fungible' | 'ReFungible';
   }
 
-  /** @name UpDataStructsCreateNftData (253) */
+  /** @name UpDataStructsCreateNftData (255) */
   interface UpDataStructsCreateNftData extends Struct {
     readonly properties: Vec<UpDataStructsProperty>;
   }
 
-  /** @name UpDataStructsCreateFungibleData (254) */
+  /** @name UpDataStructsCreateFungibleData (256) */
   interface UpDataStructsCreateFungibleData extends Struct {
     readonly value: u128;
   }
 
-  /** @name UpDataStructsCreateReFungibleData (255) */
+  /** @name UpDataStructsCreateReFungibleData (257) */
   interface UpDataStructsCreateReFungibleData extends Struct {
     readonly pieces: u128;
     readonly properties: Vec<UpDataStructsProperty>;
   }
 
-  /** @name UpDataStructsCreateItemExData (258) */
+  /** @name UpDataStructsCreateItemExData (260) */
   interface UpDataStructsCreateItemExData extends Enum {
     readonly isNft: boolean;
     readonly asNft: Vec<UpDataStructsCreateNftExData>;
@@ -2367,26 +2391,26 @@
     readonly type: 'Nft' | 'Fungible' | 'RefungibleMultipleItems' | 'RefungibleMultipleOwners';
   }
 
-  /** @name UpDataStructsCreateNftExData (260) */
+  /** @name UpDataStructsCreateNftExData (262) */
   interface UpDataStructsCreateNftExData extends Struct {
     readonly properties: Vec<UpDataStructsProperty>;
     readonly owner: PalletEvmAccountBasicCrossAccountIdRepr;
   }
 
-  /** @name UpDataStructsCreateRefungibleExSingleOwner (267) */
+  /** @name UpDataStructsCreateRefungibleExSingleOwner (269) */
   interface UpDataStructsCreateRefungibleExSingleOwner extends Struct {
     readonly user: PalletEvmAccountBasicCrossAccountIdRepr;
     readonly pieces: u128;
     readonly properties: Vec<UpDataStructsProperty>;
   }
 
-  /** @name UpDataStructsCreateRefungibleExMultipleOwners (269) */
+  /** @name UpDataStructsCreateRefungibleExMultipleOwners (271) */
   interface UpDataStructsCreateRefungibleExMultipleOwners extends Struct {
     readonly users: BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>;
     readonly properties: Vec<UpDataStructsProperty>;
   }
 
-  /** @name PalletUniqueSchedulerCall (270) */
+  /** @name PalletUniqueSchedulerCall (272) */
   interface PalletUniqueSchedulerCall extends Enum {
     readonly isScheduleNamed: boolean;
     readonly asScheduleNamed: {
@@ -2411,7 +2435,7 @@
     readonly type: 'ScheduleNamed' | 'CancelNamed' | 'ScheduleNamedAfter';
   }
 
-  /** @name FrameSupportScheduleMaybeHashed (272) */
+  /** @name FrameSupportScheduleMaybeHashed (274) */
   interface FrameSupportScheduleMaybeHashed extends Enum {
     readonly isValue: boolean;
     readonly asValue: Call;
@@ -2420,7 +2444,7 @@
     readonly type: 'Value' | 'Hash';
   }
 
-  /** @name PalletConfigurationCall (273) */
+  /** @name PalletConfigurationCall (275) */
   interface PalletConfigurationCall extends Enum {
     readonly isSetWeightToFeeCoefficientOverride: boolean;
     readonly asSetWeightToFeeCoefficientOverride: {
@@ -2433,13 +2457,13 @@
     readonly type: 'SetWeightToFeeCoefficientOverride' | 'SetMinGasPriceOverride';
   }
 
-  /** @name PalletTemplateTransactionPaymentCall (274) */
+  /** @name PalletTemplateTransactionPaymentCall (276) */
   type PalletTemplateTransactionPaymentCall = Null;
 
-  /** @name PalletStructureCall (275) */
+  /** @name PalletStructureCall (277) */
   type PalletStructureCall = Null;
 
-  /** @name PalletRmrkCoreCall (276) */
+  /** @name PalletRmrkCoreCall (278) */
   interface PalletRmrkCoreCall extends Enum {
     readonly isCreateCollection: boolean;
     readonly asCreateCollection: {
@@ -2545,7 +2569,7 @@
     readonly type: 'CreateCollection' | 'DestroyCollection' | 'ChangeCollectionIssuer' | 'LockCollection' | 'MintNft' | 'BurnNft' | 'Send' | 'AcceptNft' | 'RejectNft' | 'AcceptResource' | 'AcceptResourceRemoval' | 'SetProperty' | 'SetPriority' | 'AddBasicResource' | 'AddComposableResource' | 'AddSlotResource' | 'RemoveResource';
   }
 
-  /** @name RmrkTraitsResourceResourceTypes (282) */
+  /** @name RmrkTraitsResourceResourceTypes (284) */
   interface RmrkTraitsResourceResourceTypes extends Enum {
     readonly isBasic: boolean;
     readonly asBasic: RmrkTraitsResourceBasicResource;
@@ -2556,7 +2580,7 @@
     readonly type: 'Basic' | 'Composable' | 'Slot';
   }
 
-  /** @name RmrkTraitsResourceBasicResource (284) */
+  /** @name RmrkTraitsResourceBasicResource (286) */
   interface RmrkTraitsResourceBasicResource extends Struct {
     readonly src: Option<Bytes>;
     readonly metadata: Option<Bytes>;
@@ -2564,7 +2588,7 @@
     readonly thumb: Option<Bytes>;
   }
 
-  /** @name RmrkTraitsResourceComposableResource (286) */
+  /** @name RmrkTraitsResourceComposableResource (288) */
   interface RmrkTraitsResourceComposableResource extends Struct {
     readonly parts: Vec<u32>;
     readonly base: u32;
@@ -2574,7 +2598,7 @@
     readonly thumb: Option<Bytes>;
   }
 
-  /** @name RmrkTraitsResourceSlotResource (287) */
+  /** @name RmrkTraitsResourceSlotResource (289) */
   interface RmrkTraitsResourceSlotResource extends Struct {
     readonly base: u32;
     readonly src: Option<Bytes>;
@@ -2584,7 +2608,7 @@
     readonly thumb: Option<Bytes>;
   }
 
-  /** @name PalletRmrkEquipCall (290) */
+  /** @name PalletRmrkEquipCall (292) */
   interface PalletRmrkEquipCall extends Enum {
     readonly isCreateBase: boolean;
     readonly asCreateBase: {
@@ -2606,7 +2630,7 @@
     readonly type: 'CreateBase' | 'ThemeAdd' | 'Equippable';
   }
 
-  /** @name RmrkTraitsPartPartType (293) */
+  /** @name RmrkTraitsPartPartType (295) */
   interface RmrkTraitsPartPartType extends Enum {
     readonly isFixedPart: boolean;
     readonly asFixedPart: RmrkTraitsPartFixedPart;
@@ -2615,14 +2639,14 @@
     readonly type: 'FixedPart' | 'SlotPart';
   }
 
-  /** @name RmrkTraitsPartFixedPart (295) */
+  /** @name RmrkTraitsPartFixedPart (297) */
   interface RmrkTraitsPartFixedPart extends Struct {
     readonly id: u32;
     readonly z: u32;
     readonly src: Bytes;
   }
 
-  /** @name RmrkTraitsPartSlotPart (296) */
+  /** @name RmrkTraitsPartSlotPart (298) */
   interface RmrkTraitsPartSlotPart extends Struct {
     readonly id: u32;
     readonly equippable: RmrkTraitsPartEquippableList;
@@ -2630,7 +2654,7 @@
     readonly z: u32;
   }
 
-  /** @name RmrkTraitsPartEquippableList (297) */
+  /** @name RmrkTraitsPartEquippableList (299) */
   interface RmrkTraitsPartEquippableList extends Enum {
     readonly isAll: boolean;
     readonly isEmpty: boolean;
@@ -2639,20 +2663,54 @@
     readonly type: 'All' | 'Empty' | 'Custom';
   }
 
-  /** @name RmrkTraitsTheme (299) */
+  /** @name RmrkTraitsTheme (301) */
   interface RmrkTraitsTheme extends Struct {
     readonly name: Bytes;
     readonly properties: Vec<RmrkTraitsThemeThemeProperty>;
     readonly inherit: bool;
   }
 
-  /** @name RmrkTraitsThemeThemeProperty (301) */
+  /** @name RmrkTraitsThemeThemeProperty (303) */
   interface RmrkTraitsThemeThemeProperty extends Struct {
     readonly key: Bytes;
     readonly value: Bytes;
   }
 
-  /** @name PalletEvmCall (303) */
+  /** @name PalletAppPromotionCall (305) */
+  interface PalletAppPromotionCall extends Enum {
+    readonly isSetAdminAddress: boolean;
+    readonly asSetAdminAddress: {
+      readonly admin: PalletEvmAccountBasicCrossAccountIdRepr;
+    } & Struct;
+    readonly isStake: boolean;
+    readonly asStake: {
+      readonly amount: u128;
+    } & Struct;
+    readonly isUnstake: boolean;
+    readonly isSponsorCollection: boolean;
+    readonly asSponsorCollection: {
+      readonly collectionId: u32;
+    } & Struct;
+    readonly isStopSponsoringCollection: boolean;
+    readonly asStopSponsoringCollection: {
+      readonly collectionId: u32;
+    } & Struct;
+    readonly isSponsorContract: boolean;
+    readonly asSponsorContract: {
+      readonly contractId: H160;
+    } & Struct;
+    readonly isStopSponsoringContract: boolean;
+    readonly asStopSponsoringContract: {
+      readonly contractId: H160;
+    } & Struct;
+    readonly isPayoutStakers: boolean;
+    readonly asPayoutStakers: {
+      readonly stakersNumber: Option<u8>;
+    } & Struct;
+    readonly type: 'SetAdminAddress' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsoringCollection' | 'SponsorContract' | 'StopSponsoringContract' | 'PayoutStakers';
+  }
+
+  /** @name PalletEvmCall (307) */
   interface PalletEvmCall extends Enum {
     readonly isWithdraw: boolean;
     readonly asWithdraw: {
@@ -2697,7 +2755,7 @@
     readonly type: 'Withdraw' | 'Call' | 'Create' | 'Create2';
   }
 
-  /** @name PalletEthereumCall (307) */
+  /** @name PalletEthereumCall (311) */
   interface PalletEthereumCall extends Enum {
     readonly isTransact: boolean;
     readonly asTransact: {
@@ -2706,7 +2764,7 @@
     readonly type: 'Transact';
   }
 
-  /** @name EthereumTransactionTransactionV2 (308) */
+  /** @name EthereumTransactionTransactionV2 (312) */
   interface EthereumTransactionTransactionV2 extends Enum {
     readonly isLegacy: boolean;
     readonly asLegacy: EthereumTransactionLegacyTransaction;
@@ -2717,7 +2775,7 @@
     readonly type: 'Legacy' | 'Eip2930' | 'Eip1559';
   }
 
-  /** @name EthereumTransactionLegacyTransaction (309) */
+  /** @name EthereumTransactionLegacyTransaction (313) */
   interface EthereumTransactionLegacyTransaction extends Struct {
     readonly nonce: U256;
     readonly gasPrice: U256;
@@ -2728,7 +2786,7 @@
     readonly signature: EthereumTransactionTransactionSignature;
   }
 
-  /** @name EthereumTransactionTransactionAction (310) */
+  /** @name EthereumTransactionTransactionAction (314) */
   interface EthereumTransactionTransactionAction extends Enum {
     readonly isCall: boolean;
     readonly asCall: H160;
@@ -2736,14 +2794,14 @@
     readonly type: 'Call' | 'Create';
   }
 
-  /** @name EthereumTransactionTransactionSignature (311) */
+  /** @name EthereumTransactionTransactionSignature (315) */
   interface EthereumTransactionTransactionSignature extends Struct {
     readonly v: u64;
     readonly r: H256;
     readonly s: H256;
   }
 
-  /** @name EthereumTransactionEip2930Transaction (313) */
+  /** @name EthereumTransactionEip2930Transaction (317) */
   interface EthereumTransactionEip2930Transaction extends Struct {
     readonly chainId: u64;
     readonly nonce: U256;
@@ -2758,13 +2816,13 @@
     readonly s: H256;
   }
 
-  /** @name EthereumTransactionAccessListItem (315) */
+  /** @name EthereumTransactionAccessListItem (319) */
   interface EthereumTransactionAccessListItem extends Struct {
     readonly address: H160;
     readonly storageKeys: Vec<H256>;
   }
 
-  /** @name EthereumTransactionEip1559Transaction (316) */
+  /** @name EthereumTransactionEip1559Transaction (320) */
   interface EthereumTransactionEip1559Transaction extends Struct {
     readonly chainId: u64;
     readonly nonce: U256;
@@ -2780,7 +2838,7 @@
     readonly s: H256;
   }
 
-  /** @name PalletEvmMigrationCall (317) */
+  /** @name PalletEvmMigrationCall (321) */
   interface PalletEvmMigrationCall extends Enum {
     readonly isBegin: boolean;
     readonly asBegin: {
@@ -2799,13 +2857,13 @@
     readonly type: 'Begin' | 'SetData' | 'Finish';
   }
 
-  /** @name PalletSudoError (320) */
+  /** @name PalletSudoError (324) */
   interface PalletSudoError extends Enum {
     readonly isRequireSudo: boolean;
     readonly type: 'RequireSudo';
   }
 
-  /** @name OrmlVestingModuleError (322) */
+  /** @name OrmlVestingModuleError (326) */
   interface OrmlVestingModuleError extends Enum {
     readonly isZeroVestingPeriod: boolean;
     readonly isZeroVestingPeriodCount: boolean;
@@ -2816,21 +2874,21 @@
     readonly type: 'ZeroVestingPeriod' | 'ZeroVestingPeriodCount' | 'InsufficientBalanceToLock' | 'TooManyVestingSchedules' | 'AmountLow' | 'MaxVestingSchedulesExceeded';
   }
 
-  /** @name CumulusPalletXcmpQueueInboundChannelDetails (324) */
+  /** @name CumulusPalletXcmpQueueInboundChannelDetails (328) */
   interface CumulusPalletXcmpQueueInboundChannelDetails extends Struct {
     readonly sender: u32;
     readonly state: CumulusPalletXcmpQueueInboundState;
     readonly messageMetadata: Vec<ITuple<[u32, PolkadotParachainPrimitivesXcmpMessageFormat]>>;
   }
 
-  /** @name CumulusPalletXcmpQueueInboundState (325) */
+  /** @name CumulusPalletXcmpQueueInboundState (329) */
   interface CumulusPalletXcmpQueueInboundState extends Enum {
     readonly isOk: boolean;
     readonly isSuspended: boolean;
     readonly type: 'Ok' | 'Suspended';
   }
 
-  /** @name PolkadotParachainPrimitivesXcmpMessageFormat (328) */
+  /** @name PolkadotParachainPrimitivesXcmpMessageFormat (332) */
   interface PolkadotParachainPrimitivesXcmpMessageFormat extends Enum {
     readonly isConcatenatedVersionedXcm: boolean;
     readonly isConcatenatedEncodedBlob: boolean;
@@ -2838,7 +2896,7 @@
     readonly type: 'ConcatenatedVersionedXcm' | 'ConcatenatedEncodedBlob' | 'Signals';
   }
 
-  /** @name CumulusPalletXcmpQueueOutboundChannelDetails (331) */
+  /** @name CumulusPalletXcmpQueueOutboundChannelDetails (335) */
   interface CumulusPalletXcmpQueueOutboundChannelDetails extends Struct {
     readonly recipient: u32;
     readonly state: CumulusPalletXcmpQueueOutboundState;
@@ -2847,14 +2905,14 @@
     readonly lastIndex: u16;
   }
 
-  /** @name CumulusPalletXcmpQueueOutboundState (332) */
+  /** @name CumulusPalletXcmpQueueOutboundState (336) */
   interface CumulusPalletXcmpQueueOutboundState extends Enum {
     readonly isOk: boolean;
     readonly isSuspended: boolean;
     readonly type: 'Ok' | 'Suspended';
   }
 
-  /** @name CumulusPalletXcmpQueueQueueConfigData (334) */
+  /** @name CumulusPalletXcmpQueueQueueConfigData (338) */
   interface CumulusPalletXcmpQueueQueueConfigData extends Struct {
     readonly suspendThreshold: u32;
     readonly dropThreshold: u32;
@@ -2864,7 +2922,7 @@
     readonly xcmpMaxIndividualWeight: u64;
   }
 
-  /** @name CumulusPalletXcmpQueueError (336) */
+  /** @name CumulusPalletXcmpQueueError (340) */
   interface CumulusPalletXcmpQueueError extends Enum {
     readonly isFailedToSend: boolean;
     readonly isBadXcmOrigin: boolean;
@@ -2874,7 +2932,7 @@
     readonly type: 'FailedToSend' | 'BadXcmOrigin' | 'BadXcm' | 'BadOverweightIndex' | 'WeightOverLimit';
   }
 
-  /** @name PalletXcmError (337) */
+  /** @name PalletXcmError (341) */
   interface PalletXcmError extends Enum {
     readonly isUnreachable: boolean;
     readonly isSendFailure: boolean;
@@ -2892,29 +2950,29 @@
     readonly type: 'Unreachable' | 'SendFailure' | 'Filtered' | 'UnweighableMessage' | 'DestinationNotInvertible' | 'Empty' | 'CannotReanchor' | 'TooManyAssets' | 'InvalidOrigin' | 'BadVersion' | 'BadLocation' | 'NoSubscription' | 'AlreadySubscribed';
   }
 
-  /** @name CumulusPalletXcmError (338) */
+  /** @name CumulusPalletXcmError (342) */
   type CumulusPalletXcmError = Null;
 
-  /** @name CumulusPalletDmpQueueConfigData (339) */
+  /** @name CumulusPalletDmpQueueConfigData (343) */
   interface CumulusPalletDmpQueueConfigData extends Struct {
     readonly maxIndividual: u64;
   }
 
-  /** @name CumulusPalletDmpQueuePageIndexData (340) */
+  /** @name CumulusPalletDmpQueuePageIndexData (344) */
   interface CumulusPalletDmpQueuePageIndexData extends Struct {
     readonly beginUsed: u32;
     readonly endUsed: u32;
     readonly overweightCount: u64;
   }
 
-  /** @name CumulusPalletDmpQueueError (343) */
+  /** @name CumulusPalletDmpQueueError (347) */
   interface CumulusPalletDmpQueueError extends Enum {
     readonly isUnknown: boolean;
     readonly isOverLimit: boolean;
     readonly type: 'Unknown' | 'OverLimit';
   }
 
-  /** @name PalletUniqueError (347) */
+  /** @name PalletUniqueError (351) */
   interface PalletUniqueError extends Enum {
     readonly isCollectionDecimalPointLimitExceeded: boolean;
     readonly isConfirmUnsetSponsorFail: boolean;
@@ -2923,7 +2981,7 @@
     readonly type: 'CollectionDecimalPointLimitExceeded' | 'ConfirmUnsetSponsorFail' | 'EmptyArgument' | 'RepartitionCalledOnNonRefungibleCollection';
   }
 
-  /** @name PalletUniqueSchedulerScheduledV3 (350) */
+  /** @name PalletUniqueSchedulerScheduledV3 (354) */
   interface PalletUniqueSchedulerScheduledV3 extends Struct {
     readonly maybeId: Option<U8aFixed>;
     readonly priority: u8;
@@ -2932,7 +2990,7 @@
     readonly origin: OpalRuntimeOriginCaller;
   }
 
-  /** @name OpalRuntimeOriginCaller (351) */
+  /** @name OpalRuntimeOriginCaller (355) */
   interface OpalRuntimeOriginCaller extends Enum {
     readonly isSystem: boolean;
     readonly asSystem: FrameSupportDispatchRawOrigin;
@@ -2946,7 +3004,7 @@
     readonly type: 'System' | 'Void' | 'PolkadotXcm' | 'CumulusXcm' | 'Ethereum';
   }
 
-  /** @name FrameSupportDispatchRawOrigin (352) */
+  /** @name FrameSupportDispatchRawOrigin (356) */
   interface FrameSupportDispatchRawOrigin extends Enum {
     readonly isRoot: boolean;
     readonly isSigned: boolean;
@@ -2955,7 +3013,7 @@
     readonly type: 'Root' | 'Signed' | 'None';
   }
 
-  /** @name PalletXcmOrigin (353) */
+  /** @name PalletXcmOrigin (357) */
   interface PalletXcmOrigin extends Enum {
     readonly isXcm: boolean;
     readonly asXcm: XcmV1MultiLocation;
@@ -2964,7 +3022,7 @@
     readonly type: 'Xcm' | 'Response';
   }
 
-  /** @name CumulusPalletXcmOrigin (354) */
+  /** @name CumulusPalletXcmOrigin (358) */
   interface CumulusPalletXcmOrigin extends Enum {
     readonly isRelay: boolean;
     readonly isSiblingParachain: boolean;
@@ -2972,17 +3030,17 @@
     readonly type: 'Relay' | 'SiblingParachain';
   }
 
-  /** @name PalletEthereumRawOrigin (355) */
+  /** @name PalletEthereumRawOrigin (359) */
   interface PalletEthereumRawOrigin extends Enum {
     readonly isEthereumTransaction: boolean;
     readonly asEthereumTransaction: H160;
     readonly type: 'EthereumTransaction';
   }
 
-  /** @name SpCoreVoid (356) */
+  /** @name SpCoreVoid (360) */
   type SpCoreVoid = Null;
 
-  /** @name PalletUniqueSchedulerError (357) */
+  /** @name PalletUniqueSchedulerError (361) */
   interface PalletUniqueSchedulerError extends Enum {
     readonly isFailedToSchedule: boolean;
     readonly isNotFound: boolean;
@@ -2991,21 +3049,21 @@
     readonly type: 'FailedToSchedule' | 'NotFound' | 'TargetBlockNumberInPast' | 'RescheduleNoChange';
   }
 
-  /** @name UpDataStructsCollection (358) */
+  /** @name UpDataStructsCollection (362) */
   interface UpDataStructsCollection extends Struct {
     readonly owner: AccountId32;
     readonly mode: UpDataStructsCollectionMode;
     readonly name: Vec<u16>;
     readonly description: Vec<u16>;
     readonly tokenPrefix: Bytes;
-    readonly sponsorship: UpDataStructsSponsorshipState;
+    readonly sponsorship: UpDataStructsSponsorshipStateAccountId32;
     readonly limits: UpDataStructsCollectionLimits;
     readonly permissions: UpDataStructsCollectionPermissions;
     readonly externalCollection: bool;
   }
 
-  /** @name UpDataStructsSponsorshipState (359) */
-  interface UpDataStructsSponsorshipState extends Enum {
+  /** @name UpDataStructsSponsorshipStateAccountId32 (363) */
+  interface UpDataStructsSponsorshipStateAccountId32 extends Enum {
     readonly isDisabled: boolean;
     readonly isUnconfirmed: boolean;
     readonly asUnconfirmed: AccountId32;
@@ -3014,50 +3072,50 @@
     readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed';
   }
 
-  /** @name UpDataStructsProperties (360) */
+  /** @name UpDataStructsProperties (364) */
   interface UpDataStructsProperties extends Struct {
     readonly map: UpDataStructsPropertiesMapBoundedVec;
     readonly consumedSpace: u32;
     readonly spaceLimit: u32;
   }
 
-  /** @name UpDataStructsPropertiesMapBoundedVec (361) */
+  /** @name UpDataStructsPropertiesMapBoundedVec (365) */
   interface UpDataStructsPropertiesMapBoundedVec extends BTreeMap<Bytes, Bytes> {}
 
-  /** @name UpDataStructsPropertiesMapPropertyPermission (366) */
+  /** @name UpDataStructsPropertiesMapPropertyPermission (370) */
   interface UpDataStructsPropertiesMapPropertyPermission extends BTreeMap<Bytes, UpDataStructsPropertyPermission> {}
 
-  /** @name UpDataStructsCollectionStats (373) */
+  /** @name UpDataStructsCollectionStats (377) */
   interface UpDataStructsCollectionStats extends Struct {
     readonly created: u32;
     readonly destroyed: u32;
     readonly alive: u32;
   }
 
-  /** @name UpDataStructsTokenChild (374) */
+  /** @name UpDataStructsTokenChild (378) */
   interface UpDataStructsTokenChild extends Struct {
     readonly token: u32;
     readonly collection: u32;
   }
 
-  /** @name PhantomTypeUpDataStructs (375) */
+  /** @name PhantomTypeUpDataStructs (379) */
   interface PhantomTypeUpDataStructs extends Vec<ITuple<[UpDataStructsTokenData, UpDataStructsRpcCollection, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftNftInfo, RmrkTraitsResourceResourceInfo, RmrkTraitsPropertyPropertyInfo, RmrkTraitsBaseBaseInfo, RmrkTraitsPartPartType, RmrkTraitsTheme, RmrkTraitsNftNftChild]>> {}
 
-  /** @name UpDataStructsTokenData (377) */
+  /** @name UpDataStructsTokenData (381) */
   interface UpDataStructsTokenData extends Struct {
     readonly properties: Vec<UpDataStructsProperty>;
     readonly owner: Option<PalletEvmAccountBasicCrossAccountIdRepr>;
     readonly pieces: u128;
   }
 
-  /** @name UpDataStructsRpcCollection (379) */
+  /** @name UpDataStructsRpcCollection (383) */
   interface UpDataStructsRpcCollection extends Struct {
     readonly owner: AccountId32;
     readonly mode: UpDataStructsCollectionMode;
     readonly name: Vec<u16>;
     readonly description: Vec<u16>;
     readonly tokenPrefix: Bytes;
-    readonly sponsorship: UpDataStructsSponsorshipState;
+    readonly sponsorship: UpDataStructsSponsorshipStateAccountId32;
     readonly limits: UpDataStructsCollectionLimits;
     readonly permissions: UpDataStructsCollectionPermissions;
     readonly tokenPropertyPermissions: Vec<UpDataStructsPropertyKeyPermission>;
@@ -3065,7 +3123,7 @@
     readonly readOnly: bool;
   }
 
-  /** @name RmrkTraitsCollectionCollectionInfo (380) */
+  /** @name RmrkTraitsCollectionCollectionInfo (384) */
   interface RmrkTraitsCollectionCollectionInfo extends Struct {
     readonly issuer: AccountId32;
     readonly metadata: Bytes;
@@ -3074,7 +3132,7 @@
     readonly nftsCount: u32;
   }
 
-  /** @name RmrkTraitsNftNftInfo (381) */
+  /** @name RmrkTraitsNftNftInfo (385) */
   interface RmrkTraitsNftNftInfo extends Struct {
     readonly owner: RmrkTraitsNftAccountIdOrCollectionNftTuple;
     readonly royalty: Option<RmrkTraitsNftRoyaltyInfo>;
@@ -3083,13 +3141,13 @@
     readonly pending: bool;
   }
 
-  /** @name RmrkTraitsNftRoyaltyInfo (383) */
+  /** @name RmrkTraitsNftRoyaltyInfo (387) */
   interface RmrkTraitsNftRoyaltyInfo extends Struct {
     readonly recipient: AccountId32;
     readonly amount: Permill;
   }
 
-  /** @name RmrkTraitsResourceResourceInfo (384) */
+  /** @name RmrkTraitsResourceResourceInfo (388) */
   interface RmrkTraitsResourceResourceInfo extends Struct {
     readonly id: u32;
     readonly resource: RmrkTraitsResourceResourceTypes;
@@ -3097,26 +3155,26 @@
     readonly pendingRemoval: bool;
   }
 
-  /** @name RmrkTraitsPropertyPropertyInfo (385) */
+  /** @name RmrkTraitsPropertyPropertyInfo (389) */
   interface RmrkTraitsPropertyPropertyInfo extends Struct {
     readonly key: Bytes;
     readonly value: Bytes;
   }
 
-  /** @name RmrkTraitsBaseBaseInfo (386) */
+  /** @name RmrkTraitsBaseBaseInfo (390) */
   interface RmrkTraitsBaseBaseInfo extends Struct {
     readonly issuer: AccountId32;
     readonly baseType: Bytes;
     readonly symbol: Bytes;
   }
 
-  /** @name RmrkTraitsNftNftChild (387) */
+  /** @name RmrkTraitsNftNftChild (391) */
   interface RmrkTraitsNftNftChild extends Struct {
     readonly collectionId: u32;
     readonly nftId: u32;
   }
 
-  /** @name PalletCommonError (389) */
+  /** @name PalletCommonError (393) */
   interface PalletCommonError extends Enum {
     readonly isCollectionNotFound: boolean;
     readonly isMustBeTokenOwner: boolean;
@@ -3155,7 +3213,7 @@
     readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'CantDestroyNotEmptyCollection' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation' | 'NotSufficientFounds' | 'UserIsNotAllowedToNest' | 'SourceCollectionIsNotAllowedToNest' | 'CollectionFieldSizeExceeded' | 'NoSpaceForProperty' | 'PropertyLimitReached' | 'PropertyKeyIsTooLong' | 'InvalidCharacterInPropertyKey' | 'EmptyPropertyKey' | 'CollectionIsExternal' | 'CollectionIsInternal';
   }
 
-  /** @name PalletFungibleError (391) */
+  /** @name PalletFungibleError (395) */
   interface PalletFungibleError extends Enum {
     readonly isNotFungibleDataUsedToMintFungibleCollectionToken: boolean;
     readonly isFungibleItemsHaveNoId: boolean;
@@ -3165,12 +3223,12 @@
     readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed';
   }
 
-  /** @name PalletRefungibleItemData (392) */
+  /** @name PalletRefungibleItemData (396) */
   interface PalletRefungibleItemData extends Struct {
     readonly constData: Bytes;
   }
 
-  /** @name PalletRefungibleError (397) */
+  /** @name PalletRefungibleError (401) */
   interface PalletRefungibleError extends Enum {
     readonly isNotRefungibleDataUsedToMintFungibleCollectionToken: boolean;
     readonly isWrongRefungiblePieces: boolean;
@@ -3180,20 +3238,19 @@
     readonly type: 'NotRefungibleDataUsedToMintFungibleCollectionToken' | 'WrongRefungiblePieces' | 'RepartitionWhileNotOwningAllPieces' | 'RefungibleDisallowsNesting' | 'SettingPropertiesNotAllowed';
   }
 
-  /** @name PalletNonfungibleItemData (398) */
+  /** @name PalletNonfungibleItemData (402) */
   interface PalletNonfungibleItemData extends Struct {
     readonly owner: PalletEvmAccountBasicCrossAccountIdRepr;
   }
 
-  /** @name UpDataStructsPropertyScope (400) */
+  /** @name UpDataStructsPropertyScope (404) */
   interface UpDataStructsPropertyScope extends Enum {
     readonly isNone: boolean;
     readonly isRmrk: boolean;
-    readonly isEth: boolean;
-    readonly type: 'None' | 'Rmrk' | 'Eth';
+    readonly type: 'None' | 'Rmrk';
   }
 
-  /** @name PalletNonfungibleError (402) */
+  /** @name PalletNonfungibleError (406) */
   interface PalletNonfungibleError extends Enum {
     readonly isNotNonfungibleDataUsedToMintFungibleCollectionToken: boolean;
     readonly isNonfungibleItemsHaveNoAmount: boolean;
@@ -3201,7 +3258,7 @@
     readonly type: 'NotNonfungibleDataUsedToMintFungibleCollectionToken' | 'NonfungibleItemsHaveNoAmount' | 'CantBurnNftWithChildren';
   }
 
-  /** @name PalletStructureError (403) */
+  /** @name PalletStructureError (407) */
   interface PalletStructureError extends Enum {
     readonly isOuroborosDetected: boolean;
     readonly isDepthLimit: boolean;
@@ -3210,7 +3267,7 @@
     readonly type: 'OuroborosDetected' | 'DepthLimit' | 'BreadthLimit' | 'TokenNotFound';
   }
 
-  /** @name PalletRmrkCoreError (404) */
+  /** @name PalletRmrkCoreError (408) */
   interface PalletRmrkCoreError extends Enum {
     readonly isCorruptedCollectionType: boolean;
     readonly isRmrkPropertyKeyIsTooLong: boolean;
@@ -3234,7 +3291,7 @@
     readonly type: 'CorruptedCollectionType' | 'RmrkPropertyKeyIsTooLong' | 'RmrkPropertyValueIsTooLong' | 'RmrkPropertyIsNotFound' | 'UnableToDecodeRmrkData' | 'CollectionNotEmpty' | 'NoAvailableCollectionId' | 'NoAvailableNftId' | 'CollectionUnknown' | 'NoPermission' | 'NonTransferable' | 'CollectionFullOrLocked' | 'ResourceDoesntExist' | 'CannotSendToDescendentOrSelf' | 'CannotAcceptNonOwnedNft' | 'CannotRejectNonOwnedNft' | 'CannotRejectNonPendingNft' | 'ResourceNotPending' | 'NoAvailableResourceId';
   }
 
-  /** @name PalletRmrkEquipError (406) */
+  /** @name PalletRmrkEquipError (410) */
   interface PalletRmrkEquipError extends Enum {
     readonly isPermissionError: boolean;
     readonly isNoAvailableBaseId: boolean;
@@ -3246,7 +3303,18 @@
     readonly type: 'PermissionError' | 'NoAvailableBaseId' | 'NoAvailablePartId' | 'BaseDoesntExist' | 'NeedsDefaultThemeFirst' | 'PartDoesntExist' | 'NoEquippableOnFixedPart';
   }
 
-  /** @name PalletEvmError (409) */
+  /** @name PalletAppPromotionError (416) */
+  interface PalletAppPromotionError extends Enum {
+    readonly isAdminNotSet: boolean;
+    readonly isNoPermission: boolean;
+    readonly isNotSufficientFunds: boolean;
+    readonly isPendingForBlockOverflow: boolean;
+    readonly isSponsorNotSet: boolean;
+    readonly isIncorrectLockedBalanceOperation: boolean;
+    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation';
+  }
+
+  /** @name PalletEvmError (419) */
   interface PalletEvmError extends Enum {
     readonly isBalanceLow: boolean;
     readonly isFeeOverflow: boolean;
@@ -3257,7 +3325,7 @@
     readonly type: 'BalanceLow' | 'FeeOverflow' | 'PaymentOverflow' | 'WithdrawFailed' | 'GasPriceTooLow' | 'InvalidNonce';
   }
 
-  /** @name FpRpcTransactionStatus (412) */
+  /** @name FpRpcTransactionStatus (422) */
   interface FpRpcTransactionStatus extends Struct {
     readonly transactionHash: H256;
     readonly transactionIndex: u32;
@@ -3268,10 +3336,10 @@
     readonly logsBloom: EthbloomBloom;
   }
 
-  /** @name EthbloomBloom (414) */
+  /** @name EthbloomBloom (424) */
   interface EthbloomBloom extends U8aFixed {}
 
-  /** @name EthereumReceiptReceiptV3 (416) */
+  /** @name EthereumReceiptReceiptV3 (426) */
   interface EthereumReceiptReceiptV3 extends Enum {
     readonly isLegacy: boolean;
     readonly asLegacy: EthereumReceiptEip658ReceiptData;
@@ -3282,7 +3350,7 @@
     readonly type: 'Legacy' | 'Eip2930' | 'Eip1559';
   }
 
-  /** @name EthereumReceiptEip658ReceiptData (417) */
+  /** @name EthereumReceiptEip658ReceiptData (427) */
   interface EthereumReceiptEip658ReceiptData extends Struct {
     readonly statusCode: u8;
     readonly usedGas: U256;
@@ -3290,14 +3358,14 @@
     readonly logs: Vec<EthereumLog>;
   }
 
-  /** @name EthereumBlock (418) */
+  /** @name EthereumBlock (428) */
   interface EthereumBlock extends Struct {
     readonly header: EthereumHeader;
     readonly transactions: Vec<EthereumTransactionTransactionV2>;
     readonly ommers: Vec<EthereumHeader>;
   }
 
-  /** @name EthereumHeader (419) */
+  /** @name EthereumHeader (429) */
   interface EthereumHeader extends Struct {
     readonly parentHash: H256;
     readonly ommersHash: H256;
@@ -3316,24 +3384,34 @@
     readonly nonce: EthereumTypesHashH64;
   }
 
-  /** @name EthereumTypesHashH64 (420) */
+  /** @name EthereumTypesHashH64 (430) */
   interface EthereumTypesHashH64 extends U8aFixed {}
 
-  /** @name PalletEthereumError (425) */
+  /** @name PalletEthereumError (435) */
   interface PalletEthereumError extends Enum {
     readonly isInvalidSignature: boolean;
     readonly isPreLogExists: boolean;
     readonly type: 'InvalidSignature' | 'PreLogExists';
   }
 
-  /** @name PalletEvmCoderSubstrateError (426) */
+  /** @name PalletEvmCoderSubstrateError (436) */
   interface PalletEvmCoderSubstrateError extends Enum {
     readonly isOutOfGas: boolean;
     readonly isOutOfFund: boolean;
     readonly type: 'OutOfGas' | 'OutOfFund';
   }
 
-  /** @name PalletEvmContractHelpersSponsoringModeT (427) */
+  /** @name UpDataStructsSponsorshipStateBasicCrossAccountIdRepr (437) */
+  interface UpDataStructsSponsorshipStateBasicCrossAccountIdRepr extends Enum {
+    readonly isDisabled: boolean;
+    readonly isUnconfirmed: boolean;
+    readonly asUnconfirmed: PalletEvmAccountBasicCrossAccountIdRepr;
+    readonly isConfirmed: boolean;
+    readonly asConfirmed: PalletEvmAccountBasicCrossAccountIdRepr;
+    readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed';
+  }
+
+  /** @name PalletEvmContractHelpersSponsoringModeT (438) */
   interface PalletEvmContractHelpersSponsoringModeT extends Enum {
     readonly isDisabled: boolean;
     readonly isAllowlisted: boolean;
@@ -3341,20 +3419,21 @@
     readonly type: 'Disabled' | 'Allowlisted' | 'Generous';
   }
 
-  /** @name PalletEvmContractHelpersError (429) */
+  /** @name PalletEvmContractHelpersError (440) */
   interface PalletEvmContractHelpersError extends Enum {
     readonly isNoPermission: boolean;
-    readonly type: 'NoPermission';
+    readonly isNoPendingSponsor: boolean;
+    readonly type: 'NoPermission' | 'NoPendingSponsor';
   }
 
-  /** @name PalletEvmMigrationError (430) */
+  /** @name PalletEvmMigrationError (441) */
   interface PalletEvmMigrationError extends Enum {
     readonly isAccountNotEmpty: boolean;
     readonly isAccountIsNotMigrating: boolean;
     readonly type: 'AccountNotEmpty' | 'AccountIsNotMigrating';
   }
 
-  /** @name SpRuntimeMultiSignature (432) */
+  /** @name SpRuntimeMultiSignature (443) */
   interface SpRuntimeMultiSignature extends Enum {
     readonly isEd25519: boolean;
     readonly asEd25519: SpCoreEd25519Signature;
@@ -3365,34 +3444,34 @@
     readonly type: 'Ed25519' | 'Sr25519' | 'Ecdsa';
   }
 
-  /** @name SpCoreEd25519Signature (433) */
+  /** @name SpCoreEd25519Signature (444) */
   interface SpCoreEd25519Signature extends U8aFixed {}
 
-  /** @name SpCoreSr25519Signature (435) */
+  /** @name SpCoreSr25519Signature (446) */
   interface SpCoreSr25519Signature extends U8aFixed {}
 
-  /** @name SpCoreEcdsaSignature (436) */
+  /** @name SpCoreEcdsaSignature (447) */
   interface SpCoreEcdsaSignature extends U8aFixed {}
 
-  /** @name FrameSystemExtensionsCheckSpecVersion (439) */
+  /** @name FrameSystemExtensionsCheckSpecVersion (450) */
   type FrameSystemExtensionsCheckSpecVersion = Null;
 
-  /** @name FrameSystemExtensionsCheckGenesis (440) */
+  /** @name FrameSystemExtensionsCheckGenesis (451) */
   type FrameSystemExtensionsCheckGenesis = Null;
 
-  /** @name FrameSystemExtensionsCheckNonce (443) */
+  /** @name FrameSystemExtensionsCheckNonce (454) */
   interface FrameSystemExtensionsCheckNonce extends Compact<u32> {}
 
-  /** @name FrameSystemExtensionsCheckWeight (444) */
+  /** @name FrameSystemExtensionsCheckWeight (455) */
   type FrameSystemExtensionsCheckWeight = Null;
 
-  /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (445) */
+  /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (456) */
   interface PalletTemplateTransactionPaymentChargeTransactionPayment extends Compact<u128> {}
 
-  /** @name OpalRuntimeRuntime (446) */
+  /** @name OpalRuntimeRuntime (457) */
   type OpalRuntimeRuntime = Null;
 
-  /** @name PalletEthereumFakeTransactionFinalizer (447) */
+  /** @name PalletEthereumFakeTransactionFinalizer (458) */
   type PalletEthereumFakeTransactionFinalizer = Null;
 
 } // declare module
modifiedtests/src/interfaces/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types.ts
+++ b/tests/src/interfaces/types.ts
@@ -2,5 +2,6 @@
 /* eslint-disable */
 
 export * from './unique/types';
+export * from './appPromotion/types';
 export * from './rmrk/types';
 export * from './default/types';
modifiedtests/src/pallet-presence.test.tsdiffbeforeafterboth
--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -68,9 +68,10 @@
       const refungible = 'refungible';
       const scheduler = 'scheduler';
       const rmrkPallets = ['rmrkcore', 'rmrkequip'];
+      const appPromotion = 'apppromotion';
 
       if (chain.eq('OPAL by UNIQUE')) {
-        requiredPallets.push(refungible, scheduler, ...rmrkPallets);
+        requiredPallets.push(refungible, scheduler, appPromotion, ...rmrkPallets);
       } else if (chain.eq('QUARTZ by UNIQUE')) {
         // Insert Quartz additional pallets here
       } else if (chain.eq('UNIQUE')) {
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -245,8 +245,8 @@
         section: 'common',
         index: '0x4202',
         data: [ 
-          collection.collectionId.toString(), 
-          token.tokenId.toString(), 
+          helper.api!.createType('u32', collection.collectionId).toHuman(), 
+          helper.api!.createType('u32', token.tokenId).toHuman(),
           {Substrate: alice.address}, 
           '100',
         ],
@@ -265,8 +265,8 @@
         section: 'common',
         index: '0x4203',
         data: [ 
-          collection.collectionId.toString(), 
-          token.tokenId.toString(), 
+          helper.api!.createType('u32', collection.collectionId).toHuman(), 
+          helper.api!.createType('u32', token.tokenId).toHuman(),
           {Substrate: alice.address}, 
           '50',
         ],
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -27,6 +27,7 @@
       const alice = privateKey('//Alice');
       const bob = privateKey('//Bob');
       const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      
       const collectionInfo = await collection.getData();
       expect(collectionInfo?.raw.owner.toString()).to.be.deep.eq(alice.address);
       // first - add collection admin Bob
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -25,6 +25,8 @@
 import privateKey from './privateKey';
 import promisifySubstrate from './promisify-substrate';
 
+import {SilentConsole} from '../util/playgrounds/unique.dev';
+
 
 
 function defaultApiOptions(): ApiOptions {
@@ -42,6 +44,7 @@
     },
     rpc: {
       unique: defs.unique.rpc,
+      appPromotion: defs.appPromotion.rpc,
       rmrk: defs.rmrk.rpc,
       eth: {
         feeHistory: {
@@ -75,25 +78,8 @@
   const api: ApiPromise = new ApiPromise(settings);
   let result: T = null as unknown as T;
 
-  // TODO: Remove, this is temporary: Filter unneeded API output
-  // (Jaco promised it will be removed in the next version)
-  const consoleErr = console.error;
-  const consoleLog = console.log;
-  const consoleWarn = console.warn;
-
-  const outFn = (printer: any) => (...args: any[]) => {
-    for (const arg of args) {
-      if (typeof arg !== 'string')
-        continue;
-      if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')
-        return;
-    }
-    printer(...args);
-  };
-
-  console.error = outFn(consoleErr.bind(console));
-  console.log = outFn(consoleLog.bind(console));
-  console.warn = outFn(consoleWarn.bind(console));
+  const silentConsole = new SilentConsole();
+  silentConsole.enable();
 
   try {
     await promisifySubstrate(api, async () => {
@@ -106,9 +92,7 @@
     })();
   } finally {
     await api.disconnect();
-    console.error = consoleErr;
-    console.log = consoleLog;
-    console.warn = consoleWarn;
+    silentConsole.disable();
   }
   return result as T;
 }
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -17,10 +17,11 @@
 import '../interfaces/augment-api-rpc';
 import '../interfaces/augment-api-query';
 import {ApiPromise} from '@polkadot/api';
-import type {AccountId, EventRecord, Event} from '@polkadot/types/interfaces';
+import type {AccountId, EventRecord, Event, BlockNumber} from '@polkadot/types/interfaces';
 import type {GenericEventData} from '@polkadot/types';
 import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types';
 import {evmToAddress} from '@polkadot/util-crypto';
+import {AnyNumber} from '@polkadot/types-codec/types';
 import BN from 'bn.js';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
@@ -48,6 +49,7 @@
   Fungible = 'fungible',
   NFT = 'nonfungible',
   Scheduler = 'scheduler',
+  AppPromotion = 'apppromotion',
 }
 
 export async function isUnique(): Promise<boolean> {
@@ -1779,3 +1781,23 @@
 
 itApi.only = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {only: true});
 itApi.skip = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {skip: true});
+
+
+export async function expectSubstrateEventsAtBlock(api: ApiPromise, blockNumber: AnyNumber | BlockNumber, section: string, methods: string[], dryRun = false) {
+  const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
+  const subEvents = (await api.query.system.events.at(blockHash))
+    .filter(x => x.event.section === section)
+    .map((x) => x.toHuman());
+  const events = methods.map((m) => {
+    return {
+      event: {
+        method: m,
+        section,
+      },
+    };
+  });
+  if (!dryRun) {
+    expect(subEvents).to.be.like(events);
+  }
+  return subEvents;
+}
\ No newline at end of file
modifiedtests/src/util/playgrounds/index.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/index.ts
+++ b/tests/src/util/playgrounds/index.ts
@@ -2,39 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 
 import {IKeyringPair} from '@polkadot/types/types';
+import {Context} from 'mocha';
 import config from '../../config';
 import '../../interfaces/augment-api-events';
-import {DevUniqueHelper} from './unique.dev';
+import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev';
 
-class SilentLogger {
-  log(msg: any, level: any): void { }
-  level = {
-    ERROR: 'ERROR' as const,
-    WARNING: 'WARNING' as const,
-    INFO: 'INFO' as const,
-  };
-}
 
 export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
-  // TODO: Remove, this is temporary: Filter unneeded API output
-  // (Jaco promised it will be removed in the next version)
-  const consoleErr = console.error;
-  const consoleLog = console.log;
-  const consoleWarn = console.warn;
+  const silentConsole = new SilentConsole();
+  silentConsole.enable();
 
-  const outFn = (printer: any) => (...args: any[]) => {
-    for (const arg of args) {
-      if (typeof arg !== 'string')
-        continue;
-      if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')
-        return;
-    }
-    printer(...args);
-  };
-
-  console.error = outFn(consoleErr.bind(console));
-  console.log = outFn(consoleLog.bind(console));
-  console.warn = outFn(consoleWarn.bind(console));
   const helper = new DevUniqueHelper(new SilentLogger());
 
   try {
@@ -45,8 +22,42 @@
   }
   finally {
     await helper.disconnect();
-    console.error = consoleErr;
-    console.log = consoleLog;
-    console.warn = consoleWarn;
+    silentConsole.disable();
   }
 };
+
+export enum Pallets {
+  Inflation = 'inflation',
+  RmrkCore = 'rmrkcore',
+  RmrkEquip = 'rmrkequip',
+  ReFungible = 'refungible',
+  Fungible = 'fungible',
+  NFT = 'nonfungible',
+  Scheduler = 'scheduler',
+}
+
+export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: string[]) {
+  const missingPallets = helper.fetchMissingPalletNames(requiredPallets);
+    
+  if (missingPallets.length > 0) {
+    const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`;
+    console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg);
+    test.skip();
+  }
+}
+
+export async function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
+  (opts.only ? it.only : 
+    opts.skip ? it.skip : it)(name, async function () {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      if (opts.requiredPallets) {
+        requirePalletsOrSkip(this, helper, opts.requiredPallets);
+      }
+      
+      await cb({helper, privateKey});
+    });
+  });
+}
+itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {only: true});
+itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {skip: true});
+itSub.ifWithPallets = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {requiredPallets: required});
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -101,10 +101,27 @@
   tokenId: number;
 }
 
+export interface IBlock {
+  extrinsics: IExtrinsic[]
+  header: {
+    parentHash: string,
+    number: number,
+  };
+}
+
+export interface IExtrinsic {
+  isSigned: boolean,
+  method: {
+    method: string,
+    section: string,
+    args: any[]
+  }
+}
+
 export interface ICollectionCreationOptions {
-  name: string | number[];
-  description: string | number[];
-  tokenPrefix: string | number[];
+  name?: string | number[];
+  description?: string | number[];
+  tokenPrefix?: string | number[];
   mode?: {
     nft?: null;
     refungible?: null;
@@ -123,8 +140,15 @@
   tokenSymbol: string[]
 }
 
+export interface ISubstrateBalance {
+  free: bigint,
+  reserved: bigint,
+  miscFrozen: bigint,
+  feeFrozen: bigint
+}
+
 export type TSubstrateAccount = string;
 export type TEthereumAccount = string;
 export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';
 export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';
-export type TSigner = IKeyringPair; // | 'string'
\ No newline at end of file
+export type TSigner = IKeyringPair; // | 'string'
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -5,22 +5,69 @@
 import {UniqueHelper} from './unique';
 import {ApiPromise, WsProvider} from '@polkadot/api';
 import * as defs from '../../interfaces/definitions';
-import {TSigner} from './types';
 import {IKeyringPair} from '@polkadot/types/types';
 
 
+export class SilentLogger {
+  log(_msg: any, _level: any): void { }
+  level = {
+    ERROR: 'ERROR' as const,
+    WARNING: 'WARNING' as const,
+    INFO: 'INFO' as const,
+  };
+}
+
+export class SilentConsole {
+  // TODO: Remove, this is temporary: Filter unneeded API output
+  // (Jaco promised it will be removed in the next version)
+  consoleErr: any;
+  consoleLog: any;
+  consoleWarn: any;
+
+  constructor() {
+    this.consoleErr = console.error;
+    this.consoleLog = console.log;
+    this.consoleWarn = console.warn;
+  }
+
+  enable() {  
+    const outFn = (printer: any) => (...args: any[]) => {
+      for (const arg of args) {
+        if (typeof arg !== 'string')
+          continue;
+        if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')
+          return;
+      }
+      printer(...args);
+    };
+  
+    console.error = outFn(this.consoleErr.bind(console));
+    console.log = outFn(this.consoleLog.bind(console));
+    console.warn = outFn(this.consoleWarn.bind(console));
+  }
+
+  disable() {
+    console.error = this.consoleErr;
+    console.log = this.consoleLog;
+    console.warn = this.consoleWarn;
+  }
+}
+
+
 export class DevUniqueHelper extends UniqueHelper {
   /**
    * Arrange methods for tests
    */
   arrange: ArrangeGroup;
+  wait: WaitGroup;
 
   constructor(logger: { log: (msg: any, level: any) => void, level: any }) {
     super(logger);
     this.arrange = new ArrangeGroup(this);
+    this.wait = new WaitGroup(this);
   }
 
-  async connect(wsEndpoint: string, listeners?: any): Promise<void> {
+  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
     const wsProvider = new WsProvider(wsEndpoint);
     this.api = new ApiPromise({
       provider: wsProvider,
@@ -36,6 +83,7 @@
       },
       rpc: {
         unique: defs.unique.rpc,
+        appPromotion: defs.appPromotion.rpc,
         rmrk: defs.rmrk.rpc,
         eth: {
           feeHistory: {
@@ -72,20 +120,21 @@
    */
   createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {
     let nonce = await this.helper.chain.getNonce(donor.address);
+    const ss58Format = this.helper.chain.getChainProperties().ss58Format;
     const tokenNominal = this.helper.balance.getOneTokenNominal();
     const transactions = [];
     const accounts: IKeyringPair[] = [];
     for (const balance of balances) {
-      const recepient = this.helper.util.fromSeed(mnemonicGenerate());
-      accounts.push(recepient);
+      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
+      accounts.push(recipient);
       if (balance !== 0n) {
-        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, balance * tokenNominal]);
+        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);
         transactions.push(this.helper.signTransaction(donor, tx, 'account generation', {nonce}));
         nonce++;
       }
     }
 
-    await Promise.all(transactions).catch(e => {});
+    await Promise.all(transactions).catch(_e => {});
     
     //#region TODO remove this region, when nonce problem will be solved
     const checkBalances = async () => {
@@ -105,7 +154,7 @@
     for (let index = 0; index < 5; index++) {
       accountsCreated = await checkBalances();
       if(accountsCreated) break;
-      await this.waitNewBlocks(1);
+      
     }
 
     if (!accountsCreated) throw Error('Accounts generation failed');
@@ -114,12 +163,86 @@
     return accounts;
   };
 
+  // TODO combine this method and createAccounts into one
+  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {  
+    const createAsManyAsCan = async () => {
+      let transactions: any = [];
+      const accounts: IKeyringPair[] = [];
+      let nonce = await this.helper.chain.getNonce(donor.address);
+      const tokenNominal = this.helper.balance.getOneTokenNominal();
+      for (let i = 0; i < accountsToCreate; i++) {
+        if (i === 500) { // if there are too many accounts to create
+          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled 
+          transactions = []; //
+          nonce = await this.helper.chain.getNonce(donor.address); // update nonce 
+        }
+        const recepient = this.helper.util.fromSeed(mnemonicGenerate());
+        accounts.push(recepient);
+        if (withBalance !== 0n) {
+          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]);
+          transactions.push(this.helper.signTransaction(donor, tx, 'account generation', {nonce}));
+          nonce++;
+        }
+      }
+      
+      const fullfilledAccounts = [];
+      await Promise.allSettled(transactions);
+      for (const account of accounts) {
+        const accountBalance = await this.helper.balance.getSubstrate(account.address);
+        if (accountBalance === withBalance * tokenNominal) {
+          fullfilledAccounts.push(account);
+        }
+      }
+      return fullfilledAccounts;
+    };
+
+    
+    const crowd: IKeyringPair[] = [];
+    // do up to 5 retries
+    for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {
+      const asManyAsCan = await createAsManyAsCan();
+      crowd.push(...asManyAsCan);
+      accountsToCreate -= asManyAsCan.length;
+    }
+
+    if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);
+
+    return crowd;
+  };
+
+  isDevNode = async () => {
+    const block1 = await this.helper.api?.rpc.chain.getBlock(await this.helper.api?.rpc.chain.getBlockHash(1));
+    const block2 = await this.helper.api?.rpc.chain.getBlock(await this.helper.api?.rpc.chain.getBlockHash(2));
+    const findCreationDate = async (block: any) => {
+      const humanBlock = block.toHuman();
+      let date;
+      humanBlock.block.extrinsics.forEach((ext: any) => {
+        if(ext.method.section === 'timestamp') {
+          date = Number(ext.method.args.now.replaceAll(',', ''));
+        }
+      });
+      return date;
+    };
+    const block1date = await findCreationDate(block1);
+    const block2date = await findCreationDate(block2);
+    if(block2date! - block1date! < 9000) return true;
+  };
+}
+
+class WaitGroup {
+  helper: UniqueHelper;
+
+  constructor(helper: UniqueHelper) {
+    this.helper = helper;
+  }
+
   /**
    * Wait for specified bnumber of blocks
    * @param blocksCount number of blocks to wait
    * @returns 
    */
-  async waitNewBlocks(blocksCount = 1): Promise<void> {
+  async newBlocks(blocksCount = 1): Promise<void> {
+    // eslint-disable-next-line no-async-promise-executor
     const promise = new Promise<void>(async (resolve) => {
       const unsubscribe = await this.helper.api!.rpc.chain.subscribeNewHeads(() => {
         if (blocksCount > 0) {
@@ -132,4 +255,27 @@
     });
     return promise;
   }
-}
\ No newline at end of file
+
+  async forParachainBlockNumber(blockNumber: bigint) {
+    return new Promise<void>(async (resolve) => {
+      const unsubscribe = await this.helper.api!.rpc.chain.subscribeNewHeads(async (data: any) => {
+        if (data.number.toNumber() >= blockNumber) {
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+  }
+  
+  async forRelayBlockNumber(blockNumber: bigint) {
+    return new Promise<void>(async (resolve) => {
+      const unsubscribe = await this.helper.api!.query.parachainSystem.validationData(async (data: any) => {
+        if (data.value.relayParentNumber.toNumber() >= blockNumber) {
+          // @ts-ignore
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+  }
+}
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -9,15 +9,14 @@
 import {ApiInterfaceEvents} from '@polkadot/api/types';
 import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
 import {IKeyringPair} from '@polkadot/types/types';
-import {IApiListeners, IChainEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';
+import {IApiListeners, IBlock, IChainEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';
 
-const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
+export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
   const address = {} as ICrossAccountId;
   if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;
   if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;
   return address;
 };
-
 
 const nesting = {
   toChecksumAddress(address: string): string {
@@ -91,9 +90,9 @@
     return encodeAddress(decodeAddress(address), ss58Format);
   }
 
-  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {
+  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult) {
     if (creationResult.status !== this.transactionStatus.SUCCESS) {
-      throw Error(`Unable to create collection for ${label}`);
+      throw Error('Unable to create collection!');
     }
 
     let collectionId = null;
@@ -104,15 +103,15 @@
     });
 
     if (collectionId === null) {
-      throw Error(`No CollectionCreated event for ${label}`);
+      throw Error('No CollectionCreated event was found!');
     }
 
     return collectionId;
   }
 
-  static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {
+  static extractTokensFromCreationResult(creationResult: ITransactionResult) {
     if (creationResult.status !== this.transactionStatus.SUCCESS) {
-      throw Error(`Unable to create tokens for ${label}`);
+      throw Error('Unable to create tokens!');
     }
     let success = false;
     const tokens = [] as any;
@@ -130,9 +129,9 @@
     return {success, tokens};
   }
 
-  static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {
+  static extractTokensFromBurnResult(burnResult: ITransactionResult) {
     if (burnResult.status !== this.transactionStatus.SUCCESS) {
-      throw Error(`Unable to burn tokens for ${label}`);
+      throw Error('Unable to burn tokens!');
     }
     let success = false;
     const tokens = [] as any;
@@ -150,7 +149,7 @@
     return {success, tokens};
   }
 
-  static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {
+  static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string) {
     let eventId = null;
     events.forEach(({event: {data, method, section}}) => {
       if ((section === expectedSection) && (method === expectedMethod)) {
@@ -159,7 +158,7 @@
     });
 
     if (eventId === null) {
-      throw Error(`No ${expectedMethod} event for ${label}`);
+      throw Error(`No ${expectedMethod} event was found!`);
     }
     return eventId === collectionId;
   }
@@ -317,6 +316,7 @@
       if(options !== null) return transaction.signAndSend(sender, options, callback);
       return transaction.signAndSend(sender, callback);
     };
+    // eslint-disable-next-line no-async-promise-executor
     return new Promise(async (resolve, reject) => {
       try {
         const unsub = await sign((result: any) => {
@@ -364,7 +364,7 @@
     return call(...params);
   }
 
-  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {
+  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false/*, failureMessage='expected success'*/) {
     if(this.api === null) throw Error('API not initialized');
     if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);
 
@@ -388,6 +388,7 @@
       type: this.chainLogType.EXTRINSIC,
       status: result.status,
       call: extrinsic,
+      signer: this.getSignerAddress(sender),
       params,
     } as IUniqueHelperLog;
 
@@ -396,7 +397,7 @@
 
     this.chainLog.push(log);
 
-    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);
+    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(`${result.moduleError}`);
     return result;
   }
 
@@ -438,6 +439,16 @@
     if(typeof signer === 'string') return signer;
     return signer.address;
   }
+
+  fetchAllPalletNames(): string[] {
+    if(this.api === null) throw Error('API not initialized');
+    return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());
+  }
+
+  fetchMissingPalletNames(requiredPallets: string[]): string[] {
+    const palletNames = this.fetchAllPalletNames();
+    return requiredPallets.filter(p => !palletNames.includes(p));
+  }
 }
 
 
@@ -474,7 +485,9 @@
   }
 
   /**
-   * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description.
+   * Get information about the collection with additional data,
+   * including the number of tokens it contains, its administrators,
+   * the normalized address of the collection's owner, and decoded name and description.
    *
    * @param collectionId ID of collection
    * @example await getData(2)
@@ -502,42 +515,50 @@
       collectionData[key] = this.helper.util.vec2str(humanCollection[key]);
     }
 
-    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;
+    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))
+      ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)
+      : 0;
     collectionData.admins = await this.getAdmins(collectionId);
 
     return collectionData;
   }
 
   /**
-   * Get the normalized addresses of the collection's administrators.
+   * Get the addresses of the collection's administrators, optionally normalized.
    *
    * @param collectionId ID of collection
+   * @param normalize whether to normalize the addresses to the default ss58 format
    * @example await getAdmins(1)
    * @returns array of administrators
    */
-  async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {
-    const normalized = [];
-    for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {
-      if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});
-      else normalized.push(admin);
-    }
-    return normalized;
+  async getAdmins(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {
+    const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();
+
+    return normalize
+      ? admins.map((address: any) => {
+        return address.Substrate
+          ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)}
+          : address;
+      })
+      : admins;
   }
 
   /**
-   * Get the normalized addresses added to the collection allow-list.
+   * Get the addresses added to the collection allow-list, optionally normalized.
    * @param collectionId ID of collection
+   * @param normalize whether to normalize the addresses to the default ss58 format
    * @example await getAllowList(1)
    * @returns array of allow-listed addresses
    */
-  async getAllowList(collectionId: number): Promise<ICrossAccountId[]> {
-    const normalized = [];
+  async getAllowList(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {
     const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();
-    for (const address of allowListed) {
-      if (address.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(address.Substrate)});
-      else normalized.push(address);
-    }
-    return normalized;
+    return normalize
+      ? allowListed.map((address: any) => {
+        return address.Substrate
+          ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)}
+          : address;
+      })
+      : allowListed;
   }
 
   /**
@@ -556,40 +577,36 @@
    *
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param label extra label for log
    * @example await helper.collection.burn(aliceKeyring, 3);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async burn(signer: TSigner, collectionId: number): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.destroyCollection', [collectionId],
-      true, `Unable to burn collection for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');
   }
 
   /**
-   * Sets the sponsor for the collection (Requires the Substrate address).
+   * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.
    *
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param sponsorAddress Sponsor substrate address
-   * @param label extra label for log
    * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],
-      true, `Unable to set collection sponsor for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet');
   }
 
   /**
@@ -597,19 +614,35 @@
    *
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param label extra label for log
    * @example confirmSponsorship(aliceKeyring, 10)
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.confirmSponsorship', [collectionId],
-      true, `Unable to confirm collection sponsorship for ${label}`,
+      true,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed');
+  }
+
+  /**
+   * Removes the sponsor of a collection, regardless if it consented or not.
+   *
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @example removeSponsor(aliceKeyring, 10)
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.removeCollectionSponsor', [collectionId],
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved');
   }
 
   /**
@@ -618,7 +651,6 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param limits collection limits object
-   * @param label extra label for log
    * @example
    * await setLimits(
    *   aliceKeyring,
@@ -630,15 +662,14 @@
    * )
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.setCollectionLimits', [collectionId, limits],
-      true, `Unable to set collection limits for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet');
   }
 
   /**
@@ -647,19 +678,17 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param ownerAddress substrate address of new owner
-   * @param label extra label for log
    * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],
-      true, `Unable to change collection owner for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged');
   }
 
   /**
@@ -668,80 +697,71 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param adminAddressObj Administrator address (substrate or ethereum)
-   * @param label extra label for log
    * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],
-      true, `Unable to add collection admin for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded');
   }
 
   /**
-   * Adds an address to allow list
+   * Removes a collection administrator.
+   *
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param addressObj address to add to the allow list
-   * @param label extra label for log
+   * @param adminAddressObj Administrator address (substrate or ethereum)
+   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
-      'api.tx.unique.addToAllowList', [collectionId, addressObj],
-      true, `Unable to add address to allow list for ${label}`,
+      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved');
   }
 
   /**
-   * Removes an address from allow list.
-   *
+   * Adds an address to allow list
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param addressObj address to be removed from allow list (substrate or ethereum)
-   * @param label extra label for log
-   * @example removeFromAllowList(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @param addressObj address to add to the allow list
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
-      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],
-      true, `Unable to remove address from allow list for ${label}`,
+      'api.tx.unique.addToAllowList', [collectionId, addressObj],
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');
   }
 
   /**
-   * Removes a collection administrator.
+   * Removes an address from allow list
    *
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param adminAddressObj Administrator address (substrate or ethereum)
-   * @param label extra label for log
-   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @param addressObj address to remove from the allow list
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
-      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],
-      true, `Unable to remove collection admin for ${label}`,
+      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved');
   }
 
   /**
@@ -750,19 +770,17 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param permissions collection permissions object
-   * @param label extra label for log
    * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.setCollectionPermissions', [collectionId, permissions],
-      true, `Unable to set collection permissions for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet');
   }
 
   /**
@@ -771,12 +789,11 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param permissions nesting permissions object
-   * @param label extra label for log
    * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {
-    return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);
+  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {
+    return await this.setPermissions(signer, collectionId, {nesting: permissions});
   }
 
   /**
@@ -784,12 +801,11 @@
    *
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param label extra label for log
    * @example disableNesting(aliceKeyring, 10);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {
-    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);
+  async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {
+    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});
   }
 
   /**
@@ -798,19 +814,17 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param properties array of property objects
-   * @param label extra label for log
    * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.setCollectionProperties', [collectionId, properties],
-      true, `Unable to set collection properties for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');
   }
 
   /**
@@ -819,19 +833,17 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param propertyKeys array of property keys to delete
-   * @param label
    * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],
-      true, `Unable to delete collection properties for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');
   }
 
   /**
@@ -849,7 +861,7 @@
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],
-      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,
+      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,
     );
 
     return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);
@@ -872,7 +884,7 @@
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],
-      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,
+      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,
     );
     return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);
   }
@@ -884,22 +896,20 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param label
    * @param amount amount of tokens to be burned. For NFT must be set to 1n
    * @example burnToken(aliceKeyring, 10, 5);
    * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```
    */
-  async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{
+  async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<{
     success: boolean,
     token: number | null
   }> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
     const burnResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.burnItem', [collectionId, tokenId, amount],
-      true, `Unable to burn token for ${label}`,
+      true, // `Unable to burn token for ${label}`,
     );
-    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);
+    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);
     if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');
     return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};
   }
@@ -911,19 +921,17 @@
    * @param collectionId ID of collection
    * @param fromAddressObj address on behalf of which the token will be burnt
    * @param tokenId ID of token
-   * @param label
    * @param amount amount of tokens to be burned. For NFT must be set to 1n
    * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, amount=1n): Promise<boolean> {
     const burnResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],
-      true, `Unable to burn token from for ${label}`,
+      true, // `Unable to burn token from for ${label}`,
     );
-    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);
+    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);
     return burnedTokens.success && burnedTokens.tokens.length > 0;
   }
 
@@ -933,28 +941,27 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param toAddressObj
-   * @param label
+   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens
    * @param amount amount of token to be approved. For NFT must be set to 1n
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {
     const approveResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],
-      true, `Unable to approve token for ${label}`,
+      true, // `Unable to approve token for ${label}`,
     );
 
-    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);
+    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');
   }
 
   /**
-   * Get the amount of token pieces approved to transfer
+   * Get the amount of token pieces approved to transfer or burn. Normally 0.
+   *
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param toAccountObj
-   * @param fromAccountObj
+   * @param toAccountObj address which is approved to use token pieces
+   * @param fromAccountObj address which may have allowed the use of its owned tokens
    * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})
    * @returns number of approved to transfer pieces
    */
@@ -963,7 +970,8 @@
   }
 
   /**
-   * Get the last created token id
+   * Get the last created token ID in a collection
+   *
    * @param collectionId ID of collection
    * @example getLastTokenId(10);
    * @returns id of the last created token
@@ -974,6 +982,7 @@
 
   /**
    * Check if token exists
+   *
    * @param collectionId ID of collection
    * @param tokenId ID of token
    * @example isTokenExists(10, 20);
@@ -999,14 +1008,15 @@
 
   /**
    * Get token data
+   *
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param blockHashAt
-   * @param propertyKeys
+   * @param propertyKeys optionally filter the token properties to only these keys
+   * @param blockHashAt optionally query the data at some block with this hash
    * @example getToken(10, 5);
    * @returns human readable token data
    */
-  async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{
+  async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{
     properties: IProperty[];
     owner: ICrossAccountId;
     normalizedOwner: ICrossAccountId;
@@ -1016,7 +1026,7 @@
       tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);
     }
     else {
-      if(typeof propertyKeys === 'undefined') {
+      if(propertyKeys.length == 0) {
         const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();
         if(!collection) return null;
         propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);
@@ -1035,45 +1045,43 @@
 
   /**
    * Set permissions to change token properties
+   *
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param permissions permissions to change a property by the collection owner or admin
-   * @param label
    * @example setTokenPropertyPermissions(
    *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]
    * )
    * @returns true if extrinsic success otherwise false
    */
-  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],
-      true, `Unable to set token property permissions for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');
   }
 
   /**
    * Set token properties
+   *
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param properties
-   * @param label
+   * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection
    * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;
+  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],
-      true, `Unable to set token properties for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');
   }
 
   /**
@@ -1082,31 +1090,29 @@
    * @param collectionId ID of collection
    * @param tokenId ID of token
    * @param propertyKeys property keys to be deleted
-   * @param label
    * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;
+  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {
     const result = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],
-      true, `Unable to delete token properties for ${label}`,
+      true,
     );
 
-    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');
   }
 
   /**
    * Mint new collection
+   *
    * @param signer keyring of signer
    * @param collectionOptions basic collection options and properties
    * @param mode NFT or RFT type of a collection
-   * @param errorLabel
    * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")
    * @returns object of the created collection
    */
-  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueCollectionBase> {
     collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
     collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};
     for (const key of ['name', 'description', 'tokenPrefix']) {
@@ -1115,16 +1121,16 @@
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createCollectionEx', [collectionOptions],
-      true, errorLabel,
+      true, // errorLabel,
     );
-    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));
+    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));
   }
 
-  getCollectionObject(collectionId: number): any {
+  getCollectionObject(_collectionId: number): any {
     return null;
   }
 
-  getTokenObject(collectionId: number, tokenId: number): any {
+  getTokenObject(_collectionId: number, _tokenId: number): any {
     return null;
   }
 }
@@ -1156,7 +1162,7 @@
    * Get token's owner
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param blockHashAt
+   * @param blockHashAt optionally query the data at the block with this hash
    * @example getTokenOwner(10, 5);
    * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}
    */
@@ -1238,7 +1244,7 @@
    * Get tokens nested in the provided token
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param blockHashAt
+   * @param blockHashAt optionally query the data at the block with this hash
    * @example getTokenChildren(10, 5);
    * @returns tokens whose depth of nesting is <= 5
    */
@@ -1260,15 +1266,14 @@
    * @param signer keyring of signer
    * @param tokenObj token to be nested
    * @param rootTokenObj token to be parent
-   * @param label
    * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {
+  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {
     const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};
     const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);
     if(!result) {
-      throw Error(`Unable to nest token for ${label}`);
+      throw Error('Unable to nest token!');
     }
     return result;
   }
@@ -1279,15 +1284,14 @@
    * @param tokenObj token to unnest
    * @param rootTokenObj parent of a token
    * @param toAddressObj address of a new token owner
-   * @param label
    * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {
+  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {
     const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};
     const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);
     if(!result) {
-      throw Error(`Unable to unnest token for ${label}`);
+      throw Error('Unable to unnest token!');
     }
     return result;
   }
@@ -1296,7 +1300,6 @@
    * Mint new collection
    * @param signer keyring of signer
    * @param collectionOptions Collection options
-   * @param label
    * @example
    * mintCollection(aliceKeyring, {
    *   name: 'New',
@@ -1305,19 +1308,17 @@
    * })
    * @returns object of the created collection
    */
-  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {
-    return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise<UniqueNFTCollection> {
+    return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;
   }
 
   /**
    * Mint new token
    * @param signer keyring of signer
    * @param data token data
-   * @param label
    * @returns created token object
    */
-  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {
-    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;
+  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFTToken> {
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {
@@ -1325,9 +1326,9 @@
           properties: data.properties,
         },
       }],
-      true, `Unable to mint NFT token for ${label}`,
+      true,
     );
-    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);
+    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);
     if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');
     if (createdTokens.tokens.length < 1) throw Error('No tokens minted');
     return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);
@@ -1338,7 +1339,6 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param tokens array of tokens with owner and properties
-   * @param label
    * @example
    * mintMultipleTokens(aliceKeyring, 10, [{
    *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},
@@ -1349,15 +1349,14 @@
    * }]);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFTToken[]> {
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],
-      true, `Unable to mint NFT tokens for ${label}`,
+      true,
     );
     const collection = this.getCollectionObject(collectionId);
-    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
   }
 
   /**
@@ -1366,7 +1365,6 @@
    * @param collectionId ID of collection
    * @param owner tokens owner
    * @param tokens array of tokens with owner and properties
-   * @param label
    * @example
    * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{
    *   properties: [{
@@ -1379,8 +1377,7 @@
    * }]);
    * @returns array of newly created tokens
    */
-  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFTToken[]> {
     const rawTokens = [];
     for (const token of tokens) {
       const raw = {NFT: {properties: token.properties}};
@@ -1389,23 +1386,10 @@
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],
-      true, `Unable to mint NFT tokens for ${label}`,
+      true,
     );
     const collection = this.getCollectionObject(collectionId);
-    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
-  }
-
-  /**
-   * Destroys a concrete instance of NFT.
-   * @param signer keyring of signer
-   * @param collectionId ID of collection
-   * @param tokenId ID of token
-   * @param label
-   * @example burnToken(aliceKeyring, 10, 5);
-   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```
-   */
-  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {
-    return await super.burnToken(signer, collectionId, tokenId, label, 1n);
+    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
   }
 
   /**
@@ -1415,12 +1399,11 @@
    * @param collectionId ID of collection
    * @param tokenId ID of token
    * @param toAddressObj address to approve
-   * @param label
    * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {
-    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);
+  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {
+    return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);
   }
 }
 
@@ -1480,7 +1463,7 @@
    * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {
+  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {
     return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);
   }
 
@@ -1495,7 +1478,7 @@
    * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {
+  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {
     return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);
   }
 
@@ -1503,7 +1486,6 @@
    * Mint new collection
    * @param signer keyring of signer
    * @param collectionOptions Collection options
-   * @param label
    * @example
    * mintCollection(aliceKeyring, {
    *   name: 'New',
@@ -1512,20 +1494,18 @@
    * })
    * @returns object of the created collection
    */
-  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {
-    return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise<UniqueRFTCollection> {
+    return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;
   }
 
   /**
    * Mint new token
    * @param signer keyring of signer
    * @param data token data
-   * @param label
    * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});
    * @returns created token object
    */
-  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {
-    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;
+  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFTToken> {
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {
@@ -1534,24 +1514,23 @@
           properties: data.properties,
         },
       }],
-      true, `Unable to mint RFT token for ${label}`,
+      true,
     );
-    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);
+    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);
     if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');
     if (createdTokens.tokens.length < 1) throw Error('No tokens minted');
     return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);
   }
 
-  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {
+  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFTToken[]> {
     throw Error('Not implemented');
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],
-      true, `Unable to mint RFT tokens for ${label}`,
+      true, // `Unable to mint RFT tokens for ${label}`,
     );
     const collection = this.getCollectionObject(collectionId);
-    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
   }
 
   /**
@@ -1560,12 +1539,10 @@
    * @param collectionId ID of collection
    * @param owner tokens owner
    * @param tokens array of tokens with properties and pieces
-   * @param label
    * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);
    * @returns array of newly created RFT tokens
    */
-  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFTToken[]> {
     const rawTokens = [];
     for (const token of tokens) {
       const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};
@@ -1574,10 +1551,10 @@
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],
-      true, `Unable to mint RFT tokens for ${label}`,
+      true,
     );
     const collection = this.getCollectionObject(collectionId);
-    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
   }
 
   /**
@@ -1585,13 +1562,12 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param tokenId ID of token
-   * @param label
    * @param amount number of pieces to be burnt
    * @example burnToken(aliceKeyring, 10, 5);
    * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```
    */
-  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {
-    return await super.burnToken(signer, collectionId, tokenId, label, amount);
+  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<{ success: boolean; token: number | null; }> {
+    return await super.burnToken(signer, collectionId, tokenId, amount);
   }
 
   /**
@@ -1601,13 +1577,12 @@
    * @param collectionId ID of collection
    * @param tokenId ID of token
    * @param toAddressObj address to approve
-   * @param label
    * @param amount number of pieces to be approved
    * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);
    * @returns true if the token success, otherwise false
    */
-  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {
-    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);
+  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {
+    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);
   }
 
   /**
@@ -1627,20 +1602,18 @@
    * @param collectionId ID of collection
    * @param tokenId ID of token
    * @param amount new number of pieces
-   * @param label
    * @example repartitionToken(aliceKeyring, 10, 5, 12345n);
    * @returns true if the repartion was success, otherwise false
    */
-  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {
     const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);
     const repartitionResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.repartition', [collectionId, tokenId, amount],
-      true, `Unable to repartition RFT token for ${label}`,
+      true,
     );
-    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);
-    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);
+    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');
+    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');
   }
 }
 
@@ -1661,7 +1634,6 @@
    * @param signer keyring of signer
    * @param collectionOptions Collection options
    * @param decimalPoints number of token decimals
-   * @param errorLabel
    * @example
    * mintCollection(aliceKeyring, {
    *   name: 'New',
@@ -1670,7 +1642,7 @@
    * }, 18)
    * @returns newly created fungible collection
    */
-  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0): Promise<UniqueFTCollection> {
     collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
     if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');
     collectionOptions.mode = {fungible: decimalPoints};
@@ -1680,9 +1652,9 @@
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createCollectionEx', [collectionOptions],
-      true, errorLabel,
+      true,
     );
-    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));
+    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));
   }
 
   /**
@@ -1691,12 +1663,10 @@
    * @param collectionId ID of collection
    * @param owner address owner of new tokens
    * @param amount amount of tokens to be meanted
-   * @param label
    * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint): Promise<boolean> {
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {
@@ -1704,9 +1674,9 @@
           value: amount,
         },
       }],
-      true, `Unable to mint fungible tokens for ${label}`,
+      true, // `Unable to mint fungible tokens for ${label}`,
     );
-    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);
+    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');
   }
 
   /**
@@ -1715,11 +1685,9 @@
    * @param collectionId ID of collection
    * @param owner tokens owner
    * @param tokens array of tokens with properties and pieces
-   * @param label
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {
-    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[]): Promise<boolean> {
     const rawTokens = [];
     for (const token of tokens) {
       const raw = {Fungible: {Value: token.value}};
@@ -1728,9 +1696,9 @@
     const creationResult = await this.helper.executeExtrinsic(
       signer,
       'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],
-      true, `Unable to mint RFT tokens for ${label}`,
+      true,
     );
-    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);
+    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');
   }
 
   /**
@@ -1758,12 +1726,12 @@
    * Transfer tokens to address
    * @param signer keyring of signer
    * @param collectionId ID of collection
-   * @param toAddressObj address recepient
+   * @param toAddressObj address recipient
    * @param amount amount of tokens to be sent
    * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {
+  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {
     return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);
   }
 
@@ -1777,7 +1745,7 @@
    * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {
+  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {
     return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);
   }
 
@@ -1786,12 +1754,11 @@
    * @param signer keyring of signer
    * @param collectionId ID of collection
    * @param amount amount of tokens to be destroyed
-   * @param label
    * @example burnTokens(aliceKeyring, 10, 1000n);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {
-    return (await super.burnToken(signer, collectionId, 0, label, amount)).success;
+  async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {
+    return (await super.burnToken(signer, collectionId, 0, amount)).success;
   }
 
   /**
@@ -1800,12 +1767,11 @@
    * @param collectionId ID of collection
    * @param fromAddressObj address on behalf of which tokens will be burnt
    * @param amount amount of tokens to be burnt
-   * @param label
    * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {
-    return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);
+  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {
+    return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, amount);
   }
 
   /**
@@ -1824,12 +1790,11 @@
    * @param collectionId ID of collection
    * @param toAddressObj address to be approved
    * @param amount amount of tokens to be approved
-   * @param label
    * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
-  async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
-    return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);
+  async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {
+    return super.approveToken(signer, collectionId, 0, toAddressObj, amount);
   }
 
   /**
@@ -1881,6 +1846,13 @@
     return blockHash;
   }
 
+  // TODO add docs
+  async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {
+    const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);
+    if (!blockHash) return null;
+    return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;
+  }
+
   /**
    * Get account nonce
    * @param address substrate address
@@ -1915,6 +1887,16 @@
   }
 
   /**
+   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved
+   * @param address substrate address
+   * @returns
+   */
+  async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {
+    const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;
+    return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
+  }
+
+  /**
    * Get ethereum address balance
    * @param address ethereum address
    * @example getEthereum("0x9F0583DbB855d...")
@@ -1927,13 +1909,13 @@
   /**
    * Transfer tokens to substrate address
    * @param signer keyring of signer
-   * @param address substrate address of a recepient
+   * @param address substrate address of a recipient
    * @param amount amount of tokens to be transfered
    * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);
    * @returns ```true``` if extrinsic success, otherwise ```false```
    */
   async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {
-    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`);
+    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);
 
     let transfer = {from: null, to: null, amount: 0n} as any;
     result.result.events.forEach(({event: {data, method, section}}) => {
@@ -2000,7 +1982,59 @@
   }
 }
 
+class StakingGroup extends HelperGroup {
+  /**
+   * Stake tokens for App Promotion
+   * @param signer keyring of signer
+   * @param amountToStake amount of tokens to stake
+   * @param label extra label for log
+   * @returns
+   */
+  async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;
+    const stakeResult = await this.helper.executeExtrinsic(
+      signer, 'api.tx.appPromotion.stake',
+      [amountToStake], true,
+    );
+    // TODO extract info from stakeResult
+    return true;
+  }
 
+  /**
+   * Unstake tokens for App Promotion
+   * @param signer keyring of signer
+   * @param amountToUnstake amount of tokens to unstake
+   * @param label extra label for log
+   * @returns block number where balances will be unlocked
+   */
+  async unstake(signer: TSigner, label?: string): Promise<number> {
+    if(typeof label === 'undefined') label = `${signer.address}`;
+    const unstakeResult = await this.helper.executeExtrinsic(
+      signer, 'api.tx.appPromotion.unstake',
+      [], true,
+    );
+    // TODO extract block number fron events
+    return 1;
+  }
+
+  async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {
+    if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();
+    return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();
+  }
+
+  async getTotalStakedPerBlock(address: ICrossAccountId): Promise<bigint[][]> {
+    return (await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address])).map(([block, amount]: any[]) => [block.toBigInt(), amount.toBigInt()]);
+  }
+
+  async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();
+  }
+
+  async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<bigint[][]> {
+    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address])).map(([block, amount]: any[]) => [block.toBigInt(), amount.toBigInt()]);
+  }
+}
+
 export class UniqueHelper extends ChainHelperBase {
   chain: ChainGroup;
   balance: BalanceGroup;
@@ -2009,6 +2043,7 @@
   nft: NFTGroup;
   rft: RFTGroup;
   ft: FTGroup;
+  staking: StakingGroup;
 
   constructor(logger?: ILogger) {
     super(logger);
@@ -2019,6 +2054,7 @@
     this.nft = new NFTGroup(this);
     this.rft = new RFTGroup(this);
     this.ft = new FTGroup(this);
+    this.staking = new StakingGroup(this);
   }
 }
 
@@ -2056,64 +2092,72 @@
     return await this.helper.collection.getEffectiveLimits(this.collectionId);
   }
 
-  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {
-    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);
+  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {
+    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);
   }
 
-  async confirmSponsorship(signer: TSigner, label?: string) {
-    return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);
+  async confirmSponsorship(signer: TSigner) {
+    return await this.helper.collection.confirmSponsorship(signer, this.collectionId);
   }
 
-  async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {
-    return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);
+  async removeSponsor(signer: TSigner) {
+    return await this.helper.collection.removeSponsor(signer, this.collectionId);
   }
 
-  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {
-    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);
+  async setLimits(signer: TSigner, limits: ICollectionLimits) {
+    return await this.helper.collection.setLimits(signer, this.collectionId, limits);
+  }
+
+  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {
+    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);
+  }
+
+  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {
+    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);
   }
 
-  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {
-    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);
+  async enableCertainPermissions(signer: TSigner, accessMode: 'AllowList' | 'Normal' | undefined = 'AllowList', mintMode: boolean | undefined = true) {
+    return await this.setPermissions(signer, {access: accessMode, mintMode: mintMode});
   }
 
-  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId, label?: string) {
-    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj, label);
+  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {
+    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);
   }
 
-  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId, label?: string) {
-    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj, label);
+  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {
+    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);
   }
 
-  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {
-    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);
+  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {
+    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);
   }
 
-  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {
-    return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);
+  async setProperties(signer: TSigner, properties: IProperty[]) {
+    return await this.helper.collection.setProperties(signer, this.collectionId, properties);
   }
 
-  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {
-    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);
+  async deleteProperties(signer: TSigner, propertyKeys: string[]) {
+    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);
   }
 
   async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
     return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
   }
 
-  async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {
-    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);
+  async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {
+    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);
   }
 
-  async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {
-    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);
+  async enableNesting(signer: TSigner, permissions: INestingPermissions) {
+    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);
   }
 
-  async disableNesting(signer: TSigner, label?: string) {
-    return await this.helper.collection.disableNesting(signer, this.collectionId, label);
+  async disableNesting(signer: TSigner) {
+    return await this.helper.collection.disableNesting(signer, this.collectionId);
   }
 
-  async burn(signer: TSigner, label?: string) {
-    return await this.helper.collection.burn(signer, this.collectionId, label);
+  async burn(signer: TSigner) {
+    return await this.helper.collection.burn(signer, this.collectionId);
   }
 }
 
@@ -2128,7 +2172,7 @@
   }
 
   async getToken(tokenId: number, blockHashAt?: string) {
-    return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);
+    return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);
   }
 
   async getTokenOwner(tokenId: number, blockHashAt?: string) {
@@ -2151,44 +2195,44 @@
     return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);
   }
 
-  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {
-    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);
+  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {
+    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);
   }
 
   async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {
     return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);
   }
 
-  async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {
-    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);
+  async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[]) {
+    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});
   }
 
-  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {
-    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);
+  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {
+    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);
   }
 
-  async burnToken(signer: TSigner, tokenId: number, label?: string) {
-    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);
+  async burnToken(signer: TSigner, tokenId: number) {
+    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);
   }
 
-  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {
-    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);
+  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {
+    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);
   }
 
-  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {
-    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);
+  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {
+    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);
   }
 
-  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {
-    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);
+  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {
+    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);
   }
 
-  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {
-    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);
+  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {
+    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);
   }
 
-  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {
-    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);
+  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
+    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
   }
 }
 
@@ -2214,59 +2258,59 @@
     return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);
   }
 
-  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {
+  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {
     return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);
   }
 
-  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {
+  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {
     return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);
   }
 
-  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
-    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);
+  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {
+    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);
   }
 
   async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
     return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);
   }
 
-  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {
-    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);
+  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {
+    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);
   }
 
-  async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {
-    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);
+  async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[]) {
+    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});
   }
 
-  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {
-    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);
+  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]) {
+    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);
   }
 
-  async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {
-    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);
+  async burnToken(signer: TSigner, tokenId: number, amount=1n) {
+    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);
   }
 
-  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {
-    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);
+  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {
+    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);
   }
 
-  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {
-    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);
+  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {
+    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);
   }
 
-  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {
-    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);
+  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {
+    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);
   }
 }
 
 
 class UniqueFTCollection extends UniqueCollectionBase {
-  async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {
-    return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);
+  async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint) {
+    return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount);
   }
 
-  async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {
-    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);
+  async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[]) {
+    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens);
   }
 
   async getBalance(addressObj: ICrossAccountId) {
@@ -2277,28 +2321,28 @@
     return await this.helper.ft.getTop10Owners(this.collectionId);
   }
 
-  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {
+  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {
     return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);
   }
 
-  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {
+  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {
     return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);
   }
 
-  async burnTokens(signer: TSigner, amount: bigint, label?: string) {
-    return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);
+  async burnTokens(signer: TSigner, amount=1n) {
+    return await this.helper.ft.burnTokens(signer, this.collectionId, amount);
   }
 
-  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {
-    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);
+  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {
+    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);
   }
 
   async getTotalPieces() {
     return await this.helper.ft.getTotalPieces(this.collectionId);
   }
 
-  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
-    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);
+  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {
+    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);
   }
 
   async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
@@ -2322,12 +2366,12 @@
     return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);
   }
 
-  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {
-    return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);
+  async setProperties(signer: TSigner, properties: IProperty[]) {
+    return await this.collection.setTokenProperties(signer, this.tokenId, properties);
   }
 
-  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {
-    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);
+  async deleteProperties(signer: TSigner, propertyKeys: string[]) {
+    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);
   }
 }
 
@@ -2356,12 +2400,12 @@
     return await this.collection.getTokenChildren(this.tokenId, blockHashAt);
   }
 
-  async nest(signer: TSigner, toTokenObj: IToken, label?: string) {
-    return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);
+  async nest(signer: TSigner, toTokenObj: IToken) {
+    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);
   }
 
-  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {
-    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);
+  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
+    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);
   }
 
   async transfer(signer: TSigner, addressObj: ICrossAccountId) {
@@ -2372,16 +2416,16 @@
     return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);
   }
 
-  async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {
-    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);
+  async approve(signer: TSigner, toAddressObj: ICrossAccountId) {
+    return await this.collection.approveToken(signer, this.tokenId, toAddressObj);
   }
 
   async isApproved(toAddressObj: ICrossAccountId) {
     return await this.collection.isTokenApproved(this.tokenId, toAddressObj);
   }
 
-  async burn(signer: TSigner, label?: string) {
-    return await this.collection.burnToken(signer, this.tokenId, label);
+  async burn(signer: TSigner) {
+    return await this.collection.burnToken(signer, this.tokenId);
   }
 }
 
@@ -2405,27 +2449,27 @@
     return await this.collection.getTokenTotalPieces(this.tokenId);
   }
 
-  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {
+  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {
     return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);
   }
 
-  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {
+  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {
     return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);
   }
 
-  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
-    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);
+  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {
+    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);
   }
 
   async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {
     return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);
   }
 
-  async repartition(signer: TSigner, amount: bigint, label?: string) {
-    return await this.collection.repartitionToken(signer, this.tokenId, amount, label);
+  async repartition(signer: TSigner, amount: bigint) {
+    return await this.collection.repartitionToken(signer, this.tokenId, amount);
   }
 
-  async burn(signer: TSigner, amount=100n, label?: string) {
-    return await this.collection.burnToken(signer, this.tokenId, amount, label);
+  async burn(signer: TSigner, amount=1n) {
+    return await this.collection.burnToken(signer, this.tokenId, amount);
   }
 }