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

difftreelog

features: added app-promotion pallet prototype

PraetorP2022-08-10parent: #6c48590.patch.diff
in: master

14 files changed

modifiedCargo.lockdiffbeforeafterboth
before · Cargo.lock
967 packageslockfile v3
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/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,
@@ -41,7 +42,7 @@
 
 #[rpc(server)]
 #[async_trait]
-pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {
+pub trait UniqueApi<BlockHash, BlockNumber, CrossAccountId, AccountId> {
 	/// Get tokens owned by account.
 	#[method(name = "unique_accountTokens")]
 	fn account_tokens(
@@ -242,7 +243,23 @@
 		collection_id: CollectionId,
 		token_id: TokenId,
 		at: Option<BlockHash>,
-	) -> Result<Option<String>>;
+	) -> Result<Option<u128>>;
+
+	/// Returns the total amount of staked tokens.
+	#[method(name = "unique_totalStaked")]
+	fn total_staked(&self, staker: CrossAccountId, at: Option<BlockHash>) -> Result<u128>;
+
+	///Returns the total amount of staked tokens per block when staked.
+	#[method(name = "unique_totalStakedPerBlock")]
+	fn total_staked_per_block(
+		&self,
+		staker: CrossAccountId,
+		at: Option<BlockHash>,
+	) -> Result<Vec<(BlockNumber, u128)>>;
+
+	/// Return the total amount locked by staking tokens.
+	#[method(name = "unique_totalStakingLocked")]
+	fn total_staking_locked(&self, staker: CrossAccountId, at: Option<BlockHash>) -> Result<u128>;
 }
 
 mod rmrk_unique_rpc {
@@ -436,7 +453,7 @@
 
 macro_rules! unique_api {
 	() => {
-		dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>
+		dyn UniqueRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>
 	};
 }
 
@@ -447,13 +464,15 @@
 }
 
 #[allow(deprecated)]
-impl<C, Block, CrossAccountId, AccountId>
-	UniqueApiServer<<Block as BlockT>::Hash, CrossAccountId, AccountId> for Unique<C, Block>
+impl<C, Block, BlockNumber, CrossAccountId, AccountId>
+	UniqueApiServer<<Block as BlockT>::Hash, BlockNumber, CrossAccountId, AccountId>
+	for Unique<C, Block>
 where
 	Block: BlockT,
+	BlockNumber: Decode + Member + AtLeast32BitUnsigned,
 	AccountId: Decode,
 	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
-	C::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,
+	C::Api: UniqueRuntimeApi<Block, BlockNumber, CrossAccountId, AccountId>,
 	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,
 {
 	pass_method!(
@@ -520,6 +539,9 @@
 	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);
 	pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<String> => |o| o.map(|number| number.to_string()) , unique_api);
 	pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
