diff --git a/Cargo.lock b/Cargo.lock
index ed3a7a678aa5ce896f15cd64288ff148dbbce202..f1c8f8045b826a96a72e6425304643caada49efa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1926,11 +1926,15 @@ dependencies = [
 name = "bp-relayers"
 version = "0.7.0"
 dependencies = [
+ "bp-header-chain",
  "bp-messages",
+ "bp-parachains",
  "bp-runtime",
  "frame-support",
+ "frame-system",
  "hex",
  "hex-literal",
+ "pallet-utility",
  "parity-scale-codec",
  "scale-info",
  "sp-runtime",
@@ -10201,18 +10205,27 @@ dependencies = [
 name = "pallet-bridge-relayers"
 version = "0.7.0"
 dependencies = [
+ "bp-header-chain",
  "bp-messages",
+ "bp-parachains",
+ "bp-polkadot-core",
  "bp-relayers",
  "bp-runtime",
+ "bp-test-utils",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
  "log",
  "pallet-balances",
+ "pallet-bridge-grandpa",
  "pallet-bridge-messages",
+ "pallet-bridge-parachains",
+ "pallet-transaction-payment",
+ "pallet-utility",
  "parity-scale-codec",
  "scale-info",
  "sp-arithmetic",
+ "sp-core",
  "sp-io",
  "sp-runtime",
  "sp-std 14.0.0",
diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions.rs
similarity index 77%
rename from bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs
rename to bridges/bin/runtime-common/src/extensions.rs
index 433ab79168928671ba2f95d900b0b7a9907c65c2..dc7e14de28f311817b7bced78e62ff1b5b037b1d 100644
--- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs
+++ b/bridges/bin/runtime-common/src/extensions.rs
@@ -18,7 +18,6 @@
 //! obsolete (duplicated) data or do not pass some additional pallet-specific
 //! checks.
 
-use crate::extensions::refund_relayer_extension::RefundableParachainId;
 use bp_parachains::SubmitParachainHeadsInfo;
 use bp_relayers::ExplicitOrAccountParams;
 use bp_runtime::Parachain;
@@ -29,9 +28,10 @@ use pallet_bridge_messages::CallSubType as MessagesCallSubType;
 use pallet_bridge_parachains::{CallSubType as ParachainsCallSubtype, SubmitParachainHeadsHelper};
 use pallet_bridge_relayers::Pallet as RelayersPallet;
 use sp_runtime::{
-	traits::{Get, PhantomData, UniqueSaturatedInto},
+	traits::{Get, UniqueSaturatedInto},
 	transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder},
 };
+use sp_std::marker::PhantomData;
 
 // Re-export to avoid include tuplex dependency everywhere.
 #[doc(hidden)]
@@ -123,17 +123,27 @@ where
 /// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`.
 /// The boost is only applied if submitter has active registration in the relayers
 /// pallet.
-pub struct CheckAndBoostBridgeParachainsTransactions<T, RefPara, Priority, SlashAccount>(
-	PhantomData<(T, RefPara, Priority, SlashAccount)>,
-);
-
-impl<T, RefPara, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
-	BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
-	for CheckAndBoostBridgeParachainsTransactions<T, RefPara, Priority, SlashAccount>
+pub struct CheckAndBoostBridgeParachainsTransactions<
+	T,
+	ParachainsInstance,
+	Para,
+	Priority,
+	SlashAccount,
+>(PhantomData<(T, ParachainsInstance, Para, Priority, SlashAccount)>);
+
+impl<
+		T,
+		ParachainsInstance,
+		Para,
+		Priority: Get<TransactionPriority>,
+		SlashAccount: Get<T::AccountId>,
+	> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
+	for CheckAndBoostBridgeParachainsTransactions<T, ParachainsInstance, Para, Priority, SlashAccount>
 where
-	T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config<RefPara::Instance>,
-	RefPara: RefundableParachainId,
-	T::RuntimeCall: ParachainsCallSubtype<T, RefPara::Instance>,
+	T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config<ParachainsInstance>,
+	ParachainsInstance: 'static,
+	Para: Parachain,
+	T::RuntimeCall: ParachainsCallSubtype<T, ParachainsInstance>,
 {
 	// bridged header number, bundled in transaction
 	type ToPostDispatch = Option<SubmitParachainHeadsInfo>;
@@ -142,10 +152,10 @@ where
 		who: &T::AccountId,
 		call: &T::RuntimeCall,
 	) -> (Self::ToPostDispatch, TransactionValidity) {
-		match ParachainsCallSubtype::<T, RefPara::Instance>::check_obsolete_submit_parachain_heads(
+		match ParachainsCallSubtype::<T, ParachainsInstance>::check_obsolete_submit_parachain_heads(
 			call,
 		) {
-			Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::BridgedChain::PARACHAIN_ID => {
+			Ok(Some(our_tx)) if our_tx.base.para_id.0 == Para::PARACHAIN_ID => {
 				let to_post_dispatch = Some(our_tx.base);
 				let total_priority_boost =
 					compute_priority_boost::<T, _, Priority>(&who, our_tx.improved_by);
@@ -164,7 +174,7 @@ where
 		let Some(update) = maybe_update else { return };
 		// we are only interested in failed or unneeded transactions
 		let has_failed = has_failed ||
-			!SubmitParachainHeadsHelper::<T, RefPara::Instance>::was_successful(&update);
+			!SubmitParachainHeadsHelper::<T, ParachainsInstance>::was_successful(&update);
 
 		if !has_failed {
 			return
@@ -272,7 +282,7 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 			type Pre = (
 				$account_id,
 				( $(
-					<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
+					<$filter_call as $crate::extensions::BridgeRuntimeFilterCall<
 						$account_id,
 						$call,
 					>>::ToPostDispatch,
@@ -299,7 +309,7 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 				$(
 					let (from_validate, call_filter_validity) = <
 						$filter_call as
-						$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
+						$crate::extensions::BridgeRuntimeFilterCall<
 							Self::AccountId,
 							$call,
 						>>::validate(&who, call);
@@ -316,12 +326,13 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 				info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
 				len: usize,
 			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
-				use $crate::extensions::check_obsolete_extension::__private::tuplex::PushBack;
+				use $crate::extensions::__private::tuplex::PushBack;
+
 				let to_post_dispatch = ();
 				$(
 					let (from_validate, call_filter_validity) = <
 						$filter_call as
-						$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
+						$crate::extensions::BridgeRuntimeFilterCall<
 							$account_id,
 							$call,
 						>>::validate(&relayer, call);
@@ -339,14 +350,15 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 				len: usize,
 				result: &sp_runtime::DispatchResult,
 			) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> {
-				use $crate::extensions::check_obsolete_extension::__private::tuplex::PopFront;
+				use $crate::extensions::__private::tuplex::PopFront;
+
 				let Some((relayer, to_post_dispatch)) = to_post_dispatch else { return Ok(()) };
 				let has_failed = result.is_err();
 				$(
 					let (item, to_post_dispatch) = to_post_dispatch.pop_front();
 					<
 						$filter_call as
-						$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
+						$crate::extensions::BridgeRuntimeFilterCall<
 							$account_id,
 							$call,
 						>>::post_dispatch(&relayer, has_failed, item);
@@ -360,25 +372,37 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::{
-		extensions::refund_relayer_extension::{
-			tests::{
-				initialize_environment, relayer_account_at_this_chain,
-				submit_parachain_head_call_ex, submit_relay_header_call_ex,
-			},
-			RefundableParachain,
-		},
-		mock::*,
-	};
-	use bp_polkadot_core::parachains::ParaId;
+	use crate::mock::*;
+	use bp_header_chain::StoredHeaderDataBuilder;
+	use bp_messages::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData};
+	use bp_parachains::{BestParaHeadHash, ParaInfo};
+	use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
+	use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
 	use bp_runtime::HeaderId;
-	use frame_support::{assert_err, assert_ok};
+	use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
+	use frame_support::{assert_err, assert_ok, traits::fungible::Mutate};
+	use pallet_bridge_grandpa::{Call as GrandpaCall, StoredAuthoritySet};
+	use pallet_bridge_parachains::Call as ParachainsCall;
 	use sp_runtime::{
-		traits::{ConstU64, SignedExtension},
+		traits::{parameter_types, ConstU64, Header as _, SignedExtension},
 		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
 		DispatchError,
 	};
 
+	parameter_types! {
+		pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
+			test_lane_id(),
+			TEST_BRIDGED_CHAIN_ID,
+			RewardsAccountOwner::ThisChain,
+		);
+		pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
+			test_lane_id(),
+			TEST_BRIDGED_CHAIN_ID,
+			RewardsAccountOwner::BridgedChain,
+		);
+		pub TestLaneId: LaneId = test_lane_id();
+	}
+
 	pub struct MockCall {
 		data: u32,
 	}
@@ -452,6 +476,103 @@ mod tests {
 		}
 	}
 
+	fn test_lane_id() -> LaneId {
+		LaneId::new(1, 2)
+	}
+
+	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
+		let test_stake: ThisChainBalance = TestStake::get();
+		ExistentialDeposit::get().saturating_add(test_stake * 100)
+	}
+
+	// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
+	// works)
+
+	fn delivery_rewards_account() -> ThisChainAccountId {
+		TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
+	}
+
+	fn confirmation_rewards_account() -> ThisChainAccountId {
+		TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
+	}
+
+	fn relayer_account_at_this_chain() -> ThisChainAccountId {
+		0
+	}
+
+	fn initialize_environment(
+		best_relay_header_number: BridgedChainBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
+		best_message: MessageNonce,
+	) {
+		let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
+		let best_relay_header = HeaderId(best_relay_header_number, BridgedChainHash::default());
+		pallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
+			StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
+		);
+		pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header);
+		pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
+			best_relay_header.hash(),
+			bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
+		);
+
+		let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID);
+		let para_info = ParaInfo {
+			best_head_hash: BestParaHeadHash {
+				at_relay_block_number: parachain_head_at_relay_header_number,
+				head_hash: [parachain_head_at_relay_header_number as u8; 32].into(),
+			},
+			next_imported_hash_position: 0,
+		};
+		pallet_bridge_parachains::ParasInfo::<TestRuntime>::insert(para_id, para_info);
+
+		let lane_id = test_lane_id();
+		let in_lane_data =
+			InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
+		pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
+
+		let out_lane_data =
+			OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
+		pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
+
+		Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
+		Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
+		Balances::mint_into(
+			&relayer_account_at_this_chain(),
+			initial_balance_of_relayer_account_at_this_chain(),
+		)
+		.unwrap();
+	}
+
+	fn submit_relay_header_call(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
+		let relay_header = BridgedChainHeader::new(
+			relay_header_number,
+			Default::default(),
+			Default::default(),
+			Default::default(),
+			Default::default(),
+		);
+		let relay_justification = make_default_justification(&relay_header);
+
+		RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof {
+			finality_target: Box::new(relay_header),
+			justification: relay_justification,
+		})
+	}
+
+	fn submit_parachain_head_call(
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
+	) -> RuntimeCall {
+		RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
+			at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
+			parachains: vec![(
+				ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+				[parachain_head_at_relay_header_number as u8; 32].into(),
+			)],
+			parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
+		})
+	}
+
 	#[test]
 	fn test_generated_obsolete_extension() {
 		generate_bridge_reject_obsolete_headers_and_messages!(
@@ -543,7 +664,7 @@ mod tests {
 
 			let priority_boost = BridgeGrandpaWrapper::validate(
 				&relayer_account_at_this_chain(),
-				&submit_relay_header_call_ex(200),
+				&submit_relay_header_call(200),
 			)
 			.1
 			.unwrap()
@@ -561,7 +682,7 @@ mod tests {
 
 			let priority_boost = BridgeGrandpaWrapper::validate(
 				&relayer_account_at_this_chain(),
-				&submit_relay_header_call_ex(200),
+				&submit_relay_header_call(200),
 			)
 			.1
 			.unwrap()
@@ -598,7 +719,8 @@ mod tests {
 
 	type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions<
 		TestRuntime,
-		RefundableParachain<(), BridgedUnderlyingParachain>,
+		(),
+		BridgedUnderlyingParachain,
 		ConstU64<1_000>,
 		SlashDestination,
 	>;
@@ -610,7 +732,7 @@ mod tests {
 
 			let priority_boost = BridgeParachainsWrapper::validate(
 				&relayer_account_at_this_chain(),
-				&submit_parachain_head_call_ex(200),
+				&submit_parachain_head_call(200),
 			)
 			.1
 			.unwrap()
@@ -628,7 +750,7 @@ mod tests {
 
 			let priority_boost = BridgeParachainsWrapper::validate(
 				&relayer_account_at_this_chain(),
-				&submit_parachain_head_call_ex(200),
+				&submit_parachain_head_call(200),
 			)
 			.1
 			.unwrap()
diff --git a/bridges/bin/runtime-common/src/extensions/mod.rs b/bridges/bin/runtime-common/src/extensions/mod.rs
deleted file mode 100644
index 3f1b506aaae3ef66fe6f44379258356a2074464c..0000000000000000000000000000000000000000
--- a/bridges/bin/runtime-common/src/extensions/mod.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) Parity Technologies (UK) Ltd.
-// This file is part of Parity Bridges Common.
-
-// Parity Bridges Common 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.
-
-// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
-
-//! Bridge-specific transaction extensions.
-
-pub mod check_obsolete_extension;
-pub mod priority_calculator;
-pub mod refund_relayer_extension;
diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs
index de9ee1b0bbbca8205fdd47d84544a80a6d822545..ac8b013086b1bd63da2ed2e14d40002341be1758 100644
--- a/bridges/bin/runtime-common/src/lib.rs
+++ b/bridges/bin/runtime-common/src/lib.rs
@@ -20,7 +20,6 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod extensions;
-
 pub mod messages_api;
 pub mod messages_benchmarking;
 pub mod parachains_benchmarking;
diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs
index 917b529be8345b418b955d927af763327ce39f22..fed0d15cc080b78381d3e2ad6bfe8a768139101d 100644
--- a/bridges/bin/runtime-common/src/mock.rs
+++ b/bridges/bin/runtime-common/src/mock.rs
@@ -84,6 +84,7 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
 >;
 
 /// Message lane used in tests.
+#[allow(unused)]
 pub fn test_lane_id() -> LaneId {
 	LaneId::new(1, 2)
 }
diff --git a/bridges/bin/runtime-common/src/parachains_benchmarking.rs b/bridges/bin/runtime-common/src/parachains_benchmarking.rs
index bcbd779b44dea5fbef7781335cfa1d359ab8c1f1..e48a5664e31a62da3de91aaf368b6ad8ed2f0840 100644
--- a/bridges/bin/runtime-common/src/parachains_benchmarking.rs
+++ b/bridges/bin/runtime-common/src/parachains_benchmarking.rs
@@ -20,12 +20,13 @@
 
 use crate::messages_benchmarking::insert_header_to_grandpa_pallet;
 
-use bp_parachains::parachain_head_storage_key_at_source;
+use bp_parachains::{
+	parachain_head_storage_key_at_source, RelayBlockHash, RelayBlockHasher, RelayBlockNumber,
+};
 use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
 use bp_runtime::{grow_storage_value, record_all_trie_keys, Chain, UnverifiedStorageProofParams};
 use codec::Encode;
 use frame_support::traits::Get;
-use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use sp_std::prelude::*;
 use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
 
diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs
index 5690b8b4b18cf0cf1926c0a9c47b1dee48812001..d964901ba4bc6681246b1b9d30104e652a282fa1 100644
--- a/bridges/modules/grandpa/src/call_ext.rs
+++ b/bridges/modules/grandpa/src/call_ext.rs
@@ -34,10 +34,11 @@ use sp_runtime::{
 	transaction_validity::{InvalidTransaction, TransactionValidityError},
 	RuntimeDebug, SaturatedConversion,
 };
+use sp_std::fmt::Debug;
 
 /// Verified `SubmitFinalityProofInfo<N>`.
 #[derive(Copy, Clone, PartialEq, RuntimeDebug)]
-pub struct VerifiedSubmitFinalityProofInfo<N> {
+pub struct VerifiedSubmitFinalityProofInfo<N: Debug> {
 	/// Base call information.
 	pub base: SubmitFinalityProofInfo<N>,
 	/// A difference between bundled bridged header and best bridged header known to us
diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs
index 9d8ced5e1b3fe71d535ac33603a97b2cceb5d223..f30af9c482c59203f32219451c01ce107e41d1d1 100644
--- a/bridges/modules/parachains/src/lib.rs
+++ b/bridges/modules/parachains/src/lib.rs
@@ -28,7 +28,10 @@ pub use weights::WeightInfo;
 pub use weights_ext::WeightInfoExt;
 
 use bp_header_chain::{HeaderChain, HeaderChainError};
-use bp_parachains::{ParaInfo, ParaStoredHeaderData, SubmitParachainHeadsInfo};
+use bp_parachains::{
+	ParaInfo, ParaStoredHeaderData, RelayBlockHash, RelayBlockHasher, RelayBlockNumber,
+	SubmitParachainHeadsInfo,
+};
 use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
 use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain};
 use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound};
@@ -61,13 +64,6 @@ mod proofs;
 /// The target that will be used when publishing logs related to this pallet.
 pub const LOG_TARGET: &str = "runtime::bridge-parachains";
 
-/// Block hash of the bridged relay chain.
-pub type RelayBlockHash = bp_polkadot_core::Hash;
-/// Block number of the bridged relay chain.
-pub type RelayBlockNumber = bp_polkadot_core::BlockNumber;
-/// Hasher of the bridged relay chain.
-pub type RelayBlockHasher = bp_polkadot_core::Hasher;
-
 /// Artifacts of the parachains head update.
 struct UpdateParachainHeadArtifacts {
 	/// New best head of the parachain.
diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml
index 27a28546afb482851040cbe16bd40071e86a70cb..9a4a680e135f2e8451b670c7152b06fdb3c8d668 100644
--- a/bridges/modules/relayers/Cargo.toml
+++ b/bridges/modules/relayers/Cargo.toml
@@ -16,30 +16,38 @@ log = { workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 
 # Bridge dependencies
-
+bp-header-chain = { workspace = true }
 bp-messages = { workspace = true }
 bp-relayers = { workspace = true }
 bp-runtime = { workspace = true }
+pallet-bridge-grandpa = { workspace = true }
 pallet-bridge-messages = { workspace = true }
+pallet-bridge-parachains = { workspace = true }
 
 # Substrate Dependencies
-
 frame-benchmarking = { optional = true, workspace = true }
 frame-support = { workspace = true }
 frame-system = { workspace = true }
+pallet-transaction-payment = { workspace = true }
 sp-arithmetic = { workspace = true }
 sp-runtime = { workspace = true }
 sp-std = { workspace = true }
 
 [dev-dependencies]
-bp-runtime = { workspace = true, default-features = true }
-pallet-balances = { workspace = true, default-features = true }
-sp-io = { workspace = true, default-features = true }
-sp-runtime = { workspace = true, default-features = true }
+bp-runtime = { workspace = true }
+pallet-balances = { workspace = true }
+sp-io = { workspace = true }
+sp-runtime = { workspace = true }
+bp-parachains = { workspace = true }
+bp-polkadot-core = { workspace = true }
+bp-test-utils = { workspace = true }
+pallet-utility = { workspace = true }
+sp-core = { workspace = true }
 
 [features]
 default = ["std"]
 std = [
+	"bp-header-chain/std",
 	"bp-messages/std",
 	"bp-relayers/std",
 	"bp-runtime/std",
@@ -48,7 +56,10 @@ std = [
 	"frame-support/std",
 	"frame-system/std",
 	"log/std",
+	"pallet-bridge-grandpa/std",
 	"pallet-bridge-messages/std",
+	"pallet-bridge-parachains/std",
+	"pallet-transaction-payment/std",
 	"scale-info/std",
 	"sp-arithmetic/std",
 	"sp-runtime/std",
@@ -59,13 +70,18 @@ runtime-benchmarks = [
 	"frame-support/runtime-benchmarks",
 	"frame-system/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
+	"pallet-bridge-grandpa/runtime-benchmarks",
 	"pallet-bridge-messages/runtime-benchmarks",
+	"pallet-bridge-parachains/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 ]
 try-runtime = [
 	"frame-support/try-runtime",
 	"frame-system/try-runtime",
 	"pallet-balances/try-runtime",
+	"pallet-bridge-grandpa/try-runtime",
 	"pallet-bridge-messages/try-runtime",
+	"pallet-bridge-parachains/try-runtime",
 	"sp-runtime/try-runtime",
 ]
+integrity-test = []
diff --git a/bridges/modules/relayers/src/extension/grandpa_adapter.rs b/bridges/modules/relayers/src/extension/grandpa_adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6c9ae1c2968c0a3f63db463d720ccdcfe6a356a3
--- /dev/null
+++ b/bridges/modules/relayers/src/extension/grandpa_adapter.rs
@@ -0,0 +1,177 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common 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.
+
+// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Adapter that allows using `pallet-bridge-relayers` as a signed extension in the
+//! bridge with remote GRANDPA chain.
+
+use crate::{
+	extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig, LOG_TARGET,
+};
+
+use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
+use bp_runtime::{Chain, StaticStrProvider};
+use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
+use frame_system::Config as SystemConfig;
+use pallet_bridge_grandpa::{
+	CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
+	SubmitFinalityProofHelper,
+};
+use pallet_bridge_messages::{
+	CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig,
+};
+use sp_runtime::{
+	traits::{Dispatchable, Get},
+	transaction_validity::{TransactionPriority, TransactionValidityError},
+	Saturating,
+};
+use sp_std::marker::PhantomData;
+
+/// Adapter to be used in signed extension configuration, when bridging with remote
+/// chains that are using GRANDPA finality.
+pub struct WithGrandpaChainExtensionConfig<
+	// signed extension identifier
+	IdProvider,
+	// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
+	// uses `BridgeGrandpaConfig<BridgeGrandpaPalletInstance>` to receive messages and
+	// confirmations from the remote chain.
+	Runtime,
+	// batch call unpacker
+	BatchCallUnpacker,
+	// instance of the `pallet-bridge-grandpa`, tracked by this extension
+	BridgeGrandpaPalletInstance,
+	// instance of BridgedChain `pallet-bridge-messages`, tracked by this extension
+	BridgeMessagesPalletInstance,
+	// message delivery transaction priority boost for every additional message
+	PriorityBoostPerMessage,
+>(
+	PhantomData<(
+		IdProvider,
+		Runtime,
+		BatchCallUnpacker,
+		BridgeGrandpaPalletInstance,
+		BridgeMessagesPalletInstance,
+		PriorityBoostPerMessage,
+	)>,
+);
+
+impl<ID, R, BCU, GI, MI, P> ExtensionConfig
+	for WithGrandpaChainExtensionConfig<ID, R, BCU, GI, MI, P>
+where
+	ID: StaticStrProvider,
+	R: BridgeRelayersConfig
+		+ BridgeMessagesConfig<MI, BridgedChain = pallet_bridge_grandpa::BridgedChain<R, GI>>
+		+ BridgeGrandpaConfig<GI>,
+	BCU: BatchCallUnpacker<R>,
+	GI: 'static,
+	MI: 'static,
+	P: Get<TransactionPriority>,
+	R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+		+ BridgeGrandpaCallSubtype<R, GI>
+		+ BridgeMessagesCallSubType<R, MI>,
+{
+	type IdProvider = ID;
+	type Runtime = R;
+	type BridgeMessagesPalletInstance = MI;
+	type PriorityBoostPerMessage = P;
+	type Reward = R::Reward;
+	type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber<R, GI>;
+
+	fn parse_and_check_for_obsolete_call(
+		call: &R::RuntimeCall,
+	) -> Result<
+		Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>>,
+		TransactionValidityError,
+	> {
+		let calls = BCU::unpack(call, 2);
+		let total_calls = calls.len();
+		let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
+
+		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info());
+		let relay_finality_call =
+			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
+
+		Ok(match (total_calls, relay_finality_call, msgs_call) {
+			(2, Some(relay_finality_call), Some(msgs_call)) =>
+				Some(ExtensionCallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
+			(1, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
+			_ => None,
+		})
+	}
+
+	fn check_obsolete_parsed_call(
+		call: &R::RuntimeCall,
+	) -> Result<&R::RuntimeCall, TransactionValidityError> {
+		call.check_obsolete_submit_finality_proof()?;
+		call.check_obsolete_call()?;
+		Ok(call)
+	}
+
+	fn check_call_result(
+		call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>,
+		call_data: &mut ExtensionCallData,
+		relayer: &R::AccountId,
+	) -> bool {
+		verify_submit_finality_proof_succeeded::<Self, GI>(call_info, call_data, relayer) &&
+			verify_messages_call_succeeded::<Self, MI>(call_info, call_data, relayer)
+	}
+}
+
+/// If the batch call contains the GRANDPA chain state update call, verify that it
+/// has been successful.
+///
+/// Only returns false when GRANDPA chain state update call has failed.
+pub(crate) fn verify_submit_finality_proof_succeeded<C, GI>(
+	call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber>,
+	call_data: &mut ExtensionCallData,
+	relayer: &<C::Runtime as SystemConfig>::AccountId,
+) -> bool
+where
+	C: ExtensionConfig,
+	GI: 'static,
+	C::Runtime: BridgeGrandpaConfig<GI>,
+	<C::Runtime as BridgeGrandpaConfig<GI>>::BridgedChain:
+		Chain<BlockNumber = C::RemoteGrandpaChainBlockNumber>,
+{
+	let Some(finality_proof_info) = call_info.submit_finality_proof_info() else { return true };
+
+	if !SubmitFinalityProofHelper::<C::Runtime, GI>::was_successful(
+		finality_proof_info.block_number,
+	) {
+		// we only refund relayer if all calls have updated chain state
+		log::trace!(
+			target: LOG_TARGET,
+			"{}.{:?}: relayer {:?} has submitted invalid GRANDPA chain finality proof",
+			C::IdProvider::STR,
+			call_info.messages_call_info().lane_id(),
+			relayer,
+		);
+		return false
+	}
+
+	// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
+	// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
+	// `Pays::No`, because such transaction is mandatory for operating the bridge. But
+	// `utility.batchAll` transaction always requires payment. But in both cases we'll
+	// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
+	// to submit dedicated transaction.
+
+	// submitter has means to include extra weight/bytes in the `submit_finality_proof`
+	// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
+	call_data.extra_weight.saturating_accrue(finality_proof_info.extra_weight);
+	call_data.extra_size.saturating_accrue(finality_proof_info.extra_size);
+
+	true
+}
diff --git a/bridges/modules/relayers/src/extension/messages_adapter.rs b/bridges/modules/relayers/src/extension/messages_adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f84e25ba5aa17ec9ffbbbea25d6e54651935510b
--- /dev/null
+++ b/bridges/modules/relayers/src/extension/messages_adapter.rs
@@ -0,0 +1,94 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common 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.
+
+// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Adapter that allows using `pallet-bridge-relayers` as a signed extension in the
+//! bridge with any remote chain. This adapter does not refund any finality transactions.
+
+use crate::{extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig};
+
+use bp_relayers::{ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
+use bp_runtime::StaticStrProvider;
+use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
+use pallet_bridge_messages::{
+	CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig,
+};
+use sp_runtime::{
+	traits::{Dispatchable, Get},
+	transaction_validity::{TransactionPriority, TransactionValidityError},
+};
+use sp_std::marker::PhantomData;
+
+/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
+/// transactions. Finality transactions are not refunded.
+pub struct MessagesExtensionConfig<
+	IdProvider,
+	Runtime,
+	BridgeMessagesPalletInstance,
+	PriorityBoostPerMessage,
+>(
+	PhantomData<(
+		// signed extension identifier
+		IdProvider,
+		// runtime with `pallet-bridge-messages` pallet deployed
+		Runtime,
+		// instance of BridgedChain `pallet-bridge-messages`, tracked by this extension
+		BridgeMessagesPalletInstance,
+		// message delivery transaction priority boost for every additional message
+		PriorityBoostPerMessage,
+	)>,
+);
+
+impl<ID, R, MI, P> ExtensionConfig for MessagesExtensionConfig<ID, R, MI, P>
+where
+	ID: StaticStrProvider,
+	R: BridgeRelayersConfig + BridgeMessagesConfig<MI>,
+	MI: 'static,
+	P: Get<TransactionPriority>,
+	R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+		+ BridgeMessagesCallSubType<R, MI>,
+{
+	type IdProvider = ID;
+	type Runtime = R;
+	type BridgeMessagesPalletInstance = MI;
+	type PriorityBoostPerMessage = P;
+	type Reward = R::Reward;
+	type RemoteGrandpaChainBlockNumber = ();
+
+	fn parse_and_check_for_obsolete_call(
+		call: &R::RuntimeCall,
+	) -> Result<
+		Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>>,
+		TransactionValidityError,
+	> {
+		let call = Self::check_obsolete_parsed_call(call)?;
+		Ok(call.call_info().map(ExtensionCallInfo::Msgs))
+	}
+
+	fn check_obsolete_parsed_call(
+		call: &R::RuntimeCall,
+	) -> Result<&R::RuntimeCall, TransactionValidityError> {
+		call.check_obsolete_call()?;
+		Ok(call)
+	}
+
+	fn check_call_result(
+		call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>,
+		call_data: &mut ExtensionCallData,
+		relayer: &R::AccountId,
+	) -> bool {
+		verify_messages_call_succeeded::<Self, MI>(call_info, call_data, relayer)
+	}
+}
diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/modules/relayers/src/extension/mod.rs
similarity index 55%
rename from bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs
rename to bridges/modules/relayers/src/extension/mod.rs
index 1418c8aea3a9b853049db06dc4b1debdf0da8977..fb170965afa9682fe28c09f8a6c87dc8c5448702 100644
--- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs
+++ b/bridges/modules/relayers/src/extension/mod.rs
@@ -14,208 +14,76 @@
 // You should have received a copy of the GNU General Public License
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
-//! Signed extension that refunds relayer if he has delivered some new messages.
-//! It also refunds transaction cost if the transaction is an `utility.batchAll()`
-//! with calls that are: delivering new message and all necessary underlying headers
-//! (parachain or relay chain).
-
-use bp_header_chain::SubmitFinalityProofInfo;
-use bp_messages::{ChainWithMessages, LaneId, MessageNonce, MessagesCallInfo};
-use bp_parachains::SubmitParachainHeadsInfo;
-use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams};
-use bp_runtime::{Chain, Parachain, RangeInclusiveExt, StaticStrProvider};
-use codec::{Codec, Decode, Encode};
+//! Signed extension, built around `pallet-bridge-relayers`. It is able to:
+//!
+//! - refund the cost of successful message delivery and confirmation transactions to the submitter
+//!   by registering corresponding reward in the pallet;
+//!
+//! - bump priority of messages delivery and confirmation transactions, signed by the registered
+//!   relayers.
+
+use crate::{Config as RelayersConfig, Pallet as RelayersPallet, WeightInfoExt, LOG_TARGET};
+
+use bp_messages::{ChainWithMessages, MessageNonce};
+use bp_relayers::{
+	ExplicitOrAccountParams, ExtensionCallData, ExtensionCallInfo, ExtensionConfig,
+	RewardsAccountOwner, RewardsAccountParams,
+};
+use bp_runtime::{Chain, RangeInclusiveExt, StaticStrProvider};
+use codec::{Decode, Encode};
 use frame_support::{
-	dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
-	traits::IsSubType,
-	weights::Weight,
+	dispatch::{DispatchInfo, PostDispatchInfo},
 	CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
 };
-use pallet_bridge_grandpa::{CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper};
-use pallet_bridge_messages::{
-	CallHelper as MessagesCallHelper, CallSubType as MessagesCallSubType, Config as MessagesConfig,
-};
-use pallet_bridge_parachains::{
-	BoundedBridgeGrandpaConfig, CallSubType as ParachainsCallSubType, Config as ParachainsConfig,
-	RelayBlockNumber, SubmitParachainHeadsHelper,
+use frame_system::Config as SystemConfig;
+use pallet_bridge_messages::{CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig};
+use pallet_transaction_payment::{
+	Config as TransactionPaymentConfig, OnChargeTransaction, Pallet as TransactionPaymentPallet,
 };
-use pallet_bridge_relayers::{
-	Config as RelayersConfig, Pallet as RelayersPallet, WeightInfoExt as _,
-};
-use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTransaction};
-use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
 use scale_info::TypeInfo;
 use sp_runtime::{
-	traits::{DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf, SignedExtension, Zero},
+	traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero},
 	transaction_validity::{
-		TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransactionBuilder,
+		TransactionValidity, TransactionValidityError, ValidTransactionBuilder,
 	},
-	DispatchResult, FixedPointOperand, RuntimeDebug,
+	DispatchResult, RuntimeDebug,
 };
-use sp_std::{marker::PhantomData, vec, vec::Vec};
-
-type AccountIdOf<R> = <R as frame_system::Config>::AccountId;
-// without this typedef rustfmt fails with internal err
-type BalanceOf<R> =
-	<<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance;
-type CallOf<R> = <R as frame_system::Config>::RuntimeCall;
-
-/// Trait identifying a bridged parachain. A relayer might be refunded for delivering messages
-/// coming from this parachain.
-pub trait RefundableParachainId {
-	/// The instance of the bridge parachains pallet.
-	type Instance: 'static;
-	/// The parachain Id.
-	type BridgedChain: Parachain;
-}
-
-/// Implementation of `RefundableParachainId` for `trait Parachain`.
-pub struct RefundableParachain<Instance, Para>(PhantomData<(Instance, Para)>);
-
-impl<Instance, Para> RefundableParachainId for RefundableParachain<Instance, Para>
-where
-	Instance: 'static,
-	Para: Parachain,
-{
-	type Instance = Instance;
-	type BridgedChain = Para;
-}
-
-/// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages
-/// coming from this lane.
-pub trait RefundableMessagesLaneId {
-	/// The instance of the bridge messages pallet.
-	type Instance: 'static;
-	/// The messages lane id.
-	type Id: Get<LaneId>;
-}
+use sp_std::{fmt::Debug, marker::PhantomData};
 
-/// Default implementation of `RefundableMessagesLaneId`.
-pub struct RefundableMessagesLane<Runtime, Instance, Lane>(PhantomData<(Runtime, Instance, Lane)>);
+pub use grandpa_adapter::WithGrandpaChainExtensionConfig;
+pub use messages_adapter::MessagesExtensionConfig;
+pub use parachain_adapter::WithParachainExtensionConfig;
+pub use priority::*;
 
-impl<Runtime, Instance, Lane> RefundableMessagesLaneId
-	for RefundableMessagesLane<Runtime, Instance, Lane>
-where
-	Runtime: MessagesConfig<Instance>,
-	Instance: 'static,
-	Lane: Get<LaneId>,
-{
-	type Instance = Instance;
-	type Id = Lane;
-}
-
-/// Refund calculator.
-pub trait RefundCalculator {
-	/// The underlying integer type in which the refund is calculated.
-	type Balance;
-
-	/// Compute refund for given transaction.
-	fn compute_refund(
-		info: &DispatchInfo,
-		post_info: &PostDispatchInfo,
-		len: usize,
-		tip: Self::Balance,
-	) -> Self::Balance;
-}
-
-/// `RefundCalculator` implementation which refunds the actual transaction fee.
-pub struct ActualFeeRefund<R>(PhantomData<R>);
-
-impl<R> RefundCalculator for ActualFeeRefund<R>
-where
-	R: TransactionPaymentConfig,
-	CallOf<R>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
-	BalanceOf<R>: FixedPointOperand,
-{
-	type Balance = BalanceOf<R>;
-
-	fn compute_refund(
-		info: &DispatchInfo,
-		post_info: &PostDispatchInfo,
-		len: usize,
-		tip: BalanceOf<R>,
-	) -> BalanceOf<R> {
-		pallet_transaction_payment::Pallet::<R>::compute_actual_fee(len as _, info, post_info, tip)
-	}
-}
+mod grandpa_adapter;
+mod messages_adapter;
+mod parachain_adapter;
+mod priority;
 
 /// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`.
 #[cfg_attr(test, derive(Debug, PartialEq))]
-pub struct PreDispatchData<AccountId> {
+pub struct PreDispatchData<AccountId, RemoteGrandpaChainBlockNumber: Debug> {
 	/// Transaction submitter (relayer) account.
 	relayer: AccountId,
 	/// Type of the call.
-	call_info: CallInfo,
-}
-
-/// Type of the call that the extension recognizes.
-#[derive(RuntimeDebugNoBound, PartialEq)]
-pub enum CallInfo {
-	/// Relay chain finality + parachain finality + message delivery/confirmation calls.
-	AllFinalityAndMsgs(
-		SubmitFinalityProofInfo<RelayBlockNumber>,
-		SubmitParachainHeadsInfo,
-		MessagesCallInfo,
-	),
-	/// Relay chain finality + message delivery/confirmation calls.
-	RelayFinalityAndMsgs(SubmitFinalityProofInfo<RelayBlockNumber>, MessagesCallInfo),
-	/// Parachain finality + message delivery/confirmation calls.
-	///
-	/// This variant is used only when bridging with parachain.
-	ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo),
-	/// Standalone message delivery/confirmation call.
-	Msgs(MessagesCallInfo),
+	call_info: ExtensionCallInfo<RemoteGrandpaChainBlockNumber>,
 }
 
-impl CallInfo {
-	/// Returns true if call is a message delivery call (with optional finality calls).
-	fn is_receive_messages_proof_call(&self) -> bool {
-		match self.messages_call_info() {
-			MessagesCallInfo::ReceiveMessagesProof(_) => true,
-			MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => false,
-		}
-	}
-
-	/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
-	fn submit_finality_proof_info(&self) -> Option<SubmitFinalityProofInfo<RelayBlockNumber>> {
-		match *self {
-			Self::AllFinalityAndMsgs(info, _, _) => Some(info),
-			Self::RelayFinalityAndMsgs(info, _) => Some(info),
-			_ => None,
-		}
-	}
-
+impl<AccountId, RemoteGrandpaChainBlockNumber: Debug>
+	PreDispatchData<AccountId, RemoteGrandpaChainBlockNumber>
+{
 	/// Returns mutable reference to pre-dispatch `finality_target` sent to the
 	/// `SubmitFinalityProof` call.
 	#[cfg(test)]
-	fn submit_finality_proof_info_mut(
+	pub fn submit_finality_proof_info_mut(
 		&mut self,
-	) -> Option<&mut SubmitFinalityProofInfo<RelayBlockNumber>> {
-		match *self {
-			Self::AllFinalityAndMsgs(ref mut info, _, _) => Some(info),
-			Self::RelayFinalityAndMsgs(ref mut info, _) => Some(info),
+	) -> Option<&mut bp_header_chain::SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>> {
+		match self.call_info {
+			ExtensionCallInfo::AllFinalityAndMsgs(ref mut info, _, _) => Some(info),
+			ExtensionCallInfo::RelayFinalityAndMsgs(ref mut info, _) => Some(info),
 			_ => None,
 		}
 	}
-
-	/// Returns the pre-dispatch `SubmitParachainHeadsInfo`.
-	fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> {
-		match self {
-			Self::AllFinalityAndMsgs(_, info, _) => Some(info),
-			Self::ParachainFinalityAndMsgs(info, _) => Some(info),
-			_ => None,
-		}
-	}
-
-	/// Returns the pre-dispatch `ReceiveMessagesProofInfo`.
-	fn messages_call_info(&self) -> &MessagesCallInfo {
-		match self {
-			Self::AllFinalityAndMsgs(_, _, info) => info,
-			Self::RelayFinalityAndMsgs(_, info) => info,
-			Self::ParachainFinalityAndMsgs(_, info) => info,
-			Self::Msgs(info) => info,
-		}
-	}
 }
 
 /// The actions on relayer account that need to be performed because of his actions.
@@ -229,78 +97,93 @@ pub enum RelayerAccountAction<AccountId, Reward> {
 	Slash(AccountId, RewardsAccountParams),
 }
 
-/// Everything common among our refund signed extensions.
-pub trait RefundSignedExtension:
-	'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
+/// A signed extension, built around `pallet-bridge-relayers`.
+///
+/// It may be incorporated into runtime to refund relayers for submitting correct
+/// message delivery and confirmation transactions, optionally batched with required
+/// finality proofs.
+#[derive(
+	DefaultNoBound,
+	CloneNoBound,
+	Decode,
+	Encode,
+	EqNoBound,
+	PartialEqNoBound,
+	RuntimeDebugNoBound,
+	TypeInfo,
+)]
+#[scale_info(skip_type_params(Runtime, Config))]
+pub struct BridgeRelayersSignedExtension<Runtime, Config>(PhantomData<(Runtime, Config)>);
+
+impl<R, C> BridgeRelayersSignedExtension<R, C>
+where
+	Self: 'static + Send + Sync,
+	R: RelayersConfig
+		+ BridgeMessagesConfig<C::BridgeMessagesPalletInstance>
+		+ TransactionPaymentConfig,
+	C: ExtensionConfig<Runtime = R, Reward = R::Reward>,
+	R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
+	<R as TransactionPaymentConfig>::OnChargeTransaction:
+		OnChargeTransaction<R, Balance = R::Reward>,
 {
-	/// This chain runtime.
-	type Runtime: MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
-		+ RelayersConfig;
-	/// Messages pallet and lane reference.
-	type Msgs: RefundableMessagesLaneId;
-	/// Refund amount calculator.
-	type Refund: RefundCalculator<Balance = <Self::Runtime as RelayersConfig>::Reward>;
-	/// Priority boost calculator.
-	type Priority: Get<TransactionPriority>;
-	/// Signed extension unique identifier.
-	type Id: StaticStrProvider;
-
-	/// Unpack batch runtime call.
-	fn expand_call(call: &CallOf<Self::Runtime>) -> Vec<&CallOf<Self::Runtime>>;
-
-	/// Given runtime call, check if it has supported format. Additionally, check if any of
-	/// (optionally batched) calls are obsolete and we shall reject the transaction.
-	fn parse_and_check_for_obsolete_call(
-		call: &CallOf<Self::Runtime>,
-	) -> Result<Option<CallInfo>, TransactionValidityError>;
-
-	/// Check if parsed call is already obsolete.
-	fn check_obsolete_parsed_call(
-		call: &CallOf<Self::Runtime>,
-	) -> Result<&CallOf<Self::Runtime>, TransactionValidityError>;
-
-	/// Called from post-dispatch and shall perform additional checks (apart from messages
-	/// transaction success) of given call result.
-	fn additional_call_result_check(
-		relayer: &AccountIdOf<Self::Runtime>,
-		call_info: &CallInfo,
-		extra_weight: &mut Weight,
-		extra_size: &mut u32,
-	) -> bool;
+	/// Returns number of bundled messages `Some(_)`, if the given call info is a:
+	///
+	/// - message delivery transaction;
+	///
+	/// - with reasonable bundled messages that may be accepted by the messages pallet.
+	///
+	/// This function is used to check whether the transaction priority should be
+	/// virtually boosted. The relayer registration (we only boost priority for registered
+	/// relayer transactions) must be checked outside.
+	fn bundled_messages_for_priority_boost(
+		call_info: Option<&ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber>>,
+	) -> Option<MessageNonce> {
+		// we only boost priority of message delivery transactions
+		let parsed_call = match call_info {
+			Some(parsed_call) if parsed_call.is_receive_messages_proof_call() => parsed_call,
+			_ => return None,
+		};
+
+		// compute total number of messages in transaction
+		let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len();
+
+		// a quick check to avoid invalid high-priority transactions
+		let max_unconfirmed_messages_in_confirmation_tx = <R as BridgeMessagesConfig<C::BridgeMessagesPalletInstance>>::BridgedChain
+			::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
+		if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
+			return None
+		}
+
+		Some(bundled_messages)
+	}
 
 	/// Given post-dispatch information, analyze the outcome of relayer call and return
 	/// actions that need to be performed on relayer account.
 	fn analyze_call_result(
-		pre: Option<Option<PreDispatchData<AccountIdOf<Self::Runtime>>>>,
+		pre: Option<Option<PreDispatchData<R::AccountId, C::RemoteGrandpaChainBlockNumber>>>,
 		info: &DispatchInfo,
 		post_info: &PostDispatchInfo,
 		len: usize,
 		result: &DispatchResult,
-	) -> RelayerAccountAction<AccountIdOf<Self::Runtime>, <Self::Runtime as RelayersConfig>::Reward>
-	{
-		let mut extra_weight = Weight::zero();
-		let mut extra_size = 0;
-
+	) -> RelayerAccountAction<R::AccountId, R::Reward> {
 		// We don't refund anything for transactions that we don't support.
 		let (relayer, call_info) = match pre {
 			Some(Some(pre)) => (pre.relayer, pre.call_info),
 			_ => return RelayerAccountAction::None,
 		};
 
-		// now we know that the relayer either needs to be rewarded, or slashed
+		// now we know that the call is supported and we may need to reward or slash relayer
 		// => let's prepare the correspondent account that pays reward/receives slashed amount
-		let reward_account_params =
-			RewardsAccountParams::new(
-				<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
-				<Self::Runtime as MessagesConfig<
-					<Self::Msgs as RefundableMessagesLaneId>::Instance,
-				>>::BridgedChain::ID,
-				if call_info.is_receive_messages_proof_call() {
-					RewardsAccountOwner::ThisChain
-				} else {
-					RewardsAccountOwner::BridgedChain
-				},
-			);
+		let lane_id = call_info.messages_call_info().lane_id();
+		let reward_account_params = RewardsAccountParams::new(
+			lane_id,
+			<R as BridgeMessagesConfig<C::BridgeMessagesPalletInstance>>::BridgedChain::ID,
+			if call_info.is_receive_messages_proof_call() {
+				RewardsAccountOwner::ThisChain
+			} else {
+				RewardsAccountOwner::BridgedChain
+			},
+		);
 
 		// prepare return value for the case if the call has failed or it has not caused
 		// expected side effects (e.g. not all messages have been accepted)
@@ -323,37 +206,19 @@ pub trait RefundSignedExtension:
 		// We don't refund anything if the transaction has failed.
 		if let Err(e) = result {
 			log::trace!(
-				target: "runtime::bridge",
-				"{} via {:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
-				Self::Id::STR,
-				<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
+				target: LOG_TARGET,
+				"{}.{:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
+				Self::IDENTIFIER,
+				lane_id,
 				relayer,
 				e,
 			);
 			return slash_relayer_if_delivery_result
 		}
 
-		// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
-		// it contained. If this happens, we consider the transaction "helpful" and refund it.
-		let msgs_call_info = call_info.messages_call_info();
-		if !MessagesCallHelper::<Self::Runtime, <Self::Msgs as RefundableMessagesLaneId>::Instance>::was_successful(msgs_call_info) {
-			log::trace!(
-				target: "runtime::bridge",
-				"{} via {:?}: relayer {:?} has submitted invalid messages call",
-				Self::Id::STR,
-				<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
-				relayer,
-			);
-			return slash_relayer_if_delivery_result
-		}
-
-		// do additional checks
-		if !Self::additional_call_result_check(
-			&relayer,
-			&call_info,
-			&mut extra_weight,
-			&mut extra_size,
-		) {
+		// check whether the call has succeeded
+		let mut call_data = ExtensionCallData::default();
+		if !C::check_call_result(&call_info, &mut call_data, &relayer) {
 			return slash_relayer_if_delivery_result
 		}
 
@@ -365,81 +230,55 @@ pub trait RefundSignedExtension:
 		let tip = Zero::zero();
 
 		// decrease post-dispatch weight/size using extra weight/size that we know now
-		let post_info_len = len.saturating_sub(extra_size as usize);
-		let mut post_info_weight =
-			post_info.actual_weight.unwrap_or(info.weight).saturating_sub(extra_weight);
+		let post_info_len = len.saturating_sub(call_data.extra_size as usize);
+		let mut post_info_weight = post_info
+			.actual_weight
+			.unwrap_or(info.weight)
+			.saturating_sub(call_data.extra_weight);
 
 		// let's also replace the weight of slashing relayer with the weight of rewarding relayer
 		if call_info.is_receive_messages_proof_call() {
 			post_info_weight = post_info_weight.saturating_sub(
-				<Self::Runtime as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(),
+				<R as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(),
 			);
 		}
 
 		// compute the relayer refund
 		let mut post_info = *post_info;
 		post_info.actual_weight = Some(post_info_weight);
-		let refund = Self::Refund::compute_refund(info, &post_info, post_info_len, tip);
+		let refund = Self::compute_refund(info, &post_info, post_info_len, tip);
 
 		// we can finally reward relayer
 		RelayerAccountAction::Reward(relayer, reward_account_params, refund)
 	}
 
-	/// Returns number of bundled messages `Some(_)`, if the given call info is a:
-	///
-	/// - message delivery transaction;
-	///
-	/// - with reasonable bundled messages that may be accepted by the messages pallet.
-	///
-	/// This function is used to check whether the transaction priority should be
-	/// virtually boosted. The relayer registration (we only boost priority for registered
-	/// relayer transactions) must be checked outside.
-	fn bundled_messages_for_priority_boost(call_info: Option<&CallInfo>) -> Option<MessageNonce> {
-		// we only boost priority of message delivery transactions
-		let parsed_call = match call_info {
-			Some(parsed_call) if parsed_call.is_receive_messages_proof_call() => parsed_call,
-			_ => return None,
-		};
-
-		// compute total number of messages in transaction
-		let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len();
-
-		// a quick check to avoid invalid high-priority transactions
-		let max_unconfirmed_messages_in_confirmation_tx = <Self::Runtime as MessagesConfig<
-			<Self::Msgs as RefundableMessagesLaneId>::Instance,
-		>>::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
-		if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
-			return None
-		}
-
-		Some(bundled_messages)
+	/// Compute refund for the successful relayer transaction
+	fn compute_refund(
+		info: &DispatchInfo,
+		post_info: &PostDispatchInfo,
+		len: usize,
+		tip: R::Reward,
+	) -> R::Reward {
+		TransactionPaymentPallet::<R>::compute_actual_fee(len as _, info, post_info, tip)
 	}
 }
 
-/// Adapter that allow implementing `sp_runtime::traits::SignedExtension` for any
-/// `RefundSignedExtension`.
-#[derive(
-	DefaultNoBound,
-	CloneNoBound,
-	Decode,
-	Encode,
-	EqNoBound,
-	PartialEqNoBound,
-	RuntimeDebugNoBound,
-	TypeInfo,
-)]
-pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T);
-
-impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
+impl<R, C> SignedExtension for BridgeRelayersSignedExtension<R, C>
 where
-	CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
+	Self: 'static + Send + Sync,
+	R: RelayersConfig
+		+ BridgeMessagesConfig<C::BridgeMessagesPalletInstance>
+		+ TransactionPaymentConfig,
+	C: ExtensionConfig<Runtime = R, Reward = R::Reward>,
+	R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
+	<R as TransactionPaymentConfig>::OnChargeTransaction:
+		OnChargeTransaction<R, Balance = R::Reward>,
 {
-	const IDENTIFIER: &'static str = T::Id::STR;
-	type AccountId = AccountIdOf<T::Runtime>;
-	type Call = CallOf<T::Runtime>;
+	const IDENTIFIER: &'static str = C::IdProvider::STR;
+	type AccountId = R::AccountId;
+	type Call = R::RuntimeCall;
 	type AdditionalSigned = ();
-	type Pre = Option<PreDispatchData<AccountIdOf<T::Runtime>>>;
+	type Pre = Option<PreDispatchData<R::AccountId, C::RemoteGrandpaChainBlockNumber>>;
 
 	fn additional_signed(&self) -> Result<(), TransactionValidityError> {
 		Ok(())
@@ -457,33 +296,33 @@ where
 		// we're not calling `validate` from `pre_dispatch` directly because of performance
 		// reasons, so if you're adding some code that may fail here, please check if it needs
 		// to be added to the `pre_dispatch` as well
-		let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
+		let parsed_call = C::parse_and_check_for_obsolete_call(call)?;
 
 		// the following code just plays with transaction priority and never returns an error
 
 		// we only boost priority of presumably correct message delivery transactions
-		let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
+		let bundled_messages = match Self::bundled_messages_for_priority_boost(parsed_call.as_ref())
+		{
 			Some(bundled_messages) => bundled_messages,
 			None => return Ok(Default::default()),
 		};
 
 		// we only boost priority if relayer has staked required balance
-		if !RelayersPallet::<T::Runtime>::is_registration_active(who) {
+		if !RelayersPallet::<R>::is_registration_active(who) {
 			return Ok(Default::default())
 		}
 
 		// compute priority boost
-		let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::<
-			T::Priority,
-		>(bundled_messages);
+		let priority_boost =
+			priority::compute_priority_boost::<C::PriorityBoostPerMessage>(bundled_messages);
 		let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
 
 		log::trace!(
-			target: "runtime::bridge",
-			"{} via {:?} has boosted priority of message delivery transaction \
+			target: LOG_TARGET,
+			"{}.{:?}: has boosted priority of message delivery transaction \
 			of relayer {:?}: {} messages -> {} priority",
 			Self::IDENTIFIER,
-			<T::Msgs as RefundableMessagesLaneId>::Id::get(),
+			parsed_call.as_ref().map(|p| p.messages_call_info().lane_id()),
 			who,
 			bundled_messages,
 			priority_boost,
@@ -500,14 +339,14 @@ where
 		_len: usize,
 	) -> Result<Self::Pre, TransactionValidityError> {
 		// this is a relevant piece of `validate` that we need here (in `pre_dispatch`)
-		let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
+		let parsed_call = C::parse_and_check_for_obsolete_call(call)?;
 
 		Ok(parsed_call.map(|call_info| {
 			log::trace!(
-				target: "runtime::bridge",
-				"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
+				target: LOG_TARGET,
+				"{}.{:?}: parsed bridge transaction in pre-dispatch: {:?}",
 				Self::IDENTIFIER,
-				<T::Msgs as RefundableMessagesLaneId>::Id::get(),
+				call_info.messages_call_info().lane_id(),
 				call_info,
 			);
 			PreDispatchData { relayer: who.clone(), call_info }
@@ -521,28 +360,28 @@ where
 		len: usize,
 		result: &DispatchResult,
 	) -> Result<(), TransactionValidityError> {
-		let call_result = T::analyze_call_result(pre, info, post_info, len, result);
+		let lane_id = pre
+			.as_ref()
+			.and_then(|p| p.as_ref())
+			.map(|p| p.call_info.messages_call_info().lane_id());
+		let call_result = Self::analyze_call_result(pre, info, post_info, len, result);
 
 		match call_result {
 			RelayerAccountAction::None => (),
 			RelayerAccountAction::Reward(relayer, reward_account, reward) => {
-				RelayersPallet::<T::Runtime>::register_relayer_reward(
-					reward_account,
-					&relayer,
-					reward,
-				);
+				RelayersPallet::<R>::register_relayer_reward(reward_account, &relayer, reward);
 
 				log::trace!(
-					target: "runtime::bridge",
-					"{} via {:?} has registered reward: {:?} for {:?}",
+					target: LOG_TARGET,
+					"{}.{:?}: has registered reward: {:?} for {:?}",
 					Self::IDENTIFIER,
-					<T::Msgs as RefundableMessagesLaneId>::Id::get(),
+					lane_id,
 					reward,
 					relayer,
 				);
 			},
 			RelayerAccountAction::Slash(relayer, slash_account) =>
-				RelayersPallet::<T::Runtime>::slash_and_deregister(
+				RelayersPallet::<R>::slash_and_deregister(
 					&relayer,
 					ExplicitOrAccountParams::Params(slash_account),
 				),
@@ -552,411 +391,61 @@ where
 	}
 }
 
-/// Signed extension that refunds a relayer for new messages coming from a parachain.
-///
-/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
-/// with message delivery transaction. Batch may deliver either both relay chain header and
-/// parachain head, or just parachain head. Corresponding headers must be used in messages
-/// proof verification.
-///
-/// Extension does not refund transaction tip due to security reasons.
-#[derive(
-	DefaultNoBound,
-	CloneNoBound,
-	Decode,
-	Encode,
-	EqNoBound,
-	PartialEqNoBound,
-	RuntimeDebugNoBound,
-	TypeInfo,
-)]
-#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
-pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>(
-	PhantomData<(
-		// runtime with `frame-utility`, `pallet-bridge-grandpa`, `pallet-bridge-parachains`,
-		// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
-		Runtime,
-		// implementation of `RefundableParachainId` trait, which specifies the instance of
-		// the used `pallet-bridge-parachains` pallet and the bridged parachain id
-		Para,
-		// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
-		// the used `pallet-bridge-messages` pallet and the lane within this pallet
-		Msgs,
-		// implementation of the `RefundCalculator` trait, that is used to compute refund that
-		// we give to relayer for his transaction
-		Refund,
-		// getter for per-message `TransactionPriority` boost that we give to message
-		// delivery transactions
-		Priority,
-		// the runtime-unique identifier of this signed extension
-		Id,
-	)>,
-);
-
-impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension
-	for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
-where
-	Self: 'static + Send + Sync,
-	RefundBridgedGrandpaMessages<
-		Runtime,
-		Runtime::BridgesGrandpaPalletInstance,
-		Msgs,
-		Refund,
-		Priority,
-		Id,
-	>: 'static + Send + Sync,
-	Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
-		+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
-		+ ParachainsConfig<Para::Instance>
-		+ MessagesConfig<Msgs::Instance>
-		+ RelayersConfig,
-	Para: RefundableParachainId,
-	Msgs: RefundableMessagesLaneId,
-	Refund: RefundCalculator<Balance = Runtime::Reward>,
-	Priority: Get<TransactionPriority>,
-	Id: StaticStrProvider,
-	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
-		+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
-		+ ParachainsCallSubType<Runtime, Para::Instance>
-		+ MessagesCallSubType<Runtime, Msgs::Instance>,
-{
-	type Runtime = Runtime;
-	type Msgs = Msgs;
-	type Refund = Refund;
-	type Priority = Priority;
-	type Id = Id;
-
-	fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
-		match call.is_sub_type() {
-			Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 3 =>
-				calls.iter().collect(),
-			Some(_) => vec![],
-			None => vec![call],
-		}
-	}
-
-	fn parse_and_check_for_obsolete_call(
-		call: &CallOf<Runtime>,
-	) -> Result<Option<CallInfo>, TransactionValidityError> {
-		let calls = Self::expand_call(call);
-		let total_calls = calls.len();
-		let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
-
-		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
-		let para_finality_call = calls
-			.next()
-			.transpose()?
-			.and_then(|c| c.submit_parachain_heads_info_for(Para::BridgedChain::PARACHAIN_ID));
-		let relay_finality_call =
-			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
-
-		Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
-			(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) => Some(
-				CallInfo::AllFinalityAndMsgs(relay_finality_call, para_finality_call, msgs_call),
-			),
-			(2, None, Some(para_finality_call), Some(msgs_call)) =>
-				Some(CallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
-			(1, None, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
-			_ => None,
-		})
-	}
-
-	fn check_obsolete_parsed_call(
-		call: &CallOf<Runtime>,
-	) -> Result<&CallOf<Runtime>, TransactionValidityError> {
-		call.check_obsolete_submit_finality_proof()?;
-		call.check_obsolete_submit_parachain_heads()?;
-		call.check_obsolete_call()?;
-		Ok(call)
-	}
-
-	fn additional_call_result_check(
-		relayer: &Runtime::AccountId,
-		call_info: &CallInfo,
-		extra_weight: &mut Weight,
-		extra_size: &mut u32,
-	) -> bool {
-		// check if relay chain state has been updated
-		let is_grandpa_call_successful =
-			RefundBridgedGrandpaMessages::<
-				Runtime,
-				Runtime::BridgesGrandpaPalletInstance,
-				Msgs,
-				Refund,
-				Priority,
-				Id,
-			>::additional_call_result_check(relayer, call_info, extra_weight, extra_size);
-		if !is_grandpa_call_successful {
-			return false
-		}
-
-		// check if parachain state has been updated
-		if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
-			if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
-				para_proof_info,
-			) {
-				// we only refund relayer if all calls have updated chain state
-				log::trace!(
-					target: "runtime::bridge",
-					"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
-					Id::STR,
-					Para::BridgedChain::PARACHAIN_ID,
-					Msgs::Id::get(),
-					relayer,
-				);
-				return false
-			}
-		}
-
-		true
-	}
-}
-
-/// Signed extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
-/// chain.
-///
-/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
-/// with message delivery transaction. Batch may deliver either both relay chain header and
-/// parachain head, or just parachain head. Corresponding headers must be used in messages
-/// proof verification.
-///
-/// Extension does not refund transaction tip due to security reasons.
-#[derive(
-	DefaultNoBound,
-	CloneNoBound,
-	Decode,
-	Encode,
-	EqNoBound,
-	PartialEqNoBound,
-	RuntimeDebugNoBound,
-	TypeInfo,
-)]
-#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
-pub struct RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>(
-	PhantomData<(
-		// runtime with `frame-utility`, `pallet-bridge-grandpa`,
-		// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
-		Runtime,
-		// bridge GRANDPA pallet instance, used to track bridged chain state
-		GrandpaInstance,
-		// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
-		// the used `pallet-bridge-messages` pallet and the lane within this pallet
-		Msgs,
-		// implementation of the `RefundCalculator` trait, that is used to compute refund that
-		// we give to relayer for his transaction
-		Refund,
-		// getter for per-message `TransactionPriority` boost that we give to message
-		// delivery transactions
-		Priority,
-		// the runtime-unique identifier of this signed extension
-		Id,
-	)>,
-);
-
-impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundSignedExtension
-	for RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>
-where
-	Self: 'static + Send + Sync,
-	Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
-		+ BoundedBridgeGrandpaConfig<GrandpaInstance>
-		+ MessagesConfig<Msgs::Instance>
-		+ RelayersConfig,
-	GrandpaInstance: 'static,
-	Msgs: RefundableMessagesLaneId,
-	Refund: RefundCalculator<Balance = Runtime::Reward>,
-	Priority: Get<TransactionPriority>,
-	Id: StaticStrProvider,
-	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
-		+ GrandpaCallSubType<Runtime, GrandpaInstance>
-		+ MessagesCallSubType<Runtime, Msgs::Instance>,
-{
-	type Runtime = Runtime;
-	type Msgs = Msgs;
-	type Refund = Refund;
-	type Priority = Priority;
-	type Id = Id;
-
-	fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
-		match call.is_sub_type() {
-			Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 2 =>
-				calls.iter().collect(),
-			Some(_) => vec![],
-			None => vec![call],
-		}
-	}
-
-	fn parse_and_check_for_obsolete_call(
-		call: &CallOf<Runtime>,
-	) -> Result<Option<CallInfo>, TransactionValidityError> {
-		let calls = Self::expand_call(call);
-		let total_calls = calls.len();
-		let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
-
-		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
-		let relay_finality_call =
-			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
-
-		Ok(match (total_calls, relay_finality_call, msgs_call) {
-			(2, Some(relay_finality_call), Some(msgs_call)) =>
-				Some(CallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
-			(1, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
-			_ => None,
-		})
-	}
-
-	fn check_obsolete_parsed_call(
-		call: &CallOf<Runtime>,
-	) -> Result<&CallOf<Runtime>, TransactionValidityError> {
-		call.check_obsolete_submit_finality_proof()?;
-		call.check_obsolete_call()?;
-		Ok(call)
-	}
-
-	fn additional_call_result_check(
-		relayer: &Runtime::AccountId,
-		call_info: &CallInfo,
-		extra_weight: &mut Weight,
-		extra_size: &mut u32,
-	) -> bool {
-		// check if relay chain state has been updated
-		if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
-			if !SubmitFinalityProofHelper::<Self::Runtime, GrandpaInstance>::was_successful(
-				finality_proof_info.block_number,
-			) {
-				// we only refund relayer if all calls have updated chain state
-				log::trace!(
-					target: "runtime::bridge",
-					"{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
-					Self::Id::STR,
-					<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
-					relayer,
-				);
-				return false
-			}
-
-			// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
-			// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
-			// `Pays::No`, because such transaction is mandatory for operating the bridge. But
-			// `utility.batchAll` transaction always requires payment. But in both cases we'll
-			// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
-			// to submit dedicated transaction.
-
-			// submitter has means to include extra weight/bytes in the `submit_finality_proof`
-			// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
-			*extra_weight = (*extra_weight).saturating_add(finality_proof_info.extra_weight);
-			*extra_size = (*extra_size).saturating_add(finality_proof_info.extra_size);
-		}
-
-		true
-	}
-}
-
-/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
-/// transactions. Finality transactions are not refunded.
-#[derive(
-	DefaultNoBound,
-	CloneNoBound,
-	Decode,
-	Encode,
-	EqNoBound,
-	PartialEqNoBound,
-	RuntimeDebugNoBound,
-	TypeInfo,
-)]
-#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
-pub struct RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>(
-	PhantomData<(
-		// runtime with `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
-		Runtime,
-		// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
-		// the used `pallet-bridge-messages` pallet and the lane within this pallet
-		Msgs,
-		// implementation of the `RefundCalculator` trait, that is used to compute refund that
-		// we give to relayer for his transaction
-		Refund,
-		// getter for per-message `TransactionPriority` boost that we give to message
-		// delivery transactions
-		Priority,
-		// the runtime-unique identifier of this signed extension
-		Id,
-	)>,
-);
-
-impl<Runtime, Msgs, Refund, Priority, Id> RefundSignedExtension
-	for RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>
+/// Verify that the messages pallet call, supported by extension has succeeded.
+pub(crate) fn verify_messages_call_succeeded<C, MI>(
+	call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber>,
+	_call_data: &mut ExtensionCallData,
+	relayer: &<C::Runtime as SystemConfig>::AccountId,
+) -> bool
 where
-	Self: 'static + Send + Sync,
-	Runtime: MessagesConfig<Msgs::Instance> + RelayersConfig,
-	Msgs: RefundableMessagesLaneId,
-	Refund: RefundCalculator<Balance = Runtime::Reward>,
-	Priority: Get<TransactionPriority>,
-	Id: StaticStrProvider,
-	CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ MessagesCallSubType<Runtime, Msgs::Instance>,
+	C: ExtensionConfig,
+	MI: 'static,
+	C::Runtime: BridgeMessagesConfig<MI>,
 {
-	type Runtime = Runtime;
-	type Msgs = Msgs;
-	type Refund = Refund;
-	type Priority = Priority;
-	type Id = Id;
-
-	fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
-		vec![call]
-	}
+	let messages_call = call_info.messages_call_info();
 
-	fn parse_and_check_for_obsolete_call(
-		call: &CallOf<Runtime>,
-	) -> Result<Option<CallInfo>, TransactionValidityError> {
-		let call = Self::check_obsolete_parsed_call(call)?;
-		Ok(call.call_info_for(Msgs::Id::get()).map(CallInfo::Msgs))
-	}
-
-	fn check_obsolete_parsed_call(
-		call: &CallOf<Runtime>,
-	) -> Result<&CallOf<Runtime>, TransactionValidityError> {
-		call.check_obsolete_call()?;
-		Ok(call)
+	if !MessagesCallHelper::<C::Runtime, MI>::was_successful(messages_call) {
+		log::trace!(
+			target: LOG_TARGET,
+			"{}.{:?}: relayer {:?} has submitted invalid messages call",
+			C::IdProvider::STR,
+			call_info.messages_call_info().lane_id(),
+			relayer,
+		);
+		return false
 	}
 
-	fn additional_call_result_check(
-		_relayer: &Runtime::AccountId,
-		_call_info: &CallInfo,
-		_extra_weight: &mut Weight,
-		_extra_size: &mut u32,
-	) -> bool {
-		// everything is checked by the `RefundTransactionExtension`
-		true
-	}
+	true
 }
 
 #[cfg(test)]
-pub(crate) mod tests {
+mod tests {
 	use super::*;
 	use crate::mock::*;
-	use bp_header_chain::StoredHeaderDataBuilder;
+
+	use bp_header_chain::{StoredHeaderDataBuilder, SubmitFinalityProofInfo};
 	use bp_messages::{
 		source_chain::FromBridgedChainMessagesDeliveryProof,
-		target_chain::FromBridgedChainMessagesProof, BaseMessagesProofInfo, DeliveredMessages,
-		InboundLaneData, LaneState, MessageNonce, MessagesOperatingMode, OutboundLaneData,
-		ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayer,
-		UnrewardedRelayerOccupation, UnrewardedRelayersState,
+		target_chain::FromBridgedChainMessagesProof, BaseMessagesProofInfo, InboundLaneData,
+		LaneId, MessageNonce, MessagesCallInfo, MessagesOperatingMode, OutboundLaneData,
+		ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayerOccupation,
+		UnrewardedRelayersState,
 	};
-	use bp_parachains::{BestParaHeadHash, ParaInfo};
+	use bp_parachains::{BestParaHeadHash, ParaInfo, SubmitParachainHeadsInfo};
 	use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
-	use bp_runtime::{BasicOperatingMode, HeaderId};
+	use bp_relayers::RuntimeWithUtilityPallet;
+	use bp_runtime::{BasicOperatingMode, HeaderId, Parachain};
 	use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
 	use frame_support::{
+		__private::sp_tracing,
 		assert_storage_noop, parameter_types,
 		traits::{fungible::Mutate, ReservableCurrency},
 		weights::Weight,
 	};
 	use pallet_bridge_grandpa::{Call as GrandpaCall, Pallet as GrandpaPallet, StoredAuthoritySet};
 	use pallet_bridge_messages::{Call as MessagesCall, Pallet as MessagesPallet};
-	use pallet_bridge_parachains::{
-		Call as ParachainsCall, Pallet as ParachainsPallet, RelayBlockHash,
-	};
+	use pallet_bridge_parachains::{Call as ParachainsCall, Pallet as ParachainsPallet};
+	use pallet_utility::Call as UtilityCall;
 	use sp_runtime::{
 		traits::{ConstU64, Header as HeaderT},
 		transaction_validity::{InvalidTransaction, ValidTransaction},
@@ -964,7 +453,7 @@ pub(crate) mod tests {
 	};
 
 	parameter_types! {
-		TestParachain: u32 = 1000;
+		TestParachain: u32 = BridgedUnderlyingParachain::PARACHAIN_ID;
 		pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
 			test_lane_id(),
 			TEST_BRIDGED_CHAIN_ID,
@@ -980,35 +469,32 @@ pub(crate) mod tests {
 
 	bp_runtime::generate_static_str_provider!(TestExtension);
 
-	type TestMessagesExtensionProvider = RefundBridgedMessages<
-		TestRuntime,
-		RefundableMessagesLane<TestRuntime, (), TestLaneId>,
-		ActualFeeRefund<TestRuntime>,
-		ConstU64<1>,
-		StrTestExtension,
-	>;
-	type TestMessagesExtension = RefundSignedExtensionAdapter<TestMessagesExtensionProvider>;
-	type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
+	type TestGrandpaExtensionConfig = grandpa_adapter::WithGrandpaChainExtensionConfig<
+		StrTestId,
 		TestRuntime,
+		RuntimeWithUtilityPallet<TestRuntime>,
+		(),
 		(),
-		RefundableMessagesLane<TestRuntime, (), TestLaneId>,
-		ActualFeeRefund<TestRuntime>,
 		ConstU64<1>,
-		StrTestExtension,
 	>;
-	type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
-	type TestExtensionProvider = RefundBridgedParachainMessages<
+	type TestGrandpaExtension =
+		BridgeRelayersSignedExtension<TestRuntime, TestGrandpaExtensionConfig>;
+	type TestExtensionConfig = parachain_adapter::WithParachainExtensionConfig<
+		StrTestId,
 		TestRuntime,
-		RefundableParachain<(), BridgedUnderlyingParachain>,
-		RefundableMessagesLane<TestRuntime, (), TestLaneId>,
-		ActualFeeRefund<TestRuntime>,
+		RuntimeWithUtilityPallet<TestRuntime>,
+		(),
+		(),
 		ConstU64<1>,
-		StrTestExtension,
 	>;
-	type TestExtension = RefundSignedExtensionAdapter<TestExtensionProvider>;
+	type TestExtension = BridgeRelayersSignedExtension<TestRuntime, TestExtensionConfig>;
+
+	fn test_lane_id() -> LaneId {
+		LaneId::new(1, 2)
+	}
 
 	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
-		let test_stake: ThisChainBalance = TestStake::get();
+		let test_stake: ThisChainBalance = Stake::get();
 		ExistentialDeposit::get().saturating_add(test_stake * 100)
 	}
 
@@ -1023,7 +509,7 @@ pub(crate) mod tests {
 		TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
 	}
 
-	pub fn relayer_account_at_this_chain() -> ThisChainAccountId {
+	fn relayer_account_at_this_chain() -> ThisChainAccountId {
 		0
 	}
 
@@ -1031,13 +517,13 @@ pub(crate) mod tests {
 		0
 	}
 
-	pub fn initialize_environment(
-		best_relay_header_number: RelayBlockNumber,
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+	fn initialize_environment(
+		best_relay_header_number: BridgedChainBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) {
 		let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
-		let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default());
+		let best_relay_header = HeaderId(best_relay_header_number, BridgedChainHash::default());
 		pallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
 			StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
 		);
@@ -1047,7 +533,7 @@ pub(crate) mod tests {
 			bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
 		);
 
-		let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID);
+		let para_id = ParaId(TestParachain::get());
 		let para_info = ParaInfo {
 			best_head_hash: BestParaHeadHash {
 				at_relay_block_number: parachain_head_at_relay_header_number,
@@ -1075,7 +561,7 @@ pub(crate) mod tests {
 		.unwrap();
 	}
 
-	fn submit_relay_header_call(relay_header_number: RelayBlockNumber) -> RuntimeCall {
+	fn submit_relay_header_call(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
 		let relay_header = BridgedChainHeader::new(
 			relay_header_number,
 			Default::default(),
@@ -1091,7 +577,7 @@ pub(crate) mod tests {
 		})
 	}
 
-	pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall {
+	fn submit_relay_header_call_ex(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
 		let relay_header = BridgedChainHeader::new(
 			relay_header_number,
 			Default::default(),
@@ -1110,12 +596,12 @@ pub(crate) mod tests {
 	}
 
 	fn submit_parachain_head_call(
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 	) -> RuntimeCall {
 		RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
-			at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
+			at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
 			parachains: vec![(
-				ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+				ParaId(TestParachain::get()),
 				[parachain_head_at_relay_header_number as u8; 32].into(),
 			)],
 			parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
@@ -1123,12 +609,12 @@ pub(crate) mod tests {
 	}
 
 	pub fn submit_parachain_head_call_ex(
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 	) -> RuntimeCall {
 		RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex {
-			at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
+			at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
 			parachains: vec![(
-				ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+				ParaId(TestParachain::get()),
 				[parachain_head_at_relay_header_number as u8; 32].into(),
 			)],
 			parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
@@ -1171,7 +657,7 @@ pub(crate) mod tests {
 	}
 
 	fn parachain_finality_and_delivery_batch_call(
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1183,7 +669,7 @@ pub(crate) mod tests {
 	}
 
 	fn parachain_finality_and_confirmation_batch_call(
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1195,7 +681,7 @@ pub(crate) mod tests {
 	}
 
 	fn relay_finality_and_delivery_batch_call(
-		relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1207,7 +693,7 @@ pub(crate) mod tests {
 	}
 
 	fn relay_finality_and_delivery_batch_call_ex(
-		relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1219,7 +705,7 @@ pub(crate) mod tests {
 	}
 
 	fn relay_finality_and_confirmation_batch_call(
-		relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1231,7 +717,7 @@ pub(crate) mod tests {
 	}
 
 	fn relay_finality_and_confirmation_batch_call_ex(
-		relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1243,8 +729,8 @@ pub(crate) mod tests {
 	}
 
 	fn all_finality_and_delivery_batch_call(
-		relay_header_number: RelayBlockNumber,
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1257,8 +743,8 @@ pub(crate) mod tests {
 	}
 
 	fn all_finality_and_delivery_batch_call_ex(
-		relay_header_number: RelayBlockNumber,
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1271,8 +757,8 @@ pub(crate) mod tests {
 	}
 
 	fn all_finality_and_confirmation_batch_call(
-		relay_header_number: RelayBlockNumber,
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1285,8 +771,8 @@ pub(crate) mod tests {
 	}
 
 	fn all_finality_and_confirmation_batch_call_ex(
-		relay_header_number: RelayBlockNumber,
-		parachain_head_at_relay_header_number: RelayBlockNumber,
+		relay_header_number: BridgedChainBlockNumber,
+		parachain_head_at_relay_header_number: BridgedChainBlockNumber,
 		best_message: MessageNonce,
 	) -> RuntimeCall {
 		RuntimeCall::Utility(UtilityCall::batch_all {
@@ -1298,10 +784,11 @@ pub(crate) mod tests {
 		})
 	}
 
-	fn all_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn all_finality_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::AllFinalityAndMsgs(
+			call_info: ExtensionCallInfo::AllFinalityAndMsgs(
 				SubmitFinalityProofInfo {
 					block_number: 200,
 					current_set_id: None,
@@ -1312,7 +799,7 @@ pub(crate) mod tests {
 				},
 				SubmitParachainHeadsInfo {
 					at_relay_block: HeaderId(200, [0u8; 32].into()),
-					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+					para_id: ParaId(TestParachain::get()),
 					para_head_hash: [200u8; 32].into(),
 					is_free_execution_expected: false,
 				},
@@ -1324,26 +811,28 @@ pub(crate) mod tests {
 					},
 					unrewarded_relayers: UnrewardedRelayerOccupation {
 						free_relayer_slots:
-							BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
 						free_message_slots:
-							BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
 					},
 				}),
 			),
 		}
 	}
 
-	fn all_finality_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
+	#[cfg(test)]
+	fn all_finality_pre_dispatch_data_ex(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		let mut data = all_finality_pre_dispatch_data();
-		data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
-			Some(TEST_GRANDPA_SET_ID);
+		data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
 		data
 	}
 
-	fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn all_finality_confirmation_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::AllFinalityAndMsgs(
+			call_info: ExtensionCallInfo::AllFinalityAndMsgs(
 				SubmitFinalityProofInfo {
 					block_number: 200,
 					current_set_id: None,
@@ -1354,7 +843,7 @@ pub(crate) mod tests {
 				},
 				SubmitParachainHeadsInfo {
 					at_relay_block: HeaderId(200, [0u8; 32].into()),
-					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+					para_id: ParaId(TestParachain::get()),
 					para_head_hash: [200u8; 32].into(),
 					is_free_execution_expected: false,
 				},
@@ -1369,17 +858,18 @@ pub(crate) mod tests {
 		}
 	}
 
-	fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
+	fn all_finality_confirmation_pre_dispatch_data_ex(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		let mut data = all_finality_confirmation_pre_dispatch_data();
-		data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
-			Some(TEST_GRANDPA_SET_ID);
+		data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
 		data
 	}
 
-	fn relay_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn relay_finality_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::RelayFinalityAndMsgs(
+			call_info: ExtensionCallInfo::RelayFinalityAndMsgs(
 				SubmitFinalityProofInfo {
 					block_number: 200,
 					current_set_id: None,
@@ -1396,26 +886,27 @@ pub(crate) mod tests {
 					},
 					unrewarded_relayers: UnrewardedRelayerOccupation {
 						free_relayer_slots:
-							BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
 						free_message_slots:
-							BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
 					},
 				}),
 			),
 		}
 	}
 
-	fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
+	fn relay_finality_pre_dispatch_data_ex(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		let mut data = relay_finality_pre_dispatch_data();
-		data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
-			Some(TEST_GRANDPA_SET_ID);
+		data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
 		data
 	}
 
-	fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn relay_finality_confirmation_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::RelayFinalityAndMsgs(
+			call_info: ExtensionCallInfo::RelayFinalityAndMsgs(
 				SubmitFinalityProofInfo {
 					block_number: 200,
 					current_set_id: None,
@@ -1435,20 +926,21 @@ pub(crate) mod tests {
 		}
 	}
 
-	fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
+	fn relay_finality_confirmation_pre_dispatch_data_ex(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		let mut data = relay_finality_confirmation_pre_dispatch_data();
-		data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
-			Some(TEST_GRANDPA_SET_ID);
+		data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
 		data
 	}
 
-	fn parachain_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn parachain_finality_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::ParachainFinalityAndMsgs(
+			call_info: ExtensionCallInfo::ParachainFinalityAndMsgs(
 				SubmitParachainHeadsInfo {
 					at_relay_block: HeaderId(200, [0u8; 32].into()),
-					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+					para_id: ParaId(TestParachain::get()),
 					para_head_hash: [200u8; 32].into(),
 					is_free_execution_expected: false,
 				},
@@ -1460,22 +952,23 @@ pub(crate) mod tests {
 					},
 					unrewarded_relayers: UnrewardedRelayerOccupation {
 						free_relayer_slots:
-							BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
 						free_message_slots:
-							BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
 					},
 				}),
 			),
 		}
 	}
 
-	fn parachain_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn parachain_finality_confirmation_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::ParachainFinalityAndMsgs(
+			call_info: ExtensionCallInfo::ParachainFinalityAndMsgs(
 				SubmitParachainHeadsInfo {
 					at_relay_block: HeaderId(200, [0u8; 32].into()),
-					para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
+					para_id: ParaId(TestParachain::get()),
 					para_head_hash: [200u8; 32].into(),
 					is_free_execution_expected: false,
 				},
@@ -1490,10 +983,11 @@ pub(crate) mod tests {
 		}
 	}
 
-	fn delivery_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn delivery_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber>
+	{
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof(
+			call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof(
 				ReceiveMessagesProofInfo {
 					base: BaseMessagesProofInfo {
 						lane_id: test_lane_id(),
@@ -1502,19 +996,20 @@ pub(crate) mod tests {
 					},
 					unrewarded_relayers: UnrewardedRelayerOccupation {
 						free_relayer_slots:
-							BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
 						free_message_slots:
-							BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
+							BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
 					},
 				},
 			)),
 		}
 	}
 
-	fn confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
+	fn confirmation_pre_dispatch_data(
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		PreDispatchData {
 			relayer: relayer_account_at_this_chain(),
-			call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof(
+			call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof(
 				ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
 					lane_id: test_lane_id(),
 					bundled_range: 101..=200,
@@ -1525,14 +1020,14 @@ pub(crate) mod tests {
 	}
 
 	fn set_bundled_range_end(
-		mut pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
+		mut pre_dispatch_data: PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber>,
 		end: MessageNonce,
-	) -> PreDispatchData<ThisChainAccountId> {
+	) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber> {
 		let msg_info = match pre_dispatch_data.call_info {
-			CallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
-			CallInfo::RelayFinalityAndMsgs(_, ref mut info) => info,
-			CallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
-			CallInfo::Msgs(ref mut info) => info,
+			ExtensionCallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
+			ExtensionCallInfo::RelayFinalityAndMsgs(_, ref mut info) => info,
+			ExtensionCallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
+			ExtensionCallInfo::Msgs(ref mut info) => info,
 		};
 
 		if let MessagesCallInfo::ReceiveMessagesProof(ref mut msg_info) = msg_info {
@@ -1543,22 +1038,21 @@ pub(crate) mod tests {
 	}
 
 	fn run_validate(call: RuntimeCall) -> TransactionValidity {
-		let extension: TestExtension =
-			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+		let extension: TestExtension = BridgeRelayersSignedExtension(PhantomData);
 		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
-		let extension: TestGrandpaExtension =
-			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
+		let extension: TestGrandpaExtension = BridgeRelayersSignedExtension(PhantomData);
 		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
-	fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
-		let extension: TestMessagesExtension =
-			RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
-		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
-	}
+	// TODO:(bridges-v2) - most of that stuff was introduced with free header execution: https://github.com/paritytech/polkadot-sdk/pull/4102
+	// fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
+	// 	let extension: TestMessagesExtension =
+	// 		RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
+	// 	extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
+	// }
 
 	fn ignore_priority(tx: TransactionValidity) -> TransactionValidity {
 		tx.map(|mut tx| {
@@ -1569,27 +1063,33 @@ pub(crate) mod tests {
 
 	fn run_pre_dispatch(
 		call: RuntimeCall,
-	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
-		let extension: TestExtension =
-			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+	) -> Result<
+		Option<PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber>>,
+		TransactionValidityError,
+	> {
+		sp_tracing::try_init_simple();
+		let extension: TestExtension = BridgeRelayersSignedExtension(PhantomData);
 		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_grandpa_pre_dispatch(
 		call: RuntimeCall,
-	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
-		let extension: TestGrandpaExtension =
-			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
+	) -> Result<
+		Option<PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber>>,
+		TransactionValidityError,
+	> {
+		let extension: TestGrandpaExtension = BridgeRelayersSignedExtension(PhantomData);
 		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
-	fn run_messages_pre_dispatch(
-		call: RuntimeCall,
-	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
-		let extension: TestMessagesExtension =
-			RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
-		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
-	}
+	// TODO:(bridges-v2) - most of that stuff was introduced with free header execution: https://github.com/paritytech/polkadot-sdk/pull/4102
+	// fn run_messages_pre_dispatch(
+	// 	call: RuntimeCall,
+	// ) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
+	// 	let extension: TestMessagesExtension =
+	// 		RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
+	// 	extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
+	// }
 
 	fn dispatch_info() -> DispatchInfo {
 		DispatchInfo {
@@ -1607,7 +1107,7 @@ pub(crate) mod tests {
 	}
 
 	fn run_post_dispatch(
-		pre_dispatch_data: Option<PreDispatchData<ThisChainAccountId>>,
+		pre_dispatch_data: Option<PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber>>,
 		dispatch_result: DispatchResult,
 	) {
 		let post_dispatch_result = TestExtension::post_dispatch(
@@ -1649,46 +1149,28 @@ pub(crate) mod tests {
 			Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get());
 
 			// message delivery is failing
-			let fns = [run_validate, run_grandpa_validate, run_messages_validate];
-			for f in fns {
-				assert_eq!(f(message_delivery_call(200)), Ok(Default::default()),);
-				assert_eq!(
-					f(parachain_finality_and_delivery_batch_call(200, 200)),
-					Ok(Default::default()),
-				);
-				assert_eq!(
-					f(all_finality_and_delivery_batch_call(200, 200, 200)),
-					Ok(Default::default()),
-				);
-				assert_eq!(
-					f(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
-					Ok(Default::default()),
-				);
-			}
-
-			// message confirmation validation is passing
+			assert_eq!(run_validate(message_delivery_call(200)), Ok(Default::default()),);
 			assert_eq!(
-				ignore_priority(run_validate(message_confirmation_call(200))),
+				run_validate(parachain_finality_and_delivery_batch_call(200, 200)),
 				Ok(Default::default()),
 			);
 			assert_eq!(
-				ignore_priority(run_messages_validate(message_confirmation_call(200))),
+				run_validate(all_finality_and_delivery_batch_call(200, 200, 200)),
 				Ok(Default::default()),
 			);
+			// message confirmation validation is passing
 			assert_eq!(
-				ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
-					200, 200
-				))),
+				ignore_priority(run_validate(message_confirmation_call(200))),
 				Ok(Default::default()),
 			);
 			assert_eq!(
-				ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
-					200, 200, 200
+				ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
+					200, 200
 				))),
 				Ok(Default::default()),
 			);
 			assert_eq!(
-				ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
+				ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
 					200, 200, 200
 				))),
 				Ok(Default::default()),
@@ -1704,28 +1186,25 @@ pub(crate) mod tests {
 			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
 				.unwrap();
 
-			let fns = [run_validate, run_grandpa_validate, run_messages_validate];
-			for f in fns {
-				let priority_of_100_messages_delivery =
-					f(message_delivery_call(200)).unwrap().priority;
-				let priority_of_200_messages_delivery =
-					f(message_delivery_call(300)).unwrap().priority;
-				assert!(
-					priority_of_200_messages_delivery > priority_of_100_messages_delivery,
-					"Invalid priorities: {} for 200 messages vs {} for 100 messages",
-					priority_of_200_messages_delivery,
-					priority_of_100_messages_delivery,
-				);
+			let priority_of_100_messages_delivery =
+				run_validate(message_delivery_call(200)).unwrap().priority;
+			let priority_of_200_messages_delivery =
+				run_validate(message_delivery_call(300)).unwrap().priority;
+			assert!(
+				priority_of_200_messages_delivery > priority_of_100_messages_delivery,
+				"Invalid priorities: {} for 200 messages vs {} for 100 messages",
+				priority_of_200_messages_delivery,
+				priority_of_100_messages_delivery,
+			);
 
-				let priority_of_100_messages_confirmation =
-					f(message_confirmation_call(200)).unwrap().priority;
-				let priority_of_200_messages_confirmation =
-					f(message_confirmation_call(300)).unwrap().priority;
-				assert_eq!(
-					priority_of_100_messages_confirmation,
-					priority_of_200_messages_confirmation
-				);
-			}
+			let priority_of_100_messages_confirmation =
+				run_validate(message_confirmation_call(200)).unwrap().priority;
+			let priority_of_200_messages_confirmation =
+				run_validate(message_confirmation_call(300)).unwrap().priority;
+			assert_eq!(
+				priority_of_100_messages_confirmation,
+				priority_of_200_messages_confirmation
+			);
 		});
 	}
 
@@ -1737,26 +1216,23 @@ pub(crate) mod tests {
 			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
 				.unwrap();
 
-			let fns = [run_validate, run_grandpa_validate, run_messages_validate];
-			for f in fns {
-				let priority_of_max_messages_delivery = f(message_delivery_call(
-					100 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
-				))
-				.unwrap()
-				.priority;
-				let priority_of_more_than_max_messages_delivery = f(message_delivery_call(
-					100 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1,
-				))
-				.unwrap()
-				.priority;
+			let priority_of_max_messages_delivery = run_validate(message_delivery_call(
+				100 + BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
+			))
+			.unwrap()
+			.priority;
+			let priority_of_more_than_max_messages_delivery = run_validate(message_delivery_call(
+				100 + BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1,
+			))
+			.unwrap()
+			.priority;
 
-				assert!(
-					priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery,
-					"Invalid priorities: {} for MAX messages vs {} for MAX+1 messages",
-					priority_of_max_messages_delivery,
-					priority_of_more_than_max_messages_delivery,
-				);
-			}
+			assert!(
+				priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery,
+				"Invalid priorities: {} for MAX messages vs {} for MAX+1 messages",
+				priority_of_max_messages_delivery,
+				priority_of_more_than_max_messages_delivery,
+			);
 		});
 	}
 
@@ -1773,15 +1249,11 @@ pub(crate) mod tests {
 				ignore_priority(run_validate(message_confirmation_call(200))),
 				Ok(ValidTransaction::default()),
 			);
-
-			assert_eq!(
-				ignore_priority(run_messages_validate(message_delivery_call(200))),
-				Ok(ValidTransaction::default()),
-			);
-			assert_eq!(
-				ignore_priority(run_messages_validate(message_confirmation_call(200))),
-				Ok(ValidTransaction::default()),
-			);
+			// TODO:(bridges-v2) - most of that stuff was introduced with free header execution: https://github.com/paritytech/polkadot-sdk/pull/4102
+			// assert_eq!(
+			// 	ignore_priority(run_messages_validate(message_delivery_call(200))),
+			// 	Ok(ValidTransaction::default()),
+			// );
 
 			assert_eq!(
 				ignore_priority(run_validate(parachain_finality_and_delivery_batch_call(200, 200))),
@@ -1798,24 +1270,12 @@ pub(crate) mod tests {
 				ignore_priority(run_validate(all_finality_and_delivery_batch_call(200, 200, 200))),
 				Ok(ValidTransaction::default()),
 			);
-			assert_eq!(
-				ignore_priority(run_validate(all_finality_and_delivery_batch_call_ex(
-					200, 200, 200
-				))),
-				Ok(ValidTransaction::default()),
-			);
 			assert_eq!(
 				ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
 					200, 200, 200
 				))),
 				Ok(ValidTransaction::default()),
 			);
-			assert_eq!(
-				ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
-					200, 200, 200
-				))),
-				Ok(ValidTransaction::default()),
-			);
 		});
 	}
 
@@ -2101,13 +1561,10 @@ pub(crate) mod tests {
 			let call = RuntimeCall::Utility(UtilityCall::batch_all {
 				calls: vec![
 					RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
-						at_relay_block: (100, RelayBlockHash::default()),
+						at_relay_block: (100, BridgedChainHash::default()),
 						parachains: vec![
-							(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [1u8; 32].into()),
-							(
-								ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1),
-								[1u8; 32].into(),
-							),
+							(ParaId(TestParachain::get()), [1u8; 32].into()),
+							(ParaId(TestParachain::get() + 1), [1u8; 32].into()),
 						],
 						parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
 					}),
@@ -2248,7 +1705,7 @@ pub(crate) mod tests {
 			// now repeat the same with size+weight refund: we expect smaller reward
 			let mut pre_dispatch_data = all_finality_pre_dispatch_data();
 			match pre_dispatch_data.call_info {
-				CallInfo::AllFinalityAndMsgs(ref mut info, ..) => {
+				ExtensionCallInfo::AllFinalityAndMsgs(ref mut info, ..) => {
 					info.extra_weight.set_ref_time(
 						frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
 					);
@@ -2354,7 +1811,7 @@ pub(crate) mod tests {
 			let delivery_rewards_account_balance =
 				Balances::free_balance(delivery_rewards_account());
 
-			let test_stake: ThisChainBalance = TestStake::get();
+			let test_stake: ThisChainBalance = Stake::get();
 			Balances::set_balance(
 				&relayer_account_at_this_chain(),
 				ExistentialDeposit::get() + test_stake * 10,
@@ -2424,10 +1881,10 @@ pub(crate) mod tests {
 	}
 
 	fn run_analyze_call_result(
-		pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
+		pre_dispatch_data: PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber>,
 		dispatch_result: DispatchResult,
 	) -> RelayerAccountAction<ThisChainAccountId, ThisChainBalance> {
-		TestExtensionProvider::analyze_call_result(
+		TestExtension::analyze_call_result(
 			Some(Some(pre_dispatch_data)),
 			&dispatch_info(),
 			&post_dispatch_info(),
@@ -2491,148 +1948,6 @@ pub(crate) mod tests {
 		});
 	}
 
-	#[test]
-	fn messages_ext_only_parses_standalone_transactions() {
-		run_test(|| {
-			initialize_environment(100, 100, 100);
-
-			// relay + parachain + message delivery calls batch is ignored
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&all_finality_and_delivery_batch_call(200, 200, 200)
-				),
-				Ok(None),
-			);
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&all_finality_and_delivery_batch_call_ex(200, 200, 200)
-				),
-				Ok(None),
-			);
-
-			// relay + parachain + message confirmation calls batch is ignored
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&all_finality_and_confirmation_batch_call(200, 200, 200)
-				),
-				Ok(None),
-			);
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
-				),
-				Ok(None),
-			);
-
-			// parachain + message delivery call batch is ignored
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&parachain_finality_and_delivery_batch_call(200, 200)
-				),
-				Ok(None),
-			);
-
-			// parachain + message confirmation call batch is ignored
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&parachain_finality_and_confirmation_batch_call(200, 200)
-				),
-				Ok(None),
-			);
-
-			// relay + message delivery call batch is ignored
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&relay_finality_and_delivery_batch_call(200, 200)
-				),
-				Ok(None),
-			);
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&relay_finality_and_delivery_batch_call_ex(200, 200)
-				),
-				Ok(None),
-			);
-
-			// relay + message confirmation call batch is ignored
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&relay_finality_and_confirmation_batch_call(200, 200)
-				),
-				Ok(None),
-			);
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&relay_finality_and_confirmation_batch_call_ex(200, 200)
-				),
-				Ok(None),
-			);
-
-			// message delivery call batch is accepted
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&message_delivery_call(200)
-				),
-				Ok(Some(delivery_pre_dispatch_data().call_info)),
-			);
-
-			// message confirmation call batch is accepted
-			assert_eq!(
-				TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
-					&message_confirmation_call(200)
-				),
-				Ok(Some(confirmation_pre_dispatch_data().call_info)),
-			);
-		});
-	}
-
-	#[test]
-	fn messages_ext_rejects_calls_with_obsolete_messages() {
-		run_test(|| {
-			initialize_environment(100, 100, 100);
-
-			assert_eq!(
-				run_messages_pre_dispatch(message_delivery_call(100)),
-				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
-			);
-			assert_eq!(
-				run_messages_pre_dispatch(message_confirmation_call(100)),
-				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
-			);
-
-			assert_eq!(
-				run_messages_validate(message_delivery_call(100)),
-				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
-			);
-			assert_eq!(
-				run_messages_validate(message_confirmation_call(100)),
-				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
-			);
-		});
-	}
-
-	#[test]
-	fn messages_ext_accepts_calls_with_new_messages() {
-		run_test(|| {
-			initialize_environment(100, 100, 100);
-
-			assert_eq!(
-				run_messages_pre_dispatch(message_delivery_call(200)),
-				Ok(Some(delivery_pre_dispatch_data())),
-			);
-			assert_eq!(
-				run_messages_pre_dispatch(message_confirmation_call(200)),
-				Ok(Some(confirmation_pre_dispatch_data())),
-			);
-
-			assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),);
-			assert_eq!(
-				run_messages_validate(message_confirmation_call(200)),
-				Ok(Default::default()),
-			);
-		});
-	}
-
 	#[test]
 	fn grandpa_ext_only_parses_valid_batches() {
 		run_test(|| {
@@ -2640,35 +1955,23 @@ pub(crate) mod tests {
 
 			// relay + parachain + message delivery calls batch is ignored
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&all_finality_and_delivery_batch_call(200, 200, 200)
 				),
 				Ok(None),
 			);
-			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
-					&all_finality_and_delivery_batch_call_ex(200, 200, 200)
-				),
-				Ok(None),
-			);
 
 			// relay + parachain + message confirmation calls batch is ignored
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&all_finality_and_confirmation_batch_call(200, 200, 200)
 				),
 				Ok(None),
 			);
-			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
-					&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
-				),
-				Ok(None),
-			);
 
 			// parachain + message delivery call batch is ignored
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&parachain_finality_and_delivery_batch_call(200, 200)
 				),
 				Ok(None),
@@ -2676,7 +1979,7 @@ pub(crate) mod tests {
 
 			// parachain + message confirmation call batch is ignored
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&parachain_finality_and_confirmation_batch_call(200, 200)
 				),
 				Ok(None),
@@ -2684,35 +1987,23 @@ pub(crate) mod tests {
 
 			// relay + message delivery call batch is accepted
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&relay_finality_and_delivery_batch_call(200, 200)
 				),
 				Ok(Some(relay_finality_pre_dispatch_data().call_info)),
 			);
-			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
-					&relay_finality_and_delivery_batch_call_ex(200, 200)
-				),
-				Ok(Some(relay_finality_pre_dispatch_data_ex().call_info)),
-			);
 
 			// relay + message confirmation call batch is accepted
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&relay_finality_and_confirmation_batch_call(200, 200)
 				),
 				Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)),
 			);