+	pass_method!(total_staked(staker: CrossAccountId) -> u128, unique_api);
+	pass_method!(total_staked_per_block(staker: CrossAccountId) -> Vec<(BlockNumber, u128)>, unique_api);
+	pass_method!(total_staking_locked(staker: CrossAccountId) -> u128, unique_api);
 }
 
 #[allow(deprecated)]
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 unique_runtime_common::types::{
+	AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block, BlockNumber,
+};
 
 // RMRK
 use up_data_structs::{
@@ -361,7 +363,7 @@
 		+ sp_block_builder::BlockBuilder<Block>
 		+ 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>
+		+ up_rpc::UniqueApi<Block, BlockNumber, Runtime::CrossAccountId, AccountId>
 		+ rmrk_rpc::RmrkApi<
 			Block,
 			AccountId,
@@ -662,7 +664,7 @@
 		+ sp_block_builder::BlockBuilder<Block>
 		+ 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>
+		+ up_rpc::UniqueApi<Block, BlockNumber, Runtime::CrossAccountId, AccountId>
 		+ rmrk_rpc::RmrkApi<
 			Block,
 			AccountId,
@@ -806,7 +808,7 @@
 		+ sp_block_builder::BlockBuilder<Block>
 		+ 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>
+		+ up_rpc::UniqueApi<Block, BlockNumber, Runtime::CrossAccountId, AccountId>
 		+ rmrk_rpc::RmrkApi<
 			Block,
 			AccountId,
modifiednode/rpc/src/lib.rsdiffbeforeafterboth
--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -100,7 +100,8 @@
 	C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError>,
 	C: Send + Sync + 'static,
 	C::Api: fp_rpc::EthereumRuntimeRPCApi<Block>,
-	C::Api: up_rpc::UniqueApi<Block, <R as RuntimeInstance>::CrossAccountId, AccountId>,
+	C::Api:
+		up_rpc::UniqueApi<Block, BlockNumber, <R as RuntimeInstance>::CrossAccountId, AccountId>,
 	BE: Backend<Block> + 'static,
 	BE::State: StateBackend<BlakeTwo256>,
 	R: RuntimeInstance + Send + Sync + 'static,
@@ -144,7 +145,8 @@
 	C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>,
 	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:
+		up_rpc::UniqueApi<Block, BlockNumber, <R as RuntimeInstance>::CrossAccountId, AccountId>,
 	C::Api: rmrk_rpc::RmrkApi<
 		Block,
 		AccountId,
addedpallets/app-promotion/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/Cargo.toml
@@ -0,0 +1,110 @@
+################################################################################
+# 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',
+]
+std = [
+    'codec/std',
+    'serde/std',
+    'frame-support/std',
+    'frame-system/std',
+    'pallet-balances/std',
+    'pallet-timestamp/std',
+    'pallet-randomness-collective-flip/std',
+    'sp-std/std',
+    'sp-runtime/std',
+    'frame-benchmarking/std',
+]
+
+################################################################################
+# Substrate Dependencies
+
+[dependencies.codec]
+default-features = false
+features = ['derive']
+package = 'parity-scale-codec'
+version = '3.1.2'
+
+[dependencies.frame-benchmarking]
+default-features = false
+optional = true
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.frame-support]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.frame-system]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.pallet-balances]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.pallet-timestamp]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.pallet-randomness-collective-flip]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.sp-std]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.serde]
+default-features = false
+features = ['derive']
+version = '1.0.130'
+
+[dependencies.sp-runtime]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.sp-core]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.sp-io]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.pallet-evm]
+default-features = false
+git = "https://github.com/uniquenetwork/frontier"
+branch = "unique-polkadot-v0.9.24"
+
+[dependencies]
+scale-info = { version = "2.0.1", default-features = false, features = [
+    "derive",
+] }
addedpallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/benchmarking.rs
@@ -0,0 +1,72 @@
+// 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 frame_benchmarking::{benchmarks, account};
+use frame_support::traits::OnInitialize;
+use frame_system::{Origin, RawOrigin};
+
+const SEED: u32 = 0;
+benchmarks! {
+	where_clause{
+		where T: Config
+
+	}
+	on_initialize {
+		let block1: T::BlockNumber = T::BlockNumber::from(1u32);
+		let block2: T::BlockNumber = T::BlockNumber::from(2u32);
+		PromototionPallet::<T>::on_initialize(block1); // Create Treasury account
+	}: { PromototionPallet::<T>::on_initialize(block2); } // Benchmark deposit_into_existing path
+
+	start_app_promotion {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+
+	} : {PromototionPallet::<T>::start_app_promotion(RawOrigin::Root.into(), T::BlockNumber::from(2u32))?}
+
+	set_admin_address {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+		let _ = T::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+	} : {PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), caller)?}
+
+	stake {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+		let share = Perbill::from_rational(1u32, 10);
+		let _ = T::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+	} : {PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * T::Currency::total_balance(&caller))?}
+
+	unstake {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+		let share = Perbill::from_rational(1u32, 10);
+		let _ = T::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let _ = PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * T::Currency::total_balance(&caller))?;
+
+	} : {PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * T::Currency::total_balance(&caller))?}
+
+	recalculate_stake {
+		let caller = account::<T::AccountId>("caller", 0, SEED);
+		let share = Perbill::from_rational(1u32, 10);
+		let _ = T::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		let _ = PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * T::Currency::total_balance(&caller))?;
+		let block = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();
+		let mut acc = <BalanceOf<T>>::default();
+	} : {PromototionPallet::<T>::recalculate_stake(&caller, block, share * T::Currency::total_balance(&caller), &mut acc)}
+}
addedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/lib.rs
@@ -0,0 +1,494 @@
+// 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
+//!
+//! * `start_inflation` - This method sets the inflation start date. Can be only called once.
+//! Inflation start block can be backdated and will catch up. The method will create Treasury
+//!	account if it does not exist and perform the first inflation deposit.
+
+// #![recursion_limit = "1024"]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;
+pub mod types;
+
+#[cfg(test)]
+mod tests;
+
+use sp_std::vec::Vec;
+use codec::EncodeLike;
+use pallet_balances::BalanceLock;
+pub use types::ExtendedLockableCurrency;
+
+use frame_support::{
+	dispatch::{DispatchResult},
+	traits::{
+		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,
+	},
+	ensure,
+};
+pub use pallet::*;
+use pallet_evm::account::CrossAccountId;
+use sp_runtime::{
+	Perbill,
+	traits::{BlockNumberProvider, CheckedAdd, CheckedSub},
+	ArithmeticError,
+};
+
+type BalanceOf<T> =
+	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+
+const SECONDS_TO_BLOCK: u32 = 6;
+const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;
+const WEEK: u32 = 7 * DAY;
+const TWO_WEEK: u32 = 2 * WEEK;
+const YEAR: u32 = DAY * 365;
+
+pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};
+	use frame_system::pallet_prelude::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_evm::account::Config {
+		type Currency: ExtendedLockableCurrency<Self::AccountId>;
+
+		type TreasuryAccountId: Get<Self::AccountId>;
+
+		// The block number provider
+		type BlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;
+
+		// /// Number of blocks that pass between treasury balance updates due to inflation
+		// #[pallet::constant]
+		// type InterestBlockInterval: Get<Self::BlockNumber>;
+
+		// // Weight information for functions of this pallet.
+		// type WeightInfo: WeightInfo;
+	}
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	pub struct Pallet<T>(_);
+
+	#[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>,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub type PendingUnstake<T: Config> = StorageNMap<
+		Key = (
+			Key<Blake2_128Concat, T::AccountId>,
+			Key<Twox64Concat, T::BlockNumber>,
+		),
+		Value = BalanceOf<T>,
+		QueryKind = ValueQuery,
+	>;
+
+	/// A block when app-promotion has started
+	#[pallet::storage]
+	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;
+
+	/// Next target block when interest is recalculated
+	#[pallet::storage]
+	#[pallet::getter(fn get_interest_block)]
+	pub type NextInterestBlock<T: Config> =
+		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		fn on_initialize(current_block: T::BlockNumber) -> Weight
+		where
+			<T as frame_system::Config>::BlockNumber: From<u32>,
+		{
+			PendingUnstake::<T>::iter()
+				.filter_map(|((staker, block), amount)| {
+					if block <= current_block {
+						Some((staker, block, amount))
+					} else {
+						None
+					}
+				})
+				.for_each(|(staker, block, amount)| {
+					Self::unlock_balance_unchecked(&staker, amount); // TO-DO : Replace with a method that will check that the unstack is less than it was blocked, otherwise take the delta from the treasuries
+					<PendingUnstake<T>>::remove((staker, block));
+				});
+
+			let next_interest_block = Self::get_interest_block();
+
+			if next_interest_block != 0.into() && current_block >= next_interest_block {
+				let mut acc = <BalanceOf<T>>::default();
+				let mut weight: Weight = 0;
+				NextInterestBlock::<T>::set(current_block + DAY.into());
+				Staked::<T>::iter()
+					.filter(|((_, block), _)| *block + DAY.into() <= current_block)
+					.for_each(|((staker, block), amount)| {
+						Self::recalculate_stake(&staker, block, amount, &mut acc);
+						// weight += recalculate_stake();
+					});
+				<TotalStaked<T>>::get()
+					.checked_add(&acc)
+					.map(|res| <TotalStaked<T>>::set(res));
+			};
+			0
+		}
+	}
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		#[pallet::weight(0)]
+		pub fn set_admin_address(origin: OriginFor<T>, admin: T::AccountId) -> DispatchResult {
+			ensure_root(origin)?;
+			<Admin<T>>::set(Some(admin));
+
+			Ok(())
+		}
+
+		#[pallet::weight(0)]
+		pub fn start_app_promotion(
+			origin: OriginFor<T>,
+			promotion_start_relay_block: T::BlockNumber,
+		) -> DispatchResult
+		where
+			<T as frame_system::Config>::BlockNumber: From<u32>,
+		{
+			ensure_root(origin)?;
+
+			// Start app-promotion mechanics if it has not been yet initialized
+			if <StartBlock<T>>::get() == 0u32.into() {
+				// Set promotion global start block
+				<StartBlock<T>>::set(promotion_start_relay_block);
+
+				<NextInterestBlock<T>>::set(promotion_start_relay_block + DAY.into());
+			}
+
+			Ok(())
+		}
+
+		#[pallet::weight(0)]
+		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
+			let staker_id = ensure_signed(staker)?;
+
+			let balance =
+				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);
+
+			ensure!(balance >= amount, ArithmeticError::Underflow);
+
+			Self::set_lock_unchecked(&staker_id, amount);
+
+			let block_number =
+				<T::BlockNumberProvider as BlockNumberProvider>::current_block_number();
+
+			<Staked<T>>::insert(
+				(&staker_id, block_number),
+				<Staked<T>>::get((&staker_id, block_number))
+					.checked_add(&amount)
+					.ok_or(ArithmeticError::Overflow)?,
+			);
+
+			<TotalStaked<T>>::set(
+				<TotalStaked<T>>::get()
+					.checked_add(&amount)
+					.ok_or(ArithmeticError::Overflow)?,
+			);
+
+			Ok(())
+		}
+
+		#[pallet::weight(0)]
+		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
+			let staker_id = ensure_signed(staker)?;
+
+			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();
+
+			let total_staked = stakes
+				.iter()
+				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);
+
+			ensure!(total_staked >= amount, ArithmeticError::Underflow);
+
+			<TotalStaked<T>>::set(
+				<TotalStaked<T>>::get()
+					.checked_sub(&amount)
+					.ok_or(ArithmeticError::Underflow)?,
+			);
+
+			let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();
+			<PendingUnstake<T>>::insert(
+				(&staker_id, block),
+				<PendingUnstake<T>>::get((&staker_id, block))
+					.checked_add(&amount)
+					.ok_or(ArithmeticError::Overflow)?,
+			);
+
+			stakes.sort_by_key(|(block, _)| *block);
+
+			let mut acc_amount = amount;
+			let new_state = stakes
+				.into_iter()
+				.map_while(|(block, balance_per_block)| {
+					if acc_amount == <BalanceOf<T>>::default() {
+						return None;
+					}
+					if acc_amount <= balance_per_block {
+						let res = (block, balance_per_block - acc_amount, acc_amount);
+						acc_amount = <BalanceOf<T>>::default();
+						return Some(res);
+					} else {
+						acc_amount -= balance_per_block;
+						return Some((block, <BalanceOf<T>>::default(), acc_amount));
+					}
+				})
+				.collect::<Vec<_>>();
+
+			new_state
+				.into_iter()
+				.for_each(|(block, to_staked, _to_pending)| {
+					if to_staked == <BalanceOf<T>>::default() {
+						<Staked<T>>::remove((&staker_id, block));
+					} else {
+						<Staked<T>>::insert((&staker_id, block), to_staked);
+					}
+				});
+
+			Ok(())
+		}
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);
+
+	// 	ensure!(balance >= amount, ArithmeticError::Underflow);
+
+	// 	Self::set_lock_unchecked(staker, amount);
+
+	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();
+
+	// 	<Staked<T>>::insert(
+	// 		(staker, block_number),
+	// 		<Staked<T>>::get((staker, block_number))
+	// 			.checked_add(&amount)
+	// 			.ok_or(ArithmeticError::Overflow)?,
+	// 	);
+
+	// 	<TotalStaked<T>>::set(
+	// 		<TotalStaked<T>>::get()
+	// 			.checked_add(&amount)
+	// 			.ok_or(ArithmeticError::Overflow)?,
+	// 	);
+
+	// 	Ok(())
+	// }
+
+	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();
+
+	// 	let total_staked = stakes
+	// 		.iter()
+	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);
+
+	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);
+
+	// 	<TotalStaked<T>>::set(
+	// 		<TotalStaked<T>>::get()
+	// 			.checked_sub(&amount)
+	// 			.ok_or(ArithmeticError::Underflow)?,
+	// 	);
+
+	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();
+	// 	<PendingUnstake<T>>::insert(
+	// 		(staker, block),
+	// 		<PendingUnstake<T>>::get((staker, block))
+	// 			.checked_add(&amount)
+	// 			.ok_or(ArithmeticError::Overflow)?,
+	// 	);
+
+	// 	stakes.sort_by_key(|(block, _)| *block);
+
+	// 	let mut acc_amount = amount;
+	// 	let new_state = stakes
+	// 		.into_iter()
+	// 		.map_while(|(block, balance_per_block)| {
+	// 			if acc_amount == <BalanceOf<T>>::default() {
+	// 				return None;
+	// 			}
+	// 			if acc_amount <= balance_per_block {
+	// 				let res = (block, balance_per_block - acc_amount, acc_amount);
+	// 				acc_amount = <BalanceOf<T>>::default();
+	// 				return Some(res);
+	// 			} else {
+	// 				acc_amount -= balance_per_block;
+	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));
+	// 			}
+	// 		})
+	// 		.collect::<Vec<_>>();
+
+	// 	new_state
+	// 		.into_iter()
+	// 		.for_each(|(block, to_staked, _to_pending)| {
+	// 			if to_staked == <BalanceOf<T>>::default() {
+	// 				<Staked<T>>::remove((staker, block));
+	// 			} else {
+	// 				<Staked<T>>::insert((staker, block), to_staked);
+	// 			}
+	// 		});
+
+	// 	Ok(())
+	// }
+
+	pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
+		Ok(())
+	}
+
+	pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
+		Ok(())
+	}
+
+	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {
+		Ok(())
+	}
+
+	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {
+		Ok(())
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {
+		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();
+		locked_balance -= amount;
+		Self::set_lock_unchecked(staker, locked_balance);
+	}
+
+	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+		Self::get_locked_balance(staker)
+			.map(|l| l.amount)
+			.and_then(|b| b.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>) {
+		<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 staked = Staked::<T>::iter_prefix((staker,))
+			.into_iter()
+			.map(|(block, amount)| (block, amount))
+			.collect::<Vec<_>>();
+		if !staked.is_empty() {
+			Some(staked)
+		} else {
+			None
+		}
+	}
+
+	pub fn cross_id_total_staked(staker: T::CrossAccountId) -> Option<BalanceOf<T>> {
+		Self::total_staked_by_id(staker.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_stake(
+		staker: &T::AccountId,
+		block: T::BlockNumber,
+		base: BalanceOf<T>,
+		income_acc: &mut BalanceOf<T>,
+	) {
+		let income = Self::calculate_income(base);
+		base.checked_add(&income).map(|res| {
+			<Staked<T>>::insert((staker, block), res);
+			*income_acc += income;
+			<T::Currency as Currency<T::AccountId>>::transfer(
+				&T::TreasuryAccountId::get(),
+				staker,
+				income,
+				ExistenceRequirement::KeepAlive,
+			)
+			.and_then(|_| Self::add_lock_balance(staker, income));
+		});
+	}
+
+	fn calculate_income<I>(base: I) -> I
+	where
+		I: EncodeLike<BalanceOf<T>> + Balance,
+	{
+		let day_rate = Perbill::from_rational(5u32, 1_0000);
+		day_rate * base
+	}
+}
addedpallets/app-promotion/src/tests.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/tests.rs
@@ -0,0 +1,128 @@
+// 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(test)]
+#![allow(clippy::from_over_into)]
+use crate as pallet_promotion;
+
+use frame_benchmarking::{add_benchmark, BenchmarkBatch};
+use frame_support::{
+	assert_ok, parameter_types,
+	traits::{Currency, OnInitialize, Everything, ConstU32},
+};
+use frame_system::RawOrigin;
+use sp_core::H256;
+use sp_runtime::{
+	traits::{BlakeTwo256, BlockNumberProvider, IdentityLookup},
+	testing::Header,
+	Perbill,
+};
+
+// type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+// type Block = frame_system::mocking::MockBlock<Test>;
+
+// parameter_types! {
+// 	pub const BlockHashCount: u64 = 250;
+// 	pub BlockWeights: frame_system::limits::BlockWeights =
+// 		frame_system::limits::BlockWeights::simple_max(1024);
+// 	pub const SS58Prefix: u8 = 42;
+// 	pub TreasuryAccountId: u64 = 1234;
+// 	pub const InflationBlockInterval: u32 = 100; // every time per how many blocks inflation is applied
+// 	pub static MockBlockNumberProvider: u64 = 0;
+// 	pub const ExistentialDeposit: u64 = 1;
+// 	pub const MaxLocks: u32 = 50;
+// }
+
+// impl BlockNumberProvider for MockBlockNumberProvider {
+// 	type BlockNumber = u64;
+
+// 	fn current_block_number() -> Self::BlockNumber {
+// 		Self::get()
+// 	}
+// }
+
+// frame_support::construct_runtime!(
+// 	pub enum Test where
+// 		Block = Block,
+// 		NodeBlock = Block,
+// 		UncheckedExtrinsic = UncheckedExtrinsic,
+// 	{
+// 		Balances: pallet_balances::{Pallet, Call, Storage},
+// 		System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
+// 		Promotion: pallet_promotion::{Pallet, Call, Storage}
+// 	}
+// );
+
+// impl frame_system::Config for Test {
+// 	type BaseCallFilter = Everything;
+// 	type BlockWeights = ();
+// 	type BlockLength = ();
+// 	type DbWeight = ();
+// 	type Origin = Origin;
+// 	type Call = Call;
+// 	type Index = u64;
+// 	type BlockNumber = u64;
+// 	type Hash = H256;
+// 	type Hashing = BlakeTwo256;
+// 	type AccountId = u64;
+// 	type Lookup = IdentityLookup<Self::AccountId>;
+// 	type Header = Header;
+// 	type Event = ();
+// 	type BlockHashCount = BlockHashCount;
+// 	type Version = ();
+// 	type PalletInfo = PalletInfo;
+// 	type AccountData = pallet_balances::AccountData<u64>;
+// 	type OnNewAccount = ();
+// 	type OnKilledAccount = ();
+// 	type SystemWeightInfo = ();
+// 	type SS58Prefix = SS58Prefix;
+// 	type OnSetCode = ();
+// 	type MaxConsumers = ConstU32<16>;
+// }
+
+// impl pallet_balances::Config for Test {
+// 	type AccountStore = System;
+// 	type Balance = u64;
+// 	type DustRemoval = ();
+// 	type Event = ();
+// 	type ExistentialDeposit = ExistentialDeposit;
+// 	type WeightInfo = ();
+// 	type MaxLocks = MaxLocks;
+// 	type MaxReserves = ();
+// 	type ReserveIdentifier = [u8; 8];
+// }
+
+// impl pallet_promotion::Config for Test {
+// 	type Currency = Balances;
+
+// 	type TreasuryAccountId = TreasuryAccountId;
+
+// 	type BlockNumberProvider = MockBlockNumberProvider;
+// }
+
+// pub fn new_test_ext() -> sp_io::TestExternalities {
+// 	frame_system::GenesisConfig::default()
+// 		.build_storage::<Test>()
+// 		.unwrap()
+// 		.into()
+// }
+
+// #[test]
+// fn test_benchmark() {
+// 	new_test_ext().execute_with(|| {
+// 		test_benchmark_stake::<Test>();
+// 	} )
+// }
addedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/types.rs
@@ -0,0 +1,20 @@
+use codec::EncodeLike;
+use frame_support::{traits::LockableCurrency, WeakBoundedVec, Parameter};
+use pallet_balances::{BalanceLock, Config as BalancesConfig, Pallet as PalletBalances};
+
+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)
+	}
+}
addedpallets/app-promotion/src/weights.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/app-promotion/src/weights.rs
@@ -0,0 +1,141 @@
+// 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-08-09, 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(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() -> Weight;
+	fn start_app_promotion() -> Weight;
+	fn set_admin_address() -> Weight;
+	fn stake() -> Weight;
+	fn unstake() -> Weight;
+	fn recalculate_stake() -> 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: Promotion PendingUnstake (r:1 w:0)
+	// Storage: Promotion NextInterestBlock (r:1 w:0)
+	fn on_initialize() -> Weight {
+		(2_705_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+	}
+	// Storage: Promotion StartBlock (r:1 w:1)
+	// Storage: Promotion NextInterestBlock (r:0 w:1)
+	fn start_app_promotion() -> Weight {
+		(1_436_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
+	}
+	// Storage: Promotion Admin (r:0 w:1)
+	fn set_admin_address() -> Weight {
+		(516_000 as Weight)
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: System Account (r:1 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: Promotion Staked (r:1 w:1)
+	// Storage: Promotion TotalStaked (r:1 w:1)
+	fn stake() -> Weight {
+		(10_019_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(4 as Weight))
+	}
+	// Storage: System Account (r:1 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: Promotion Staked (r:1 w:1)
+	// Storage: Promotion TotalStaked (r:1 w:1)
+	fn unstake() -> Weight {
+		(10_619_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(4 as Weight))
+	}
+	// Storage: System Account (r:2 w:0)
+	// Storage: Promotion Staked (r:0 w:1)
+	fn recalculate_stake() -> Weight {
+		(4_932_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: Promotion PendingUnstake (r:1 w:0)
+	// Storage: Promotion NextInterestBlock (r:1 w:0)
+	fn on_initialize() -> Weight {
+		(2_705_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+	}
+	// Storage: Promotion StartBlock (r:1 w:1)
+	// Storage: Promotion NextInterestBlock (r:0 w:1)
+	fn start_app_promotion() -> Weight {
+		(1_436_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
+	}
+	// Storage: Promotion Admin (r:0 w:1)
+	fn set_admin_address() -> Weight {
+		(516_000 as Weight)
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: System Account (r:1 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: Promotion Staked (r:1 w:1)
+	// Storage: Promotion TotalStaked (r:1 w:1)
+	fn stake() -> Weight {
+		(10_019_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
+	}
+	// Storage: System Account (r:1 w:1)
+	// Storage: Balances Locks (r:1 w:1)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: Promotion Staked (r:1 w:1)
+	// Storage: Promotion TotalStaked (r:1 w:1)
+	fn unstake() -> Weight {
+		(10_619_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
+	}
+	// Storage: System Account (r:2 w:0)
+	// Storage: Promotion Staked (r:0 w:1)
+	fn recalculate_stake() -> Weight {
+		(4_932_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+}
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -20,16 +20,21 @@
 	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>;
 
 sp_api::decl_runtime_apis! {
 	#[api_version(2)]
 	/// Trait for generate rpc.
-	pub trait UniqueApi<CrossAccountId, AccountId> where
+	pub trait UniqueApi<BlockNumber ,CrossAccountId, AccountId> where
+		BlockNumber: Decode + Member + AtLeast32BitUnsigned,
 		AccountId: Decode,
 		CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,
 	{
@@ -121,5 +126,8 @@
 		/// 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>>;
+		fn total_staked(staker: CrossAccountId) -> Result<u128>;
+		fn total_staked_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>>;
+		fn total_staking_locked(staker: CrossAccountId) -> Result<u128>;
 	}
 }
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -61,7 +61,7 @@
         impl_runtime_apis! {
             $($($custom_apis)+)?
 
-            impl up_rpc::UniqueApi<Block, CrossAccountId, AccountId> for Runtime {
+            impl up_rpc::UniqueApi<Block, BlockNumber, CrossAccountId, AccountId> for Runtime {
                 fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>, DispatchError> {
                     dispatch_unique_runtime!(collection.account_tokens(account))
                 }
@@ -187,6 +187,20 @@
                 fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
                     dispatch_unique_runtime!(collection.total_pieces(token_id))
                 }
+
+                fn total_staked(staker: CrossAccountId) -> Result<u128, DispatchError> {
+                    Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_total_staked(staker).unwrap_or_default())
+                    // Ok(0)
+                }
+
+                fn total_staked_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>, DispatchError> {
+                    Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_total_staked_per_block(staker))
+                }
+
+                fn total_staking_locked(staker: CrossAccountId) -> Result<u128, DispatchError> {
+                    // Ok(0)
+                    Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_locked_balance(staker))
+                }
             }
 
             impl rmrk_rpc::RmrkApi<
@@ -638,6 +652,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, Promotion);
                     list_benchmark!(list, extra, pallet_fungible, Fungible);
                     list_benchmark!(list, extra, pallet_nonfungible, Nonfungible);
 
@@ -693,6 +708,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, Promotion);
                     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',
@@ -88,6 +89,7 @@
     'serde',
     'pallet-inflation/std',
     'pallet-configuration/std',
+    'pallet-app-promotion/std',
     'pallet-common/std',
     'pallet-structure/std',
     'pallet-fungible/std',
@@ -414,6 +416,7 @@
 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" }