-			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
-					&relay_finality_and_confirmation_batch_call_ex(200, 200)
-				),
-				Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex().call_info)),
-			);
 
 			// message delivery call batch is accepted
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&message_delivery_call(200)
 				),
 				Ok(Some(delivery_pre_dispatch_data().call_info)),
@@ -2720,7 +2011,7 @@ pub(crate) mod tests {
 
 			// message confirmation call batch is accepted
 			assert_eq!(
-				TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
+				TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
 					&message_confirmation_call(200)
 				),
 				Ok(Some(confirmation_pre_dispatch_data().call_info)),
@@ -2728,6 +2019,149 @@ pub(crate) mod tests {
 		});
 	}
 
+	// TODO:(bridges-v2) - most of that stuff was introduced with free header execution: https://github.com/paritytech/polkadot-sdk/pull/4102
+	// #[test]
+	// fn messages_ext_only_parses_standalone_transactions() {
+	// 	run_test(|| {
+	// 		initialize_environment(100, 100, 100);
+	//
+	// 		// relay + parachain + message delivery calls batch is ignored
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&all_finality_and_delivery_batch_call(200, 200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&all_finality_and_delivery_batch_call_ex(200, 200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	//
+	// 		// relay + parachain + message confirmation calls batch is ignored
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&all_finality_and_confirmation_batch_call(200, 200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	//
+	// 		// parachain + message delivery call batch is ignored
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&parachain_finality_and_delivery_batch_call(200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	//
+	// 		// parachain + message confirmation call batch is ignored
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&parachain_finality_and_confirmation_batch_call(200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	//
+	// 		// relay + message delivery call batch is ignored
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&relay_finality_and_delivery_batch_call(200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&relay_finality_and_delivery_batch_call_ex(200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	//
+	// 		// relay + message confirmation call batch is ignored
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&relay_finality_and_confirmation_batch_call(200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&relay_finality_and_confirmation_batch_call_ex(200, 200)
+	// 			),
+	// 			Ok(None),
+	// 		);
+	//
+	// 		// message delivery call batch is accepted
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&message_delivery_call(200)
+	// 			),
+	// 			Ok(Some(delivery_pre_dispatch_data().call_info)),
+	// 		);
+	//
+	// 		// message confirmation call batch is accepted
+	// 		assert_eq!(
+	// 			TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
+	// 				&message_confirmation_call(200)
+	// 			),
+	// 			Ok(Some(confirmation_pre_dispatch_data().call_info)),
+	// 		);
+	// 	});
+	// }
+	//
+	// #[test]
+	// fn messages_ext_rejects_calls_with_obsolete_messages() {
+	// 	run_test(|| {
+	// 		initialize_environment(100, 100, 100);
+	//
+	// 		assert_eq!(
+	// 			run_messages_pre_dispatch(message_delivery_call(100)),
+	// 			Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+	// 		);
+	// 		assert_eq!(
+	// 			run_messages_pre_dispatch(message_confirmation_call(100)),
+	// 			Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+	// 		);
+	//
+	// 		assert_eq!(
+	// 			run_messages_validate(message_delivery_call(100)),
+	// 			Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+	// 		);
+	// 		assert_eq!(
+	// 			run_messages_validate(message_confirmation_call(100)),
+	// 			Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
+	// 		);
+	// 	});
+	// }
+	//
+	// #[test]
+	// fn messages_ext_accepts_calls_with_new_messages() {
+	// 	run_test(|| {
+	// 		initialize_environment(100, 100, 100);
+	//
+	// 		assert_eq!(
+	// 			run_messages_pre_dispatch(message_delivery_call(200)),
+	// 			Ok(Some(delivery_pre_dispatch_data())),
+	// 		);
+	// 		assert_eq!(
+	// 			run_messages_pre_dispatch(message_confirmation_call(200)),
+	// 			Ok(Some(confirmation_pre_dispatch_data())),
+	// 		);
+	//
+	// 		assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),);
+	// 		assert_eq!(
+	// 			run_messages_validate(message_confirmation_call(200)),
+	// 			Ok(Default::default()),
+	// 		);
+	// 	});
+	// }
+
 	#[test]
 	fn grandpa_ext_rejects_batch_with_obsolete_relay_chain_header() {
 		run_test(|| {
@@ -2867,40 +2301,4 @@ pub(crate) mod tests {
 			);
 		});
 	}
-
-	#[test]
-	fn does_not_panic_on_boosting_priority_of_empty_message_delivery_transaction() {
-		run_test(|| {
-			let best_delivered_message =
-				BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
-			initialize_environment(100, 100, best_delivered_message);
-
-			// register relayer so it gets priority boost
-			BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
-				.unwrap();
-
-			// allow empty message delivery transactions
-			let lane_id = test_lane_id();
-			let in_lane_data = InboundLaneData {
-				state: LaneState::Opened,
-				last_confirmed_nonce: 0,
-				relayers: vec![UnrewardedRelayer {
-					relayer: relayer_account_at_bridged_chain(),
-					messages: DeliveredMessages { begin: 1, end: best_delivered_message },
-				}]
-				.into(),
-			};
-			pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
-
-			// now check that the priority of empty tx is the same as priority of 1-message tx
-			let priority_of_zero_messages_delivery =
-				run_validate(message_delivery_call(best_delivered_message)).unwrap().priority;
-			let priority_of_one_messages_delivery =
-				run_validate(message_delivery_call(best_delivered_message + 1))
-					.unwrap()
-					.priority;
-
-			assert_eq!(priority_of_zero_messages_delivery, priority_of_one_messages_delivery);
-		});
-	}
 }
diff --git a/bridges/modules/relayers/src/extension/parachain_adapter.rs b/bridges/modules/relayers/src/extension/parachain_adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b6f57cebc309dcf1d5e5c952395879f01933d188
--- /dev/null
+++ b/bridges/modules/relayers/src/extension/parachain_adapter.rs
@@ -0,0 +1,182 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common 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.
+
+// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Adapter that allows using `pallet-bridge-relayers` as a signed extension in the
+//! bridge with remote parachain.
+
+use crate::{
+	extension::{
+		grandpa_adapter::verify_submit_finality_proof_succeeded, verify_messages_call_succeeded,
+	},
+	Config as BridgeRelayersConfig, LOG_TARGET,
+};
+
+use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
+use bp_runtime::{Parachain, StaticStrProvider};
+use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
+use frame_system::Config as SystemConfig;
+use pallet_bridge_grandpa::{
+	CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
+};
+use pallet_bridge_messages::{
+	CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig,
+};
+use pallet_bridge_parachains::{
+	CallSubType as BridgeParachainsCallSubtype, Config as BridgeParachainsConfig,
+	SubmitParachainHeadsHelper,
+};
+use sp_runtime::{
+	traits::{Dispatchable, Get},
+	transaction_validity::{TransactionPriority, TransactionValidityError},
+};
+use sp_std::marker::PhantomData;
+
+/// Adapter to be used in signed extension configuration, when bridging with remote parachains.
+pub struct WithParachainExtensionConfig<
+	// signed extension identifier
+	IdProvider,
+	// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
+	// uses `BridgeParachainsConfig<BridgeParachainsPalletInstance>` to receive messages and
+	// confirmations from the remote chain.
+	Runtime,
+	// batch call unpacker
+	BatchCallUnpacker,
+	// instance of the `pallet-bridge-parachains`, tracked by this extension
+	BridgeParachainsPalletInstance,
+	// instance of BridgedChain `pallet-bridge-messages`, tracked by this extension
+	BridgeMessagesPalletInstance,
+	// message delivery transaction priority boost for every additional message
+	PriorityBoostPerMessage,
+>(
+	PhantomData<(
+		IdProvider,
+		Runtime,
+		BatchCallUnpacker,
+		BridgeParachainsPalletInstance,
+		BridgeMessagesPalletInstance,
+		PriorityBoostPerMessage,
+	)>,
+);
+
+impl<ID, R, BCU, PI, MI, P> ExtensionConfig for WithParachainExtensionConfig<ID, R, BCU, PI, MI, P>
+where
+	ID: StaticStrProvider,
+	R: BridgeRelayersConfig
+		+ BridgeMessagesConfig<MI>
+		+ BridgeParachainsConfig<PI>
+		+ BridgeGrandpaConfig<R::BridgesGrandpaPalletInstance>,
+	BCU: BatchCallUnpacker<R>,
+	PI: 'static,
+	MI: 'static,
+	P: Get<TransactionPriority>,
+	R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+		+ BridgeGrandpaCallSubtype<R, R::BridgesGrandpaPalletInstance>
+		+ BridgeParachainsCallSubtype<R, PI>
+		+ BridgeMessagesCallSubType<R, MI>,
+	<R as BridgeMessagesConfig<MI>>::BridgedChain: Parachain,
+{
+	type IdProvider = ID;
+	type Runtime = R;
+	type BridgeMessagesPalletInstance = MI;
+	type PriorityBoostPerMessage = P;
+	type Reward = R::Reward;
+	type RemoteGrandpaChainBlockNumber =
+		pallet_bridge_grandpa::BridgedBlockNumber<R, R::BridgesGrandpaPalletInstance>;
+
+	fn parse_and_check_for_obsolete_call(
+		call: &R::RuntimeCall,
+	) -> Result<
+		Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>>,
+		TransactionValidityError,
+	> {
+		let calls = BCU::unpack(call, 3);
+		let total_calls = calls.len();
+		let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
+
+		let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info());
+		let para_finality_call = calls.next().transpose()?.and_then(|c| {
+			let r = c.submit_parachain_heads_info_for(
+				<R as BridgeMessagesConfig<MI>>::BridgedChain::PARACHAIN_ID,
+			);
+			r
+		});
+		let relay_finality_call =
+			calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
+		Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
+			(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) =>
+				Some(ExtensionCallInfo::AllFinalityAndMsgs(
+					relay_finality_call,
+					para_finality_call,
+					msgs_call,
+				)),
+			(2, None, Some(para_finality_call), Some(msgs_call)) =>
+				Some(ExtensionCallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
+			(1, None, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
+			_ => None,
+		})
+	}
+
+	fn check_obsolete_parsed_call(
+		call: &R::RuntimeCall,
+	) -> Result<&R::RuntimeCall, TransactionValidityError> {
+		call.check_obsolete_submit_finality_proof()?;
+		call.check_obsolete_submit_parachain_heads()?;
+		call.check_obsolete_call()?;
+		Ok(call)
+	}
+
+	fn check_call_result(
+		call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>,
+		call_data: &mut ExtensionCallData,
+		relayer: &R::AccountId,
+	) -> bool {
+		verify_submit_finality_proof_succeeded::<Self, R::BridgesGrandpaPalletInstance>(
+			call_info, call_data, relayer,
+		) && verify_submit_parachain_head_succeeded::<Self, PI>(call_info, call_data, relayer) &&
+			verify_messages_call_succeeded::<Self, MI>(call_info, call_data, relayer)
+	}
+}
+
+/// If the batch call contains the parachain state update call, verify that it
+/// has been successful.
+///
+/// Only returns false when parachain state update call has failed.
+pub(crate) fn verify_submit_parachain_head_succeeded<C, PI>(
+	call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber>,
+	_call_data: &mut ExtensionCallData,
+	relayer: &<C::Runtime as SystemConfig>::AccountId,
+) -> bool
+where
+	C: ExtensionConfig,
+	PI: 'static,
+	C::Runtime: BridgeParachainsConfig<PI>,
+{
+	let Some(para_proof_info) = call_info.submit_parachain_heads_info() else { return true };
+
+	if !SubmitParachainHeadsHelper::<C::Runtime, PI>::was_successful(para_proof_info) {
+		// we only refund relayer if all calls have updated chain state
+		log::trace!(
+			target: LOG_TARGET,
+			"{}.{:?}: relayer {:?} has submitted invalid parachain finality proof",
+			C::IdProvider::STR,
+			call_info.messages_call_info().lane_id(),
+			relayer,
+		);
+		return false
+	}
+
+	true
+}
diff --git a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs b/bridges/modules/relayers/src/extension/priority.rs
similarity index 96%
rename from bridges/bin/runtime-common/src/extensions/priority_calculator.rs
rename to bridges/modules/relayers/src/extension/priority.rs
index 9f559dc13b64d3912f0d1679c21fa682034bdb8e..da188eaf5bdd4a270368c8dad867e77e591ed12c 100644
--- a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs
+++ b/bridges/modules/relayers/src/extension/priority.rs
@@ -50,7 +50,6 @@ mod integrity_tests {}
 #[cfg(feature = "integrity-test")]
 mod integrity_tests {
 	use super::{compute_priority_boost, ItemCount};
-	use crate::extensions::refund_relayer_extension::RefundableParachainId;
 
 	use bp_messages::MessageNonce;
 	use bp_runtime::PreComputedSize;
@@ -239,12 +238,18 @@ mod integrity_tests {
 		/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
 		/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
 		/// will be close to `TX2` as well.
-		pub fn ensure_priority_boost_is_sane<Runtime, RefundableParachain, PriorityBoostPerHeader>(
+		pub fn ensure_priority_boost_is_sane<
+			Runtime,
+			ParachainsInstance,
+			Para,
+			PriorityBoostPerHeader,
+		>(
 			tip_boost_per_header: BalanceOf<Runtime>,
 		) where
 			Runtime: pallet_transaction_payment::Config
-				+ pallet_bridge_parachains::Config<RefundableParachain::Instance>,
-			RefundableParachain: RefundableParachainId,
+				+ pallet_bridge_parachains::Config<ParachainsInstance>,
+			ParachainsInstance: 'static,
+			Para: Parachain,
 			PriorityBoostPerHeader: Get<TransactionPriority>,
 			Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
 			BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
@@ -263,7 +268,8 @@ mod integrity_tests {
 				|_n_headers, tip| {
 					estimate_parachain_header_submit_transaction_priority::<
 						Runtime,
-						RefundableParachain,
+						ParachainsInstance,
+						Para,
 					>(tip)
 				},
 			);
@@ -271,13 +277,18 @@ mod integrity_tests {
 
 		/// Estimate parachain header delivery transaction priority.
 		#[cfg(feature = "integrity-test")]
-		fn estimate_parachain_header_submit_transaction_priority<Runtime, RefundableParachain>(
+		fn estimate_parachain_header_submit_transaction_priority<
+			Runtime,
+			ParachainsInstance,
+			Para,
+		>(
 			tip: BalanceOf<Runtime>,
 		) -> TransactionPriority
 		where
 			Runtime: pallet_transaction_payment::Config
-				+ pallet_bridge_parachains::Config<RefundableParachain::Instance>,
-			RefundableParachain: RefundableParachainId,
+				+ pallet_bridge_parachains::Config<ParachainsInstance>,
+			ParachainsInstance: 'static,
+			Para: Parachain,
 			Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
 			BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
 		{
@@ -287,14 +298,14 @@ mod integrity_tests {
 			let base_tx_size = 512;
 			// let's say we are relaying largest parachain headers and proof takes some more bytes
 			let tx_call_size = <Runtime as pallet_bridge_parachains::Config<
-				RefundableParachain::Instance,
+				ParachainsInstance,
 			>>::WeightInfo::expected_extra_storage_proof_size()
-			.saturating_add(RefundableParachain::BridgedChain::MAX_HEADER_SIZE);
+			.saturating_add(Para::MAX_HEADER_SIZE);
 
 			// finally we are able to estimate transaction size and weight
 			let transaction_size = base_tx_size.saturating_add(tx_call_size);
 			let transaction_weight = <Runtime as pallet_bridge_parachains::Config<
-				RefundableParachain::Instance,
+				ParachainsInstance,
 			>>::WeightInfo::submit_parachain_heads_weight(
 				Runtime::DbWeight::get(),
 				&PreComputedSize(transaction_size as _),
diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs
index 114f8ca3fb1ceac67796dba8e5ced397841b6fd8..b9627774db1ec9fcc828713904e1e60e24a7ba1d 100644
--- a/bridges/modules/relayers/src/lib.rs
+++ b/bridges/modules/relayers/src/lib.rs
@@ -36,13 +36,13 @@ pub use stake_adapter::StakeAndSlashNamed;
 pub use weights::WeightInfo;
 pub use weights_ext::WeightInfoExt;
 
-pub mod benchmarking;
-
 mod mock;
 mod payment_adapter;
 mod stake_adapter;
 mod weights_ext;
 
+pub mod benchmarking;
+pub mod extension;
 pub mod weights;
 
 /// The target that will be used when publishing logs related to this pallet.
@@ -502,7 +502,7 @@ mod tests {
 				System::<TestRuntime>::events().last(),
 				Some(&EventRecord {
 					phase: Phase::Initialization,
-					event: TestEvent::Relayers(RewardRegistered {
+					event: TestEvent::BridgeRelayers(RewardRegistered {
 						relayer: REGULAR_RELAYER,
 						rewards_account_params: test_reward_account_param(),
 						reward: 100
@@ -581,7 +581,7 @@ mod tests {
 				System::<TestRuntime>::events().last(),
 				Some(&EventRecord {
 					phase: Phase::Initialization,
-					event: TestEvent::Relayers(RewardPaid {
+					event: TestEvent::BridgeRelayers(RewardPaid {
 						relayer: REGULAR_RELAYER,
 						rewards_account_params: test_reward_account_param(),
 						reward: 100
@@ -595,7 +595,8 @@ mod tests {
 	#[test]
 	fn pay_reward_from_account_actually_pays_reward() {
 		type Balances = pallet_balances::Pallet<TestRuntime>;
-		type PayLaneRewardFromAccount = bp_relayers::PayRewardFromAccount<Balances, AccountId>;
+		type PayLaneRewardFromAccount =
+			bp_relayers::PayRewardFromAccount<Balances, ThisChainAccountId>;
 
 		run_test(|| {
 			let in_lane_0 = RewardsAccountParams::new(
@@ -676,7 +677,7 @@ mod tests {
 				System::<TestRuntime>::events().last(),
 				Some(&EventRecord {
 					phase: Phase::Initialization,
-					event: TestEvent::Relayers(Event::RegistrationUpdated {
+					event: TestEvent::BridgeRelayers(Event::RegistrationUpdated {
 						relayer: REGISTER_RELAYER,
 						registration: Registration { valid_till: 150, stake: Stake::get() },
 					}),
@@ -744,7 +745,7 @@ mod tests {
 				System::<TestRuntime>::events().last(),
 				Some(&EventRecord {
 					phase: Phase::Initialization,
-					event: TestEvent::Relayers(Event::RegistrationUpdated {
+					event: TestEvent::BridgeRelayers(Event::RegistrationUpdated {
 						relayer: REGISTER_RELAYER,
 						registration: Registration { valid_till: 150, stake: Stake::get() }
 					}),
@@ -808,7 +809,7 @@ mod tests {
 				System::<TestRuntime>::events().last(),
 				Some(&EventRecord {
 					phase: Phase::Initialization,
-					event: TestEvent::Relayers(Event::RegistrationUpdated {
+					event: TestEvent::BridgeRelayers(Event::RegistrationUpdated {
 						relayer: REGISTER_RELAYER,
 						registration: Registration { valid_till: 150, stake: Stake::get() }
 					}),
@@ -870,7 +871,9 @@ mod tests {
 				System::<TestRuntime>::events().last(),
 				Some(&EventRecord {
 					phase: Phase::Initialization,
-					event: TestEvent::Relayers(Event::Deregistered { relayer: REGISTER_RELAYER }),
+					event: TestEvent::BridgeRelayers(Event::Deregistered {
+						relayer: REGISTER_RELAYER
+					}),
 					topics: vec![],
 				}),
 			);
diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs
index 81993589de6193673695965cc867fbfe327a721f..43aeec8bfc3b4b480e838ba5d35566a3384b9bf9 100644
--- a/bridges/modules/relayers/src/mock.rs
+++ b/bridges/modules/relayers/src/mock.rs
@@ -18,51 +18,187 @@
 
 use crate as pallet_bridge_relayers;
 
-use bp_messages::LaneId;
+use bp_header_chain::ChainWithGrandpa;
+use bp_messages::{
+	target_chain::{DispatchMessage, MessageDispatch},
+	ChainWithMessages, LaneId, MessageNonce,
+};
+use bp_parachains::SingleParaStoredHeaderDataBuilder;
 use bp_relayers::{
 	PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams,
 };
+use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Parachain};
+use codec::Encode;
 use frame_support::{
-	derive_impl, parameter_types, traits::fungible::Mutate, weights::RuntimeDbWeight,
+	derive_impl, parameter_types,
+	traits::fungible::Mutate,
+	weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
+};
+use pallet_transaction_payment::Multiplier;
+use sp_core::{ConstU64, ConstU8, H256};
+use sp_runtime::{
+	traits::{BlakeTwo256, ConstU32},
+	BuildStorage, FixedPointNumber, Perquintill, StateVersion,
 };
-use sp_runtime::BuildStorage;
 
-pub type AccountId = u64;
-pub type Balance = u64;
-pub type BlockNumber = u64;
+// generate identifier of the signed extension
+bp_runtime::generate_static_str_provider!(TestId);
+
+/// Account identifier at `ThisChain`.
+pub type ThisChainAccountId = u64;
+/// Balance at `ThisChain`.
+pub type ThisChainBalance = u64;
+/// Block number at `ThisChain`.
+pub type ThisChainBlockNumber = u32;
+/// Hash at `ThisChain`.
+pub type ThisChainHash = H256;
+/// Hasher at `ThisChain`.
+pub type ThisChainHasher = BlakeTwo256;
+/// Header of `ThisChain`.
+pub type ThisChainHeader = sp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
+/// Block of `ThisChain`.
+pub type ThisChainBlock = frame_system::mocking::MockBlockU32<TestRuntime>;
+
+/// Account identifier at the `BridgedChain`.
+pub type BridgedChainAccountId = u128;
+/// Balance at the `BridgedChain`.
+pub type BridgedChainBalance = u128;
+/// Block number at the `BridgedChain`.
+pub type BridgedChainBlockNumber = u32;
+/// Hash at the `BridgedChain`.
+pub type BridgedChainHash = H256;
+/// Hasher at the `BridgedChain`.
+pub type BridgedChainHasher = BlakeTwo256;
+/// Header of the `BridgedChain`.
+pub type BridgedChainHeader =
+	sp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
+
+/// Bridged chain id used in tests.
+pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
+/// Maximal extrinsic size at the `BridgedChain`.
+pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
+
+/// Underlying chain of `ThisChain`.
+pub struct ThisUnderlyingChain;
+
+impl Chain for ThisUnderlyingChain {
+	const ID: ChainId = *b"tuch";
+
+	type BlockNumber = ThisChainBlockNumber;
+	type Hash = ThisChainHash;
+	type Hasher = ThisChainHasher;
+	type Header = ThisChainHeader;
+	type AccountId = ThisChainAccountId;
+	type Balance = ThisChainBalance;
+	type Nonce = u32;
+	type Signature = sp_runtime::MultiSignature;
+
+	const STATE_VERSION: StateVersion = StateVersion::V1;
+
+	fn max_extrinsic_size() -> u32 {
+		BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
+	}
+
+	fn max_extrinsic_weight() -> Weight {
+		Weight::zero()
+	}
+}
+
+impl ChainWithMessages for ThisUnderlyingChain {
+	const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
+
+	const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
+	const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
+}
+
+/// Underlying chain of `BridgedChain`.
+pub struct BridgedUnderlyingParachain;
+
+impl Chain for BridgedUnderlyingParachain {
+	const ID: ChainId = TEST_BRIDGED_CHAIN_ID;
+
+	type BlockNumber = BridgedChainBlockNumber;
+	type Hash = BridgedChainHash;
+	type Hasher = BridgedChainHasher;
+	type Header = BridgedChainHeader;
+	type AccountId = BridgedChainAccountId;
+	type Balance = BridgedChainBalance;
+	type Nonce = u32;
+	type Signature = sp_runtime::MultiSignature;
+
+	const STATE_VERSION: StateVersion = StateVersion::V1;
+
+	fn max_extrinsic_size() -> u32 {
+		BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
+	}
+	fn max_extrinsic_weight() -> Weight {
+		Weight::zero()
+	}
+}
+
+impl ChainWithGrandpa for BridgedUnderlyingParachain {
+	const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
+	const MAX_AUTHORITIES_COUNT: u32 = 16;
+	const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
+	const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
+	const AVERAGE_HEADER_SIZE: u32 = 64;
+}
+
+impl ChainWithMessages for BridgedUnderlyingParachain {
+	const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
+	const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
+	const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
+}
+
+impl Parachain for BridgedUnderlyingParachain {
+	const PARACHAIN_ID: u32 = 42;
+	const MAX_HEADER_SIZE: u32 = 1_024;
+}
 
 pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
-	AccountId,
-	BlockNumber,
+	ThisChainAccountId,
+	ThisChainBlockNumber,
 	Balances,
 	ReserveId,
 	Stake,
 	Lease,
 >;
 
-type Block = frame_system::mocking::MockBlock<TestRuntime>;
-
 frame_support::construct_runtime! {
 	pub enum TestRuntime
 	{
 		System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
-		Balances: pallet_balances::{Pallet, Event<T>},
-		Relayers: pallet_bridge_relayers::{Pallet, Call, Event<T>},
+		Utility: pallet_utility,
+		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+		TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
+		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
+		BridgeGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
+		BridgeParachains: pallet_bridge_parachains::{Pallet, Call, Storage, Event<T>},
+		BridgeMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 	}
 }
 
 parameter_types! {
+	pub const BridgedParasPalletName: &'static str = "Paras";
 	pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
-	pub const ExistentialDeposit: Balance = 1;
+	pub const ExistentialDeposit: ThisChainBalance = 1;
 	pub const ReserveId: [u8; 8] = *b"brdgrlrs";
-	pub const Stake: Balance = 1_000;
-	pub const Lease: BlockNumber = 8;
+	pub const Stake: ThisChainBalance = 1_000;
+	pub const Lease: ThisChainBlockNumber = 8;
+	pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
+	pub const TransactionBaseFee: ThisChainBalance = 0;
+	pub const TransactionByteFee: ThisChainBalance = 1;
+	pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000);
+	pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128);
+	pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value();
 }
 
 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
-	type Block = Block;
-	type AccountData = pallet_balances::AccountData<Balance>;
+	type Block = ThisChainBlock;
+	// TODO: remove when https://github.com/paritytech/polkadot-sdk/pull/4543 merged
+	type BlockHashCount = ConstU32<10>;
+	type AccountData = pallet_balances::AccountData<ThisChainBalance>;
 	type DbWeight = DbWeight;
 }
 
@@ -72,9 +208,74 @@ impl pallet_balances::Config for TestRuntime {
 	type AccountStore = System;
 }
 
+impl pallet_utility::Config for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type RuntimeCall = RuntimeCall;
+	type PalletsOrigin = OriginCaller;
+	type WeightInfo = ();
+}
+
+#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
+impl pallet_transaction_payment::Config for TestRuntime {
+	type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>;
+	type OperationalFeeMultiplier = ConstU8<5>;
+	type WeightToFee = IdentityFee<ThisChainBalance>;
+	type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
+	type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment<
+		TestRuntime,
+		TargetBlockFullness,
+		AdjustmentVariable,
+		MinimumMultiplier,
+		MaximumMultiplier,
+	>;
+	type RuntimeEvent = RuntimeEvent;
+}
+
+impl pallet_bridge_grandpa::Config for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type BridgedChain = BridgedUnderlyingParachain;
+	type MaxFreeHeadersPerBlock = ConstU32<4>;
+	type FreeHeadersInterval = ConstU32<1_024>;
+	type HeadersToKeep = ConstU32<8>;
+	type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
+}
+
+impl pallet_bridge_parachains::Config for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type BridgesGrandpaPalletInstance = ();
+	type ParasPalletName = BridgedParasPalletName;
+	type ParaStoredHeaderDataBuilder =
+		SingleParaStoredHeaderDataBuilder<BridgedUnderlyingParachain>;
+	type HeadsToKeep = ConstU32<8>;
+	type MaxParaHeadDataSize = ConstU32<1024>;
+	type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight<TestRuntime>;
+}
+
+impl pallet_bridge_messages::Config for TestRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type WeightInfo = pallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
+
+	type OutboundPayload = Vec<u8>;
+
+	type InboundPayload = Vec<u8>;
+	type DeliveryPayments = ();
+
+	type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
+		TestRuntime,
+		(),
+		ConstU64<100_000>,
+	>;
+	type OnMessagesDelivered = ();
+
+	type MessageDispatch = DummyMessageDispatch;
+	type ThisChain = ThisUnderlyingChain;
+	type BridgedChain = BridgedUnderlyingParachain;
+	type BridgedHeaderChain = BridgeGrandpa;
+}
+
 impl pallet_bridge_relayers::Config for TestRuntime {
 	type RuntimeEvent = RuntimeEvent;
-	type Reward = Balance;
+	type Reward = ThisChainBalance;
 	type PaymentProcedure = TestPaymentProcedure;
 	type StakeAndSlash = TestStakeAndSlash;
 	type WeightInfo = ();
@@ -82,9 +283,9 @@ impl pallet_bridge_relayers::Config for TestRuntime {
 
 #[cfg(feature = "runtime-benchmarks")]
 impl pallet_bridge_relayers::benchmarking::Config for TestRuntime {
-	fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Balance) {
+	fn prepare_rewards_account(account_params: RewardsAccountParams, reward: ThisChainBalance) {
 		let rewards_account =
-			bp_relayers::PayRewardFromAccount::<Balances, AccountId>::rewards_account(
+			bp_relayers::PayRewardFromAccount::<Balances, ThisChainAccountId>::rewards_account(
 				account_params,
 			);
 		Self::deposit_account(rewards_account, reward);
@@ -96,30 +297,30 @@ impl pallet_bridge_relayers::benchmarking::Config for TestRuntime {
 }
 
 /// Regular relayer that may receive rewards.
-pub const REGULAR_RELAYER: AccountId = 1;
+pub const REGULAR_RELAYER: ThisChainAccountId = 1;
 
 /// Relayer that can't receive rewards.
-pub const FAILING_RELAYER: AccountId = 2;
+pub const FAILING_RELAYER: ThisChainAccountId = 2;
 
 /// Relayer that is able to register.
-pub const REGISTER_RELAYER: AccountId = 42;
+pub const REGISTER_RELAYER: ThisChainAccountId = 42;
 
 /// Payment procedure that rejects payments to the `FAILING_RELAYER`.
 pub struct TestPaymentProcedure;
 
 impl TestPaymentProcedure {
-	pub fn rewards_account(params: RewardsAccountParams) -> AccountId {
-		PayRewardFromAccount::<(), AccountId>::rewards_account(params)
+	pub fn rewards_account(params: RewardsAccountParams) -> ThisChainAccountId {
+		PayRewardFromAccount::<(), ThisChainAccountId>::rewards_account(params)
 	}
 }
 
-impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
+impl PaymentProcedure<ThisChainAccountId, ThisChainBalance> for TestPaymentProcedure {
 	type Error = ();
 
 	fn pay_reward(
-		relayer: &AccountId,
+		relayer: &ThisChainAccountId,
 		_lane_id: RewardsAccountParams,
-		_reward: Balance,
+		_reward: ThisChainBalance,
 	) -> Result<(), Self::Error> {
 		match *relayer {
 			FAILING_RELAYER => Err(()),
@@ -128,6 +329,35 @@ impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
 	}
 }
 
+/// Dummy message dispatcher.
+pub struct DummyMessageDispatch;
+
+impl DummyMessageDispatch {
+	pub fn deactivate(lane: LaneId) {
+		frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
+	}
+}
+
+impl MessageDispatch for DummyMessageDispatch {
+	type DispatchPayload = Vec<u8>;
+	type DispatchLevelResult = ();
+
+	fn is_active(lane: LaneId) -> bool {
+		frame_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
+			Some(false)
+	}
+
+	fn dispatch_weight(_message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
+		Weight::zero()
+	}
+
+	fn dispatch(
+		_: DispatchMessage<Self::DispatchPayload>,
+	) -> MessageDispatchResult<Self::DispatchLevelResult> {
+		MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
+	}
+}
+
 /// Reward account params that we are using in tests.
 pub fn test_reward_account_param() -> RewardsAccountParams {
 	RewardsAccountParams::new(LaneId::new(1, 2), *b"test", RewardsAccountOwner::ThisChain)
diff --git a/bridges/modules/relayers/src/payment_adapter.rs b/bridges/modules/relayers/src/payment_adapter.rs
index beec412c1203588ab4dc48a54bd4e6364cc122a1..3693793a3e5cac36d7fc46574abf2153249e4467 100644
--- a/bridges/modules/relayers/src/payment_adapter.rs
+++ b/bridges/modules/relayers/src/payment_adapter.rs
@@ -103,11 +103,11 @@ mod tests {
 	use super::*;
 	use crate::{mock::*, RelayerRewards};
 
-	const RELAYER_1: AccountId = 1;
-	const RELAYER_2: AccountId = 2;
-	const RELAYER_3: AccountId = 3;
+	const RELAYER_1: ThisChainAccountId = 1;
+	const RELAYER_2: ThisChainAccountId = 2;
+	const RELAYER_3: ThisChainAccountId = 3;
 
-	fn relayers_rewards() -> RelayersRewards<AccountId> {
+	fn relayers_rewards() -> RelayersRewards<ThisChainAccountId> {
 		vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect()
 	}
 
diff --git a/bridges/modules/relayers/src/stake_adapter.rs b/bridges/modules/relayers/src/stake_adapter.rs
index ce097344d56be145f34a57d3cb0c2648c87b2428..0c965e9e6bff8182e9c45498faa3cb4535a87b62 100644
--- a/bridges/modules/relayers/src/stake_adapter.rs
+++ b/bridges/modules/relayers/src/stake_adapter.rs
@@ -80,7 +80,7 @@ mod tests {
 
 	use frame_support::traits::fungible::Mutate;
 
-	fn test_stake() -> Balance {
+	fn test_stake() -> ThisChainBalance {
 		Stake::get()
 	}
 
diff --git a/bridges/primitives/header-chain/src/call_info.rs b/bridges/primitives/header-chain/src/call_info.rs
index 6f880e9394147d04788eaf9c37513b6934ffd20f..acf7447adabc764b5205f5ad03c3f0a2609d9784 100644
--- a/bridges/primitives/header-chain/src/call_info.rs
+++ b/bridges/primitives/header-chain/src/call_info.rs
@@ -20,14 +20,11 @@ use crate::{justification, InitializationData};
 
 use bp_runtime::HeaderOf;
 use codec::{Decode, Encode};
-use frame_support::weights::Weight;
+use frame_support::{weights::Weight, RuntimeDebugNoBound};
 use scale_info::TypeInfo;
 use sp_consensus_grandpa::SetId;
-use sp_runtime::{
-	traits::{Header as HeaderT, Zero},
-	RuntimeDebug,
-};
-use sp_std::boxed::Box;
+use sp_runtime::traits::{Header as HeaderT, Zero};
+use sp_std::{boxed::Box, fmt::Debug};
 
 /// A minimized version of `pallet-bridge-grandpa::Call` that can be used without a runtime.
 #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
@@ -63,8 +60,8 @@ pub enum BridgeGrandpaCall<Header: HeaderT> {
 pub type BridgeGrandpaCallOf<C> = BridgeGrandpaCall<HeaderOf<C>>;
 
 /// A digest information on the `BridgeGrandpaCall::submit_finality_proof` call.
-#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
-pub struct SubmitFinalityProofInfo<N> {
+#[derive(Copy, Clone, PartialEq, RuntimeDebugNoBound)]
+pub struct SubmitFinalityProofInfo<N: Debug> {
 	/// Number of the finality target.
 	pub block_number: N,
 	/// An identifier of the validators set that has signed the submitted justification.
@@ -89,7 +86,7 @@ pub struct SubmitFinalityProofInfo<N> {
 	pub extra_size: u32,
 }
 
-impl<N> SubmitFinalityProofInfo<N> {
+impl<N: Debug> SubmitFinalityProofInfo<N> {
 	/// Returns `true` if call size/weight is below our estimations for regular calls.
 	pub fn fits_limits(&self) -> bool {
 		self.extra_weight.is_zero() && self.extra_size.is_zero()
diff --git a/bridges/primitives/messages/src/call_info.rs b/bridges/primitives/messages/src/call_info.rs
index 4cc3b9d4afea78b02330e2822ef1603f9d741baa..c8f06ed8cb7c34e11a37fb7613e3dfad6bc45538 100644
--- a/bridges/primitives/messages/src/call_info.rs
+++ b/bridges/primitives/messages/src/call_info.rs
@@ -154,6 +154,14 @@ pub enum MessagesCallInfo {
 }
 
 impl MessagesCallInfo {
+	/// Returns lane, used by the call.
+	pub fn lane_id(&self) -> LaneId {
+		match *self {
+			Self::ReceiveMessagesProof(ref info) => info.base.lane_id,
+			Self::ReceiveMessagesDeliveryProof(ref info) => info.0.lane_id,
+		}
+	}
+
 	/// Returns range of messages, bundled with the call.
 	pub fn bundled_messages(&self) -> RangeInclusive<MessageNonce> {
 		match *self {
diff --git a/bridges/primitives/parachains/src/lib.rs b/bridges/primitives/parachains/src/lib.rs
index 9717fee8597c73f0430130e34af59b9760b46855..ec3bf9ca3a0af6d2b159fc354e6792519e0c6321 100644
--- a/bridges/primitives/parachains/src/lib.rs
+++ b/bridges/primitives/parachains/src/lib.rs
@@ -20,11 +20,9 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub use bp_header_chain::StoredHeaderData;
+pub use call_info::{BridgeParachainCall, SubmitParachainHeadsInfo};
 
-use bp_polkadot_core::{
-	parachains::{ParaHash, ParaHead, ParaId},
-	BlockNumber as RelayBlockNumber, Hash as RelayBlockHash,
-};
+use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaId};
 use bp_runtime::{
 	BlockNumberOf, Chain, HashOf, HeaderOf, Parachain, StorageDoubleMapKeyProvider,
 	StorageMapKeyProvider,
@@ -36,7 +34,12 @@ use sp_core::storage::StorageKey;
 use sp_runtime::{traits::Header as HeaderT, RuntimeDebug};
 use sp_std::{marker::PhantomData, prelude::*};
 
-pub use call_info::{BridgeParachainCall, SubmitParachainHeadsInfo};
+/// Block hash of the bridged relay chain.
+pub type RelayBlockHash = bp_polkadot_core::Hash;
+/// Block number of the bridged relay chain.
+pub type RelayBlockNumber = bp_polkadot_core::BlockNumber;
+/// Hasher of the bridged relay chain.
+pub type RelayBlockHasher = bp_polkadot_core::Hasher;
 
 mod call_info;
 
diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml
index 3448e8a4096339966023d0f5ddd0e158380ab12a..2019b71965737079c48549e0c107bc6629c2a653 100644
--- a/bridges/primitives/relayers/Cargo.toml
+++ b/bridges/primitives/relayers/Cargo.toml
@@ -15,13 +15,15 @@ codec = { features = ["bit-vec", "derive"], workspace = true }
 scale-info = { features = ["bit-vec", "derive"], workspace = true }
 
 # Bridge Dependencies
-
+bp-header-chain = { workspace = true }
 bp-messages = { workspace = true }
-bp-runtime = { workspace = true }
+bp-parachains = { workspace = true }
+bp-runtime = { features = ["test-helpers"], workspace = true }
 
 # Substrate Dependencies
-
+frame-system = { workspace = true }
 frame-support = { workspace = true }
+pallet-utility = { workspace = true }
 sp-runtime = { workspace = true }
 sp-std = { workspace = true }
 
@@ -32,10 +34,14 @@ hex-literal = { workspace = true, default-features = true }
 [features]
 default = ["std"]
 std = [
+	"bp-header-chain/std",
 	"bp-messages/std",
+	"bp-parachains/std",
 	"bp-runtime/std",
 	"codec/std",
 	"frame-support/std",
+	"frame-system/std",
+	"pallet-utility/std",
 	"scale-info/std",
 	"sp-runtime/std",
 	"sp-std/std",
diff --git a/bridges/primitives/relayers/src/extension.rs b/bridges/primitives/relayers/src/extension.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5ab8e6cde96b408f6415768053eebf14c2b82ae4
--- /dev/null
+++ b/bridges/primitives/relayers/src/extension.rs
@@ -0,0 +1,191 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common 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.
+
+// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! All runtime calls, supported by `pallet-bridge-relayers` when it acts as a signed
+//! extension.
+
+use bp_header_chain::SubmitFinalityProofInfo;
+use bp_messages::MessagesCallInfo;
+use bp_parachains::SubmitParachainHeadsInfo;
+use bp_runtime::StaticStrProvider;
+use frame_support::{
+	dispatch::CallableCallFor, traits::IsSubType, weights::Weight, RuntimeDebugNoBound,
+};
+use frame_system::Config as SystemConfig;
+use pallet_utility::{Call as UtilityCall, Pallet as UtilityPallet};
+use sp_runtime::{
+	traits::Get,
+	transaction_validity::{TransactionPriority, TransactionValidityError},
+	RuntimeDebug,
+};
+use sp_std::{fmt::Debug, marker::PhantomData, vec, vec::Vec};
+
+/// Type of the call that the signed extension recognizes.
+#[derive(PartialEq, RuntimeDebugNoBound)]
+pub enum ExtensionCallInfo<RemoteGrandpaChainBlockNumber: Debug> {
+	/// Relay chain finality + parachain finality + message delivery/confirmation calls.
+	AllFinalityAndMsgs(
+		SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>,
+		SubmitParachainHeadsInfo,
+		MessagesCallInfo,
+	),
+	/// Relay chain finality + message delivery/confirmation calls.
+	RelayFinalityAndMsgs(SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>, MessagesCallInfo),
+	/// Parachain finality + message delivery/confirmation calls.
+	///
+	/// This variant is used only when bridging with parachain.
+	ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo),
+	/// Standalone message delivery/confirmation call.
+	Msgs(MessagesCallInfo),
+}
+
+impl<RemoteGrandpaChainBlockNumber: Clone + Copy + Debug>
+	ExtensionCallInfo<RemoteGrandpaChainBlockNumber>
+{
+	/// Returns true if call is a message delivery call (with optional finality calls).
+	pub fn is_receive_messages_proof_call(&self) -> bool {
+		match self.messages_call_info() {
+			MessagesCallInfo::ReceiveMessagesProof(_) => true,
+			MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => false,
+		}
+	}
+
+	/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
+	pub fn submit_finality_proof_info(
+		&self,
+	) -> Option<SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>> {
+		match *self {
+			Self::AllFinalityAndMsgs(info, _, _) => Some(info),
+			Self::RelayFinalityAndMsgs(info, _) => Some(info),
+			_ => None,
+		}
+	}
+
+	/// Returns the pre-dispatch `SubmitParachainHeadsInfo`.
+	pub fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> {
+		match self {
+			Self::AllFinalityAndMsgs(_, info, _) => Some(info),
+			Self::ParachainFinalityAndMsgs(info, _) => Some(info),
+			_ => None,
+		}
+	}
+
+	/// Returns the pre-dispatch `ReceiveMessagesProofInfo`.
+	pub fn messages_call_info(&self) -> &MessagesCallInfo {
+		match self {
+			Self::AllFinalityAndMsgs(_, _, info) => info,
+			Self::RelayFinalityAndMsgs(_, info) => info,
+			Self::ParachainFinalityAndMsgs(_, info) => info,
+			Self::Msgs(info) => info,
+		}
+	}
+}
+
+/// Extra post-dispatch data, associated with the supported runtime call.
+#[derive(Default, RuntimeDebug)]
+pub struct ExtensionCallData {
+	/// Extra weight, consumed by the call. We have some assumptions about normal weight
+	/// that may be consumed by expected calls. If the actual weight is larger than that,
+	/// we do not refund relayer for this extra weight.
+	pub extra_weight: Weight,
+	/// Extra size, consumed by the call. We have some assumptions about normal size
+	/// of the encoded call. If the actual size is larger than that, we do not refund relayer
+	/// for this extra size.
+	pub extra_size: u32,
+}
+
+/// Signed extension configuration.
+///
+/// The single `pallet-bridge-relayers` instance may be shared by multiple messages
+/// pallet instances, bridging with different remote networks. We expect every instance
+/// of the messages pallet to add a separate signed extension to runtime. So it must
+/// have a separate configuration.
+pub trait ExtensionConfig {
+	/// Unique identifier of the signed extension that will use this configuration.
+	type IdProvider: StaticStrProvider;
+	/// Runtime that optionally supports batched calls. We assume that batched call
+	/// succeeds if and only if all of its nested calls succeed.
+	type Runtime: frame_system::Config;
+	/// Messages pallet instance.
+	type BridgeMessagesPalletInstance: 'static;
+	/// Additional priority that is added to base message delivery transaction priority
+	/// for every additional bundled message.
+	type PriorityBoostPerMessage: Get<TransactionPriority>;
+	/// Type of reward, that the `pallet-bridge-relayers` is using.
+	type Reward;
+	/// Block number for the remote **GRANDPA chain**. Mind that this chain is not
+	/// necessarily the chain that we are bridging with. If we are bridging with
+	/// parachain, it must be its parent relay chain. If we are bridging with the
+	/// GRANDPA chain, it must be it.
+	type RemoteGrandpaChainBlockNumber: Clone + Copy + Debug;
+
+	/// Given runtime call, check if it is supported by the signed extension. Additionally,
+	/// check if call (or any of batched calls) are obsolete.
+	fn parse_and_check_for_obsolete_call(
+		call: &<Self::Runtime as SystemConfig>::RuntimeCall,
+	) -> Result<
+		Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>>,
+		TransactionValidityError,
+	>;
+
+	/// Check if runtime call is already obsolete.
+	fn check_obsolete_parsed_call(
+		call: &<Self::Runtime as SystemConfig>::RuntimeCall,
+	) -> Result<&<Self::Runtime as SystemConfig>::RuntimeCall, TransactionValidityError>;
+
+	/// Given runtime call info, check that this call has been successful and has updated
+	/// runtime storage accordingly.
+	fn check_call_result(
+		call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber>,
+		call_data: &mut ExtensionCallData,
+		relayer: &<Self::Runtime as SystemConfig>::AccountId,
+	) -> bool;
+}
+
+/// Something that can unpack batch calls (all-or-nothing flavor) of given size.
+pub trait BatchCallUnpacker<Runtime: frame_system::Config> {
+	/// Unpack batch call with no more than `max_packed_calls` calls.
+	fn unpack(call: &Runtime::RuntimeCall, max_packed_calls: u32) -> Vec<&Runtime::RuntimeCall>;
+}
+
+/// An `BatchCallUnpacker` adapter for runtimes with utility pallet.
+pub struct RuntimeWithUtilityPallet<Runtime>(PhantomData<Runtime>);
+
+impl<Runtime> BatchCallUnpacker<Runtime> for RuntimeWithUtilityPallet<Runtime>
+where
+	Runtime: pallet_utility::Config<RuntimeCall = <Runtime as SystemConfig>::RuntimeCall>,
+	<Runtime as SystemConfig>::RuntimeCall:
+		IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>,
+{
+	fn unpack(
+		call: &<Runtime as frame_system::Config>::RuntimeCall,
+		max_packed_calls: u32,
+	) -> Vec<&<Runtime as frame_system::Config>::RuntimeCall> {
+		match call.is_sub_type() {
+			Some(UtilityCall::<Runtime>::batch_all { ref calls })
+				if calls.len() <= max_packed_calls as usize =>
+				calls.iter().collect(),
+			Some(_) => vec![],
+			None => vec![call],
+		}
+	}
+}
+
+impl<Runtime: frame_system::Config> BatchCallUnpacker<Runtime> for () {
+	fn unpack(call: &Runtime::RuntimeCall, _max_packed_calls: u32) -> Vec<&Runtime::RuntimeCall> {
+		vec![call]
+	}
+}
diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs
index 704fef7c8e6baca3ee5804b632ad248e8c3637dc..1e63c89ecd70434de754e1442c193d65bb5ec182 100644
--- a/bridges/primitives/relayers/src/lib.rs
+++ b/bridges/primitives/relayers/src/lib.rs
@@ -19,6 +19,10 @@
 #![warn(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
 
+pub use extension::{
+	BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig,
+	RuntimeWithUtilityPallet,
+};
 pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash};
 
 use bp_messages::LaneId;
@@ -32,6 +36,7 @@ use sp_runtime::{
 };
 use sp_std::{fmt::Debug, marker::PhantomData};
 
+mod extension;
 mod registration;
 
 /// The owner of the sovereign account that should pay the rewards.
diff --git a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs
index 5631285b3c544de0e3caf85a6b74b7ee31601c56..28b0eb0ad526789d44ec6936e01e269f4e93a816 100644
--- a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs
+++ b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs
@@ -22,7 +22,7 @@ use crate::{
 	messages::{MessagesRelayLimits, SubstrateMessageLane},
 	parachains::SubstrateParachainsPipeline,
 };
-use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
+use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use relay_substrate_client::{
 	Chain, ChainWithRuntimeVersion, ChainWithTransactions, Parachain, RelayChain,
 };
diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs
index 8104be7af807a67bbc001e70e24565c81b6beb17..e8b797f84fa51d39c42ae761839fd9335f23bc84 100644
--- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs
+++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs
@@ -30,8 +30,8 @@ use crate::{
 		headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay,
 	},
 };
+use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use bp_polkadot_core::parachains::ParaHash;
-use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use relay_substrate_client::{
 	AccountIdOf, AccountKeyPairOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions, Client,
 	Parachain,
diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs
index 6c078973fedc08a724e51808f2cb47f3a64ca1a1..f9884ee197b4002305f0b56e315837b3ce4f3709 100644
--- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs
+++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs
@@ -33,8 +33,8 @@ use crate::{
 		headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay,
 	},
 };
+use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use bp_polkadot_core::parachains::ParaHash;
-use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use relay_substrate_client::{
 	AccountIdOf, AccountKeyPairOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions, Client,
 	Parachain,
diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs
index 4579222a2c681c49e076f67d2eacaeb1dc8b9fca..2ef86f48ecbeb6a3c5e6084f16ab9e50352fed4f 100644
--- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs
+++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs
@@ -31,11 +31,11 @@ use async_std::{
 	sync::{Arc, Mutex},
 };
 use async_trait::async_trait;
+use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use bp_polkadot_core::parachains::{ParaHash, ParaId};
 use bp_runtime::HeaderIdProvider;
 use futures::{select, FutureExt};
 use num_traits::Zero;
-use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use parachains_relay::parachains_loop::{AvailableHeader, SourceClient, TargetClient};
 use relay_substrate_client::{
 	is_ancient_block, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client,
diff --git a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs
index 8b128bb770dd7a05d28ad46d4561f4d859b1deb6..08d8e5e2a4f55e7463a21c6bad289a000be50f08 100644
--- a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs
+++ b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs
@@ -18,11 +18,9 @@
 //! parachain finality proofs synchronization pipelines.
 
 use async_trait::async_trait;
+use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
 use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
-use pallet_bridge_parachains::{
-	Call as BridgeParachainsCall, Config as BridgeParachainsConfig, RelayBlockHash,
-	RelayBlockHasher, RelayBlockNumber,
-};
+use pallet_bridge_parachains::{Call as BridgeParachainsCall, Config as BridgeParachainsConfig};
 use parachains_relay::ParachainsPipeline;
 use relay_substrate_client::{
 	CallOf, Chain, ChainWithTransactions, HeaderIdOf, Parachain, RelayChain,