diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml
index 835a8cf1318f332b48fe7c0d2a349370630c46ef..e3c05d1bebbcec749fc2a416f65d757245e1a617 100644
--- a/bridges/bin/runtime-common/Cargo.toml
+++ b/bridges/bin/runtime-common/Cargo.toml
@@ -93,7 +93,6 @@ runtime-benchmarks = [
 	"pallet-bridge-messages/runtime-benchmarks",
 	"pallet-bridge-parachains/runtime-benchmarks",
 	"pallet-bridge-relayers/runtime-benchmarks",
-	"pallet-transaction-payment/runtime-benchmarks",
 	"pallet-utility/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"xcm-builder/runtime-benchmarks",
diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs
index 035077408c40bd42c9e941c19af868989d66e362..2722f6f1c6d14f09ab215f8f020f2c449eda4d4b 100644
--- a/bridges/bin/runtime-common/src/lib.rs
+++ b/bridges/bin/runtime-common/src/lib.rs
@@ -105,48 +105,43 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 	($call:ty, $account_id:ty, $($filter_call:ty),*) => {
 		#[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
 		pub struct BridgeRejectObsoleteHeadersAndMessages;
-		impl sp_runtime::traits::TransactionExtensionBase for BridgeRejectObsoleteHeadersAndMessages {
+		impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
 			const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
-			type Implicit = ();
-		}
-		impl<Context> sp_runtime::traits::TransactionExtension<$call, Context> for BridgeRejectObsoleteHeadersAndMessages {
+			type AccountId = $account_id;
+			type Call = $call;
+			type AdditionalSigned = ();
 			type Pre = ();
-			type Val = ();
+
+			fn additional_signed(&self) -> sp_std::result::Result<
+				(),
+				sp_runtime::transaction_validity::TransactionValidityError,
+			> {
+				Ok(())
+			}
 
 			fn validate(
 				&self,
-				origin: <$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
-				call: &$call,
-				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
+				_who: &Self::AccountId,
+				call: &Self::Call,
+				_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
 				_len: usize,
-				_context: &mut Context,
-				_self_implicit: Self::Implicit,
-				_inherited_implication: &impl codec::Encode,
-			) -> Result<
-				(
-					sp_runtime::transaction_validity::ValidTransaction,
-					Self::Val,
-					<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
-				), sp_runtime::transaction_validity::TransactionValidityError
-			> {
-				let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default();
+			) -> sp_runtime::transaction_validity::TransactionValidity {
+				let valid = sp_runtime::transaction_validity::ValidTransaction::default();
 				$(
-					let call_filter_validity = <$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?;
-					let tx_validity = tx_validity.combine_with(call_filter_validity);
+					let valid = valid
+						.combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?);
 				)*
-				Ok((tx_validity, (), origin))
+				Ok(valid)
 			}
 
-			fn prepare(
+			fn pre_dispatch(
 				self,
-				_val: Self::Val,
-				_origin: &<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
-				_call: &$call,
-				_info: &sp_runtime::traits::DispatchInfoOf<$call>,
-				_len: usize,
-				_context: &Context,
+				who: &Self::AccountId,
+				call: &Self::Call,
+				info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
+				len: usize,
 			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
-				Ok(())
+				self.validate(who, call, info, len).map(drop)
 			}
 		}
 	};
@@ -155,14 +150,12 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
 #[cfg(test)]
 mod tests {
 	use crate::BridgeRuntimeFilterCall;
-	use codec::Encode;
-	use frame_support::assert_err;
+	use frame_support::{assert_err, assert_ok};
 	use sp_runtime::{
-		traits::DispatchTransaction,
+		traits::SignedExtension,
 		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
 	};
 
-	#[derive(Encode)]
 	pub struct MockCall {
 		data: u32,
 	}
@@ -213,20 +206,17 @@ mod tests {
 		);
 
 		assert_err!(
-			BridgeRejectObsoleteHeadersAndMessages.validate_only((), &MockCall { data: 1 }, &(), 0),
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
 			InvalidTransaction::Custom(1)
 		);
 
 		assert_err!(
-			BridgeRejectObsoleteHeadersAndMessages.validate_only((), &MockCall { data: 2 }, &(), 0),
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
 			InvalidTransaction::Custom(2)
 		);
 
-		assert_eq!(
-			BridgeRejectObsoleteHeadersAndMessages
-				.validate_only((), &MockCall { data: 3 }, &(), 0)
-				.unwrap()
-				.0,
+		assert_ok!(
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
 			ValidTransaction { priority: 3, ..Default::default() }
 		)
 	}
diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs
index f147f1404f06fb60c608d4ad049502c6e184a85f..deee4524e8586d45c01f08126e84b9c619cfeba2 100644
--- a/bridges/bin/runtime-common/src/mock.rs
+++ b/bridges/bin/runtime-common/src/mock.rs
@@ -141,7 +141,7 @@ parameter_types! {
 	pub const ReserveId: [u8; 8] = *b"brdgrlrs";
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Hash = ThisChainHash;
 	type Hashing = ThisChainHasher;
@@ -158,13 +158,13 @@ impl pallet_utility::Config for TestRuntime {
 	type WeightInfo = ();
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
 }
 
-#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig as pallet_transaction_payment::DefaultConfig)]
+#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
 impl pallet_transaction_payment::Config for TestRuntime {
 	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
 	type OperationalFeeMultiplier = ConstU8<5>;
@@ -177,6 +177,7 @@ impl pallet_transaction_payment::Config for TestRuntime {
 		MinimumMultiplier,
 		MaximumMultiplier,
 	>;
+	type RuntimeEvent = RuntimeEvent;
 }
 
 impl pallet_bridge_grandpa::Config for TestRuntime {
diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/priority_calculator.rs
index 0c53018330ea0ebc2fbacb32808e01a9ec88960f..c2737128e3422dd72ca3b49d66151268d719e0bc 100644
--- a/bridges/bin/runtime-common/src/priority_calculator.rs
+++ b/bridges/bin/runtime-common/src/priority_calculator.rs
@@ -128,7 +128,7 @@ mod integrity_tests {
 		Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
 		BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
 	{
-		// esimate priority of transaction that delivers one message and has large tip
+		// estimate priority of transaction that delivers one message and has large tip
 		let maximal_messages_in_delivery_transaction =
 			Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
 		let small_with_tip_priority =
@@ -169,15 +169,12 @@ mod integrity_tests {
 		// nodes to the proof (x0.5 because we expect some nodes to be reused)
 		let estimated_message_size = 512;
 		// let's say all our messages have the same dispatch weight
-		let estimated_message_dispatch_weight = <Runtime as pallet_bridge_messages::Config<
-			MessagesInstance,
-		>>::WeightInfo::message_dispatch_weight(
-			estimated_message_size
-		);
+		let estimated_message_dispatch_weight =
+			Runtime::WeightInfo::message_dispatch_weight(estimated_message_size);
 		// messages proof argument size is (for every message) messages size + some additional
 		// trie nodes. Some of them are reused by different messages, so let's take 2/3 of default
 		// "overhead" constant
-		let messages_proof_size = <Runtime as pallet_bridge_messages::Config<MessagesInstance>>::WeightInfo::expected_extra_storage_proof_size()
+		let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size()
 			.saturating_mul(2)
 			.saturating_div(3)
 			.saturating_add(estimated_message_size)
@@ -185,7 +182,7 @@ mod integrity_tests {
 
 		// finally we are able to estimate transaction size and weight
 		let transaction_size = base_tx_size.saturating_add(messages_proof_size);
-		let transaction_weight = <Runtime as pallet_bridge_messages::Config<MessagesInstance>>::WeightInfo::receive_messages_proof_weight(
+		let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight(
 			&PreComputedSize(transaction_size as _),
 			messages as _,
 			estimated_message_dispatch_weight.saturating_mul(messages),
diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/refund_relayer_extension.rs
index b912f8445ac1b455e95110d41347ac8e55afaa7c..8e901d72821fe1dfb749f4227c2875f1d807fccc 100644
--- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs
+++ b/bridges/bin/runtime-common/src/refund_relayer_extension.rs
@@ -16,7 +16,7 @@
 
 //! 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 messsage and all necessary underlying headers
+//! with calls that are: delivering new message and all necessary underlying headers
 //! (parachain or relay chain).
 
 use crate::messages_call_ext::{
@@ -48,12 +48,9 @@ use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTra
 use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
 use scale_info::TypeInfo;
 use sp_runtime::{
-	traits::{
-		AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf,
-		TransactionExtension, TransactionExtensionBase, ValidateResult, Zero,
-	},
+	traits::{DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf, SignedExtension, Zero},
 	transaction_validity::{
-		InvalidTransaction, TransactionPriority, TransactionValidityError, ValidTransactionBuilder,
+		TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransactionBuilder,
 	},
 	DispatchResult, FixedPointOperand, RuntimeDebug,
 };
@@ -242,8 +239,8 @@ pub enum RelayerAccountAction<AccountId, Reward> {
 	Slash(AccountId, RewardsAccountParams),
 }
 
-/// Everything common among our refund transaction extensions.
-pub trait RefundTransactionExtension:
+/// Everything common among our refund signed extensions.
+pub trait RefundSignedExtension:
 	'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
 where
 	<Self::Runtime as GrandpaConfig<Self::GrandpaInstance>>::BridgedChain:
@@ -459,8 +456,8 @@ where
 	}
 }
 
-/// Adapter that allow implementing `sp_runtime::traits::TransactionExtension` for any
-/// `RefundTransactionExtension`.
+/// Adapter that allow implementing `sp_runtime::traits::SignedExtension` for any
+/// `RefundSignedExtension`.
 #[derive(
 	DefaultNoBound,
 	CloneNoBound,
@@ -471,13 +468,12 @@ where
 	RuntimeDebugNoBound,
 	TypeInfo,
 )]
-pub struct RefundTransactionExtensionAdapter<T: RefundTransactionExtension>(T)
+pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T)
 where
 	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
 		Chain<BlockNumber = RelayBlockNumber>;
 
-impl<T: RefundTransactionExtension> TransactionExtensionBase
-	for RefundTransactionExtensionAdapter<T>
+impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
 where
 	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
 		Chain<BlockNumber = RelayBlockNumber>,
@@ -487,35 +483,22 @@ where
 		+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
 {
 	const IDENTIFIER: &'static str = T::Id::STR;
-	type Implicit = ();
-}
-
-impl<T: RefundTransactionExtension, Context> TransactionExtension<CallOf<T::Runtime>, Context>
-	for RefundTransactionExtensionAdapter<T>
-where
-	<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
-		Chain<BlockNumber = RelayBlockNumber>,
-	CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
-		+ IsSubType<CallableCallFor<UtilityPallet<T::Runtime>, T::Runtime>>
-		+ GrandpaCallSubType<T::Runtime, T::GrandpaInstance>
-		+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
-	<CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin:
-		AsSystemOriginSigner<AccountIdOf<T::Runtime>> + Clone,
-{
+	type AccountId = AccountIdOf<T::Runtime>;
+	type Call = CallOf<T::Runtime>;
+	type AdditionalSigned = ();
 	type Pre = Option<PreDispatchData<AccountIdOf<T::Runtime>>>;
-	type Val = Option<CallInfo>;
+
+	fn additional_signed(&self) -> Result<(), TransactionValidityError> {
+		Ok(())
+	}
 
 	fn validate(
 		&self,
-		origin: <CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin,
-		call: &CallOf<T::Runtime>,
-		_info: &DispatchInfoOf<CallOf<T::Runtime>>,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
 		_len: usize,
-		_context: &mut Context,
-		_self_implicit: Self::Implicit,
-		_inherited_implication: &impl Encode,
-	) -> ValidateResult<Self::Val, CallOf<T::Runtime>> {
-		let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
+	) -> TransactionValidity {
 		// this is the only relevant line of code for the `pre_dispatch`
 		//
 		// we're not calling `validate` from `pre_dispatch` directly because of performance
@@ -528,12 +511,12 @@ where
 		// we only boost priority of presumably correct message delivery transactions
 		let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
 			Some(bundled_messages) => bundled_messages,
-			None => return Ok((Default::default(), parsed_call, origin)),
+			None => return Ok(Default::default()),
 		};
 
 		// we only boost priority if relayer has staked required balance
 		if !RelayersPallet::<T::Runtime>::is_registration_active(who) {
-			return Ok((Default::default(), parsed_call, origin))
+			return Ok(Default::default())
 		}
 
 		// compute priority boost
@@ -552,21 +535,20 @@ where
 			priority_boost,
 		);
 
-		let validity = valid_transaction.build()?;
-		Ok((validity, parsed_call, origin))
+		valid_transaction.build()
 	}
 
-	fn prepare(
+	fn pre_dispatch(
 		self,
-		val: Self::Val,
-		origin: &<CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin,
-		_call: &CallOf<T::Runtime>,
-		_info: &DispatchInfoOf<CallOf<T::Runtime>>,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
 		_len: usize,
-		_context: &Context,
 	) -> Result<Self::Pre, TransactionValidityError> {
-		let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
-		Ok(val.map(|call_info| {
+		// 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)?;
+
+		Ok(parsed_call.map(|call_info| {
 			log::trace!(
 				target: "runtime::bridge",
 				"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
@@ -579,14 +561,13 @@ where
 	}
 
 	fn post_dispatch(
-		pre: Self::Pre,
-		info: &DispatchInfoOf<CallOf<T::Runtime>>,
-		post_info: &PostDispatchInfoOf<CallOf<T::Runtime>>,
+		pre: Option<Self::Pre>,
+		info: &DispatchInfoOf<Self::Call>,
+		post_info: &PostDispatchInfoOf<Self::Call>,
 		len: usize,
 		result: &DispatchResult,
-		_context: &Context,
 	) -> Result<(), TransactionValidityError> {
-		let call_result = T::analyze_call_result(Some(pre), info, post_info, len, result);
+		let call_result = T::analyze_call_result(pre, info, post_info, len, result);
 
 		match call_result {
 			RelayerAccountAction::None => (),
@@ -614,7 +595,7 @@ where
 	}
 }
 
-/// Transaction extension that refunds a relayer for new messages coming from a parachain.
+/// 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
@@ -655,7 +636,7 @@ pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority,
 	)>,
 );
 
-impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundTransactionExtension
+impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension
 	for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
 where
 	Self: 'static + Send + Sync,
@@ -749,13 +730,13 @@ where
 	}
 }
 
-/// Transaction extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
+/// 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.
+/// 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(
@@ -790,7 +771,7 @@ pub struct RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund,
 	)>,
 );
 
-impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundTransactionExtension
+impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundSignedExtension
 	for RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>
 where
 	Self: 'static + Send + Sync,
@@ -888,8 +869,8 @@ mod tests {
 		Call as ParachainsCall, Pallet as ParachainsPallet, RelayBlockHash,
 	};
 	use sp_runtime::{
-		traits::{ConstU64, DispatchTransaction, Header as HeaderT},
-		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
+		traits::{ConstU64, Header as HeaderT},
+		transaction_validity::{InvalidTransaction, ValidTransaction},
 		DispatchError,
 	};
 
@@ -918,7 +899,7 @@ mod tests {
 		ConstU64<1>,
 		StrTestExtension,
 	>;
-	type TestGrandpaExtension = RefundTransactionExtensionAdapter<TestGrandpaExtensionProvider>;
+	type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
 	type TestExtensionProvider = RefundBridgedParachainMessages<
 		TestRuntime,
 		DefaultRefundableParachainId<(), TestParachain>,
@@ -927,7 +908,7 @@ mod tests {
 		ConstU64<1>,
 		StrTestExtension,
 	>;
-	type TestExtension = RefundTransactionExtensionAdapter<TestExtensionProvider>;
+	type TestExtension = RefundSignedExtensionAdapter<TestExtensionProvider>;
 
 	fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
 		let test_stake: ThisChainBalance = TestStake::get();
@@ -1426,28 +1407,14 @@ mod tests {
 
 	fn run_validate(call: RuntimeCall) -> TransactionValidity {
 		let extension: TestExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
-		extension
-			.validate_only(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|res| res.0)
+			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
+		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
 		let extension: TestGrandpaExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
-		extension
-			.validate_only(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|res| res.0)
+			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
+		extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity {
@@ -1461,30 +1428,16 @@ mod tests {
 		call: RuntimeCall,
 	) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
 		let extension: TestExtension =
-			RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
-		extension
-			.validate_and_prepare(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|(pre, _)| pre)
+			RefundSignedExtensionAdapter(RefundBridgedParachainMessages(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 =
-			RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
-		extension
-			.validate_and_prepare(
-				Some(relayer_account_at_this_chain()).into(),
-				&call,
-				&DispatchInfo::default(),
-				0,
-			)
-			.map(|(pre, _)| pre)
+			RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
+		extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
 	}
 
 	fn dispatch_info() -> DispatchInfo {
@@ -1507,12 +1460,11 @@ mod tests {
 		dispatch_result: DispatchResult,
 	) {
 		let post_dispatch_result = TestExtension::post_dispatch(
-			pre_dispatch_data,
+			Some(pre_dispatch_data),
 			&dispatch_info(),
 			&post_dispatch_info(),
 			1024,
 			&dispatch_result,
-			&(),
 		);
 		assert_eq!(post_dispatch_result, Ok(()));
 	}
diff --git a/bridges/modules/grandpa/README.md b/bridges/modules/grandpa/README.md
index 43ee5c316d1b76ec8fc94b0c3819b1340a6ce75c..992bd2cc47228249310c56747416b07be6e1e287 100644
--- a/bridges/modules/grandpa/README.md
+++ b/bridges/modules/grandpa/README.md
@@ -27,7 +27,7 @@ for provided header.
 There are two main things in GRANDPA that help building light clients:
 
 - there's no need to import all headers of the bridged chain. Light client may import finalized headers or just
-  some of finalized headders that it consider useful. While the validators set stays the same, the client may
+  some of finalized headers that it consider useful. While the validators set stays the same, the client may
   import any header that is finalized by this set;
 
 - when validators set changes, the GRANDPA gadget adds next set to the header. So light client doesn't need to
diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs
index e41e89341b312eb252bddce6e918e8367a5ce27f..4318d663a2e17fe80199830d443f5b6a85fae441 100644
--- a/bridges/modules/grandpa/src/mock.rs
+++ b/bridges/modules/grandpa/src/mock.rs
@@ -42,7 +42,7 @@ construct_runtime! {
 	}
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 }
diff --git a/bridges/modules/messages/src/mock.rs b/bridges/modules/messages/src/mock.rs
index af92120539854347111d0562e284dc59e6e251d9..ec63f15b94b5205d744b1379bd6697a4ae43534a 100644
--- a/bridges/modules/messages/src/mock.rs
+++ b/bridges/modules/messages/src/mock.rs
@@ -77,14 +77,14 @@ frame_support::construct_runtime! {
 
 pub type DbWeight = RocksDbWeight;
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 	type AccountData = pallet_balances::AccountData<Balance>;
 	type DbWeight = DbWeight;
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs
index 143f11d986371c4907f79fc4faf55143d3679034..3af3fd3e76398eaefdb7a9380344371ec44d27d7 100644
--- a/bridges/modules/parachains/src/mock.rs
+++ b/bridges/modules/parachains/src/mock.rs
@@ -161,7 +161,7 @@ construct_runtime! {
 	}
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 }
diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs
index 667b10e5c125ed74bad2aa7796756f372578c2ce..3124787896c3e1ee20014fc21b87ccbc19e6a2c2 100644
--- a/bridges/modules/relayers/src/mock.rs
+++ b/bridges/modules/relayers/src/mock.rs
@@ -59,14 +59,14 @@ parameter_types! {
 	pub const Lease: BlockNumber = 8;
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 	type AccountData = pallet_balances::AccountData<Balance>;
 	type DbWeight = DbWeight;
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
index 6dbfba5f6fdc1f521fb2fdf000ffb778740435e6..54e10966d51b23e7be5010b39cb9cb7d6a3b0118 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs
@@ -64,7 +64,7 @@ parameter_types! {
 	pub UnknownXcmVersionLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(9999)]);
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type Block = Block;
 }
diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs
index e40e1f9fb65157feffebeaa53e16c7def2ad22e0..4c09bce56d73eea717ad5149084e2ae337e48e87 100644
--- a/bridges/modules/xcm-bridge-hub/src/mock.rs
+++ b/bridges/modules/xcm-bridge-hub/src/mock.rs
@@ -64,7 +64,7 @@ parameter_types! {
 	pub const ExistentialDeposit: Balance = 1;
 }
 
-#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for TestRuntime {
 	type AccountId = AccountId;
 	type AccountData = pallet_balances::AccountData<Balance>;
@@ -72,7 +72,7 @@ impl frame_system::Config for TestRuntime {
 	type Lookup = IdentityLookup<Self::AccountId>;
 }
 
-#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for TestRuntime {
 	type AccountStore = System;
 }
diff --git a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs
index f186f6427ae7d5cbac37c0dea528665ea474803e..c49aa4b856397d28746d017fd8333ae3ad10655e 100644
--- a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs
@@ -26,7 +26,7 @@ pub use bp_polkadot_core::{
 };
 
 use bp_messages::*;
-use bp_polkadot_core::SuffixedCommonTransactionExtension;
+use bp_polkadot_core::SuffixedCommonSignedExtension;
 use bp_runtime::extensions::{
 	BridgeRejectObsoleteHeadersAndMessages, RefundBridgedParachainMessagesSchema,
 };
@@ -164,7 +164,7 @@ pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
 pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
 
 /// Signed extension that is used by all bridge hubs.
-pub type TransactionExtension = SuffixedCommonTransactionExtension<(
+pub type SignedExtension = SuffixedCommonSignedExtension<(
 	BridgeRejectObsoleteHeadersAndMessages,
 	RefundBridgedParachainMessagesSchema,
 )>;
diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
index 992ef1bd7a10f5fd95d1a5b4ec1a1267543ce615..c4e697fbe9526b85c7f10cf739c6893d50190fe9 100644
--- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
+++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs
@@ -107,5 +107,5 @@ frame_support::parameter_types! {
 
 	/// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation.
 	/// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`)
-	pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_904_835;
+	pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_829_647;
 }
diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs
index 253a1010e83df928cceae91a18c309959c33d94e..e3b4d0520f61c858b54d78dfa4a45f57bac411fb 100644
--- a/bridges/primitives/chain-kusama/src/lib.rs
+++ b/bridges/primitives/chain-kusama/src/lib.rs
@@ -59,8 +59,8 @@ impl ChainWithGrandpa for Kusama {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-// The TransactionExtension used by Kusama.
-pub use bp_polkadot_core::CommonTransactionExtension as TransactionExtension;
+// The SignedExtension used by Kusama.
+pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
 
 /// Name of the parachains pallet in the Kusama runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
index 73dd122bd153869b937ed65f8e7ea7f4dde79c7c..f2eebf9312470a42e1d3a1c7d67ab8b7a38af189 100644
--- a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
+++ b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs
@@ -25,7 +25,7 @@ use bp_runtime::{
 	decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis,
 	extensions::{
 		CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion,
-		CheckWeight, GenericTransactionExtension, GenericTransactionExtensionSchema,
+		CheckWeight, GenericSignedExtension, GenericSignedExtensionSchema,
 	},
 	Chain, ChainId, TransactionEra,
 };
@@ -37,12 +37,7 @@ use frame_support::{
 };
 use frame_system::limits;
 use scale_info::TypeInfo;
-use sp_runtime::{
-	impl_tx_ext_default,
-	traits::{Dispatchable, TransactionExtensionBase},
-	transaction_validity::TransactionValidityError,
-	Perbill,
-};
+use sp_runtime::{traits::DispatchInfoOf, transaction_validity::TransactionValidityError, Perbill};
 
 // This chain reuses most of Polkadot primitives.
 pub use bp_polkadot_core::{
@@ -76,10 +71,10 @@ pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024;
 pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 4096;
 
 /// This signed extension is used to ensure that the chain transactions are signed by proper
-pub type ValidateSigned = GenericTransactionExtensionSchema<(), ()>;
+pub type ValidateSigned = GenericSignedExtensionSchema<(), ()>;
 
 /// Signed extension schema, used by Polkadot Bulletin.
-pub type TransactionExtensionSchema = GenericTransactionExtension<(
+pub type SignedExtensionSchema = GenericSignedExtension<(
 	(
 		CheckNonZeroSender,
 		CheckSpecVersion,
@@ -92,30 +87,34 @@ pub type TransactionExtensionSchema = GenericTransactionExtension<(
 	ValidateSigned,
 )>;
 
-/// Transaction extension, used by Polkadot Bulletin.
+/// Signed extension, used by Polkadot Bulletin.
 #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
-pub struct TransactionExtension(TransactionExtensionSchema);
+pub struct SignedExtension(SignedExtensionSchema);
 
-impl TransactionExtensionBase for TransactionExtension {
+impl sp_runtime::traits::SignedExtension for SignedExtension {
 	const IDENTIFIER: &'static str = "Not needed.";
-	type Implicit = <TransactionExtensionSchema as TransactionExtensionBase>::Implicit;
+	type AccountId = ();
+	type Call = ();
+	type AdditionalSigned =
+		<SignedExtensionSchema as sp_runtime::traits::SignedExtension>::AdditionalSigned;
+	type Pre = ();
 
-	fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
-		<TransactionExtensionSchema as TransactionExtensionBase>::implicit(&self.0)
+	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
+		self.0.additional_signed()
 	}
-}
 
-impl<C, Context> sp_runtime::traits::TransactionExtension<C, Context> for TransactionExtension
-where
-	C: Dispatchable,
-{
-	type Pre = ();
-	type Val = ();
-
-	impl_tx_ext_default!(C; Context; validate prepare);
+	fn pre_dispatch(
+		self,
+		_who: &Self::AccountId,
+		_call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
+		_len: usize,
+	) -> Result<Self::Pre, TransactionValidityError> {
+		Ok(())
+	}
 }
 
-impl TransactionExtension {
+impl SignedExtension {
 	/// Create signed extension from its components.
 	pub fn from_params(
 		spec_version: u32,
@@ -124,7 +123,7 @@ impl TransactionExtension {
 		genesis_hash: Hash,
 		nonce: Nonce,
 	) -> Self {
-		Self(GenericTransactionExtension::new(
+		Self(GenericSignedExtension::new(
 			(
 				(
 					(),              // non-zero sender
diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs
index e5e2e7c3a042abddd8fb3dbd6f1decf62529cfe3..fc5e10308a8e33463a74c041f157daaef09cc9c8 100644
--- a/bridges/primitives/chain-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-polkadot/src/lib.rs
@@ -61,8 +61,8 @@ impl ChainWithGrandpa for Polkadot {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-/// The TransactionExtension used by Polkadot.
-pub type TransactionExtension = SuffixedCommonTransactionExtension<PrevalidateAttests>;
+/// The SignedExtension used by Polkadot.
+pub type SignedExtension = SuffixedCommonSignedExtension<PrevalidateAttests>;
 
 /// Name of the parachains pallet in the Polkadot runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs
index 267c6b2b1f0292b64ca8aaf969845129dae64dd8..f1b256f0f090f048cc8db3a16c112ed8b938f6ce 100644
--- a/bridges/primitives/chain-rococo/src/lib.rs
+++ b/bridges/primitives/chain-rococo/src/lib.rs
@@ -59,8 +59,8 @@ impl ChainWithGrandpa for Rococo {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-// The TransactionExtension used by Rococo.
-pub use bp_polkadot_core::CommonTransactionExtension as TransactionExtension;
+// The SignedExtension used by Rococo.
+pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
 
 /// Name of the parachains pallet in the Rococo runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs
index afa02e8ee541e5e2912b4b9a216feb633a07b617..f03fd2160a700eb3817a6feb629e9d366cc366aa 100644
--- a/bridges/primitives/chain-westend/src/lib.rs
+++ b/bridges/primitives/chain-westend/src/lib.rs
@@ -59,8 +59,8 @@ impl ChainWithGrandpa for Westend {
 	const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
 }
 
-// The TransactionExtension used by Westend.
-pub use bp_polkadot_core::CommonTransactionExtension as TransactionExtension;
+// The SignedExtension used by Westend.
+pub use bp_polkadot_core::CommonSignedExtension as SignedExtension;
 
 /// Name of the parachains pallet in the Rococo runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml
index dd3912429d66913618fe6703d61a7d7a8d86f563..4851ce14c0dad67faf4d311dbfe3947a2797f465 100644
--- a/bridges/primitives/polkadot-core/Cargo.toml
+++ b/bridges/primitives/polkadot-core/Cargo.toml
@@ -13,7 +13,7 @@ workspace = true
 codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] }
 parity-util-mem = { version = "0.12.0", optional = true }
 scale-info = { version = "2.11.0", default-features = false, features = ["derive"] }
-serde = { default-features = false, features = ["derive"], optional = true, workspace = true }
+serde = { optional = true, features = ["derive"], workspace = true, default-features = true }
 
 # Bridge Dependencies
 
diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs
index d59b99db4b586dde7b2d645ff44c34b94f865f24..df2836495bbe131e9cf810c43eb4af5eefaf43b7 100644
--- a/bridges/primitives/polkadot-core/src/lib.rs
+++ b/bridges/primitives/polkadot-core/src/lib.rs
@@ -24,8 +24,8 @@ use bp_runtime::{
 	self,
 	extensions::{
 		ChargeTransactionPayment, CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce,
-		CheckSpecVersion, CheckTxVersion, CheckWeight, GenericTransactionExtension,
-		TransactionExtensionSchema,
+		CheckSpecVersion, CheckTxVersion, CheckWeight, GenericSignedExtension,
+		SignedExtensionSchema,
 	},
 	EncodedOrDecodedCall, StorageMapKeyProvider, TransactionEra,
 };
@@ -229,12 +229,8 @@ pub type SignedBlock = generic::SignedBlock<Block>;
 pub type Balance = u128;
 
 /// Unchecked Extrinsic type.
-pub type UncheckedExtrinsic<Call, TransactionExt> = generic::UncheckedExtrinsic<
-	AccountAddress,
-	EncodedOrDecodedCall<Call>,
-	Signature,
-	TransactionExt,
->;
+pub type UncheckedExtrinsic<Call, SignedExt> =
+	generic::UncheckedExtrinsic<AccountAddress, EncodedOrDecodedCall<Call>, Signature, SignedExt>;
 
 /// Account address, used by the Polkadot-like chain.
 pub type Address = MultiAddress<AccountId, ()>;
@@ -279,7 +275,7 @@ impl AccountInfoStorageMapKeyProvider {
 }
 
 /// Extra signed extension data that is used by most chains.
-pub type CommonTransactionExtra = (
+pub type CommonSignedExtra = (
 	CheckNonZeroSender,
 	CheckSpecVersion,
 	CheckTxVersion,
@@ -290,12 +286,12 @@ pub type CommonTransactionExtra = (
 	ChargeTransactionPayment<Balance>,
 );
 
-/// Extra transaction extension data that starts with `CommonTransactionExtra`.
-pub type SuffixedCommonTransactionExtension<Suffix> =
-	GenericTransactionExtension<(CommonTransactionExtra, Suffix)>;
+/// Extra signed extension data that starts with `CommonSignedExtra`.
+pub type SuffixedCommonSignedExtension<Suffix> =
+	GenericSignedExtension<(CommonSignedExtra, Suffix)>;
 
-/// Helper trait to define some extra methods on `SuffixedCommonTransactionExtension`.
-pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSchema> {
+/// Helper trait to define some extra methods on `SuffixedCommonSignedExtension`.
+pub trait SuffixedCommonSignedExtensionExt<Suffix: SignedExtensionSchema> {
 	/// Create signed extension from its components.
 	fn from_params(
 		spec_version: u32,
@@ -304,7 +300,7 @@ pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSche
 		genesis_hash: Hash,
 		nonce: Nonce,
 		tip: Balance,
-		extra: (Suffix::Payload, Suffix::Implicit),
+		extra: (Suffix::Payload, Suffix::AdditionalSigned),
 	) -> Self;
 
 	/// Return transaction nonce.
@@ -314,10 +310,9 @@ pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSche
 	fn tip(&self) -> Balance;
 }
 
-impl<Suffix> SuffixedCommonTransactionExtensionExt<Suffix>
-	for SuffixedCommonTransactionExtension<Suffix>
+impl<Suffix> SuffixedCommonSignedExtensionExt<Suffix> for SuffixedCommonSignedExtension<Suffix>
 where
-	Suffix: TransactionExtensionSchema,
+	Suffix: SignedExtensionSchema,
 {
 	fn from_params(
 		spec_version: u32,
@@ -326,9 +321,9 @@ where
 		genesis_hash: Hash,
 		nonce: Nonce,
 		tip: Balance,
-		extra: (Suffix::Payload, Suffix::Implicit),
+		extra: (Suffix::Payload, Suffix::AdditionalSigned),
 	) -> Self {
-		GenericTransactionExtension::new(
+		GenericSignedExtension::new(
 			(
 				(
 					(),              // non-zero sender
@@ -370,7 +365,7 @@ where
 }
 
 /// Signed extension that is used by most chains.
-pub type CommonTransactionExtension = SuffixedCommonTransactionExtension<()>;
+pub type CommonSignedExtension = SuffixedCommonSignedExtension<()>;
 
 #[cfg(test)]
 mod tests {
diff --git a/bridges/primitives/runtime/src/extensions.rs b/bridges/primitives/runtime/src/extensions.rs
index a31e7b5bb47a64ec2333bbaba3e9c520aa53ef5a..d896bc92efffc4e8fcb427ffa7057dece6f17241 100644
--- a/bridges/primitives/runtime/src/extensions.rs
+++ b/bridges/primitives/runtime/src/extensions.rs
@@ -20,138 +20,135 @@ use codec::{Compact, Decode, Encode};
 use impl_trait_for_tuples::impl_for_tuples;
 use scale_info::{StaticTypeInfo, TypeInfo};
 use sp_runtime::{
-	impl_tx_ext_default,
-	traits::{Dispatchable, TransactionExtension, TransactionExtensionBase},
+	traits::{DispatchInfoOf, SignedExtension},
 	transaction_validity::TransactionValidityError,
 };
 use sp_std::{fmt::Debug, marker::PhantomData};
 
-/// Trait that describes some properties of a `TransactionExtension` that are needed in order to
-/// send a transaction to the chain.
-pub trait TransactionExtensionSchema:
-	Encode + Decode + Debug + Eq + Clone + StaticTypeInfo
-{
+/// Trait that describes some properties of a `SignedExtension` that are needed in order to send a
+/// transaction to the chain.
+pub trait SignedExtensionSchema: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo {
 	/// A type of the data encoded as part of the transaction.
 	type Payload: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo;
 	/// Parameters which are part of the payload used to produce transaction signature,
 	/// but don't end up in the transaction itself (i.e. inherent part of the runtime).
-	type Implicit: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo;
+	type AdditionalSigned: Encode + Debug + Eq + Clone + StaticTypeInfo;
 }
 
-impl TransactionExtensionSchema for () {
+impl SignedExtensionSchema for () {
 	type Payload = ();
-	type Implicit = ();
+	type AdditionalSigned = ();
 }
 
-/// An implementation of `TransactionExtensionSchema` using generic params.
+/// An implementation of `SignedExtensionSchema` using generic params.
 #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)]
-pub struct GenericTransactionExtensionSchema<P, S>(PhantomData<(P, S)>);
+pub struct GenericSignedExtensionSchema<P, S>(PhantomData<(P, S)>);
 
-impl<P, S> TransactionExtensionSchema for GenericTransactionExtensionSchema<P, S>
+impl<P, S> SignedExtensionSchema for GenericSignedExtensionSchema<P, S>
 where
 	P: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo,
-	S: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo,
+	S: Encode + Debug + Eq + Clone + StaticTypeInfo,
 {
 	type Payload = P;
-	type Implicit = S;
+	type AdditionalSigned = S;
 }
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckNonZeroSender`.
-pub type CheckNonZeroSender = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `frame_system::CheckNonZeroSender`.
+pub type CheckNonZeroSender = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckSpecVersion`.
-pub type CheckSpecVersion = GenericTransactionExtensionSchema<(), u32>;
+/// The `SignedExtensionSchema` for `frame_system::CheckSpecVersion`.
+pub type CheckSpecVersion = GenericSignedExtensionSchema<(), u32>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckTxVersion`.
-pub type CheckTxVersion = GenericTransactionExtensionSchema<(), u32>;
+/// The `SignedExtensionSchema` for `frame_system::CheckTxVersion`.
+pub type CheckTxVersion = GenericSignedExtensionSchema<(), u32>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckGenesis`.
-pub type CheckGenesis<Hash> = GenericTransactionExtensionSchema<(), Hash>;
+/// The `SignedExtensionSchema` for `frame_system::CheckGenesis`.
+pub type CheckGenesis<Hash> = GenericSignedExtensionSchema<(), Hash>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckEra`.
-pub type CheckEra<Hash> = GenericTransactionExtensionSchema<sp_runtime::generic::Era, Hash>;
+/// The `SignedExtensionSchema` for `frame_system::CheckEra`.
+pub type CheckEra<Hash> = GenericSignedExtensionSchema<sp_runtime::generic::Era, Hash>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckNonce`.
-pub type CheckNonce<TxNonce> = GenericTransactionExtensionSchema<Compact<TxNonce>, ()>;
+/// The `SignedExtensionSchema` for `frame_system::CheckNonce`.
+pub type CheckNonce<TxNonce> = GenericSignedExtensionSchema<Compact<TxNonce>, ()>;
 
-/// The `TransactionExtensionSchema` for `frame_system::CheckWeight`.
-pub type CheckWeight = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `frame_system::CheckWeight`.
+pub type CheckWeight = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `pallet_transaction_payment::ChargeTransactionPayment`.
-pub type ChargeTransactionPayment<Balance> =
-	GenericTransactionExtensionSchema<Compact<Balance>, ()>;
+/// The `SignedExtensionSchema` for `pallet_transaction_payment::ChargeTransactionPayment`.
+pub type ChargeTransactionPayment<Balance> = GenericSignedExtensionSchema<Compact<Balance>, ()>;
 
-/// The `TransactionExtensionSchema` for `polkadot-runtime-common::PrevalidateAttests`.
-pub type PrevalidateAttests = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `polkadot-runtime-common::PrevalidateAttests`.
+pub type PrevalidateAttests = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `BridgeRejectObsoleteHeadersAndMessages`.
-pub type BridgeRejectObsoleteHeadersAndMessages = GenericTransactionExtensionSchema<(), ()>;
+/// The `SignedExtensionSchema` for `BridgeRejectObsoleteHeadersAndMessages`.
+pub type BridgeRejectObsoleteHeadersAndMessages = GenericSignedExtensionSchema<(), ()>;
 
-/// The `TransactionExtensionSchema` for `RefundBridgedParachainMessages`.
+/// The `SignedExtensionSchema` for `RefundBridgedParachainMessages`.
 /// This schema is dedicated for `RefundBridgedParachainMessages` signed extension as
 /// wildcard/placeholder, which relies on the scale encoding for `()` or `((), ())`, or `((), (),
 /// ())` is the same. So runtime can contains any kind of tuple:
 /// `(BridgeRefundBridgeHubRococoMessages)`
 /// `(BridgeRefundBridgeHubRococoMessages, BridgeRefundBridgeHubWestendMessages)`
 /// `(BridgeRefundParachainMessages1, ..., BridgeRefundParachainMessagesN)`
-pub type RefundBridgedParachainMessagesSchema = GenericTransactionExtensionSchema<(), ()>;
+pub type RefundBridgedParachainMessagesSchema = GenericSignedExtensionSchema<(), ()>;
 
 #[impl_for_tuples(1, 12)]
-impl TransactionExtensionSchema for Tuple {
+impl SignedExtensionSchema for Tuple {
 	for_tuples!( type Payload = ( #( Tuple::Payload ),* ); );
-	for_tuples!( type Implicit = ( #( Tuple::Implicit ),* ); );
+	for_tuples!( type AdditionalSigned = ( #( Tuple::AdditionalSigned ),* ); );
 }
 
 /// A simplified version of signed extensions meant for producing signed transactions
 /// and signed payloads in the client code.
 #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
-pub struct GenericTransactionExtension<S: TransactionExtensionSchema> {
+pub struct GenericSignedExtension<S: SignedExtensionSchema> {
 	/// A payload that is included in the transaction.
 	pub payload: S::Payload,
 	#[codec(skip)]
 	// It may be set to `None` if extensions are decoded. We are never reconstructing transactions
-	// (and it makes no sense to do that) => decoded version of `TransactionExtensions` is only
-	// used to read fields of the `payload`. And when resigning transaction, we're reconstructing
-	// `TransactionExtensions` from scratch.
-	implicit: Option<S::Implicit>,
+	// (and it makes no sense to do that) => decoded version of `SignedExtensions` is only used to
+	// read fields of the `payload`. And when resigning transaction, we're reconstructing
+	// `SignedExtensions` from scratch.
+	additional_signed: Option<S::AdditionalSigned>,
 }
 
-impl<S: TransactionExtensionSchema> GenericTransactionExtension<S> {
-	/// Create new `GenericTransactionExtension` object.
-	pub fn new(payload: S::Payload, implicit: Option<S::Implicit>) -> Self {
-		Self { payload, implicit }
+impl<S: SignedExtensionSchema> GenericSignedExtension<S> {
+	/// Create new `GenericSignedExtension` object.
+	pub fn new(payload: S::Payload, additional_signed: Option<S::AdditionalSigned>) -> Self {
+		Self { payload, additional_signed }
 	}
 }
 
-impl<S> TransactionExtensionBase for GenericTransactionExtension<S>
+impl<S> SignedExtension for GenericSignedExtension<S>
 where
-	S: TransactionExtensionSchema,
+	S: SignedExtensionSchema,
 	S::Payload: Send + Sync,
-	S::Implicit: Send + Sync,
+	S::AdditionalSigned: Send + Sync,
 {
 	const IDENTIFIER: &'static str = "Not needed.";
-	type Implicit = S::Implicit;
+	type AccountId = ();
+	type Call = ();
+	type AdditionalSigned = S::AdditionalSigned;
+	type Pre = ();
 
-	fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
+	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
 		// we shall not ever see this error in relay, because we are never signing decoded
 		// transactions. Instead we're constructing and signing new transactions. So the error code
 		// is kinda random here
-		self.implicit
-			.clone()
-			.ok_or(frame_support::unsigned::TransactionValidityError::Unknown(
+		self.additional_signed.clone().ok_or(
+			frame_support::unsigned::TransactionValidityError::Unknown(
 				frame_support::unsigned::UnknownTransaction::Custom(0xFF),
-			))
+			),
+		)
 	}
-}
-impl<S, C, Context> TransactionExtension<C, Context> for GenericTransactionExtension<S>
-where
-	C: Dispatchable,
-	S: TransactionExtensionSchema,
-	S::Payload: Send + Sync,
-	S::Implicit: Send + Sync,
-{
-	type Pre = ();
-	type Val = ();
 
-	impl_tx_ext_default!(C; Context; validate prepare);
+	fn pre_dispatch(
+		self,
+		_who: &Self::AccountId,
+		_call: &Self::Call,
+		_info: &DispatchInfoOf<Self::Call>,
+		_len: usize,
+	) -> Result<Self::Pre, TransactionValidityError> {
+		Ok(())
+	}
 }
diff --git a/bridges/relays/bin-substrate/Cargo.toml b/bridges/relays/bin-substrate/Cargo.toml
index d5873752e22f852e8708f4802cf6336836d1f213..2b89ce4f1989c1cd8a2ba777af6963c85b805ff3 100644
--- a/bridges/relays/bin-substrate/Cargo.toml
+++ b/bridges/relays/bin-substrate/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 [dependencies]
 anyhow = "1.0"
 async-std = "1.9.0"
-async-trait = "0.1"
+async-trait = "0.1.74"
 codec = { package = "parity-scale-codec", version = "3.1.5" }
 env_logger = "0.11"
 futures = "0.3.30"
diff --git a/bridges/relays/client-bridge-hub-kusama/src/lib.rs b/bridges/relays/client-bridge-hub-kusama/src/lib.rs
index 80f621dee302b660b2e0863bac4ee8c7cceb2345..43dd53d2d83a2d0324ac987b37f4dc7ad41056da 100644
--- a/bridges/relays/client-bridge-hub-kusama/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-kusama/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_kusama::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_kusama::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -37,8 +37,7 @@ pub type RuntimeCall = runtime_types::bridge_hub_kusama_runtime::RuntimeCall;
 pub type BridgeMessagesCall = runtime_types::pallet_bridge_messages::pallet::Call;
 pub type BridgeGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_kusama::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_kusama::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Kusama chain definition
@@ -88,7 +87,7 @@ impl ChainWithTransactions for BridgeHubKusama {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-bridge-hub-polkadot/src/lib.rs b/bridges/relays/client-bridge-hub-polkadot/src/lib.rs
index ed147e92d5bd578c7934cb7c860dd3a8239e5bf4..88b69065f84217c0b08db88e3b47131c4076a075 100644
--- a/bridges/relays/client-bridge-hub-polkadot/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-polkadot/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_polkadot::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_polkadot::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -41,8 +41,7 @@ pub type BridgeKusamaMessagesCall = runtime_types::pallet_bridge_messages::palle
 pub type BridgePolkadotBulletinGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeKusamaGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_polkadot::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_polkadot::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Polkadot chain definition
@@ -92,7 +91,7 @@ impl ChainWithTransactions for BridgeHubPolkadot {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-bridge-hub-rococo/src/lib.rs b/bridges/relays/client-bridge-hub-rococo/src/lib.rs
index 169135646d94be6d97d7dfab64660cfdb717458e..cae9e4f77a501dd843ae975f06eba5ca6a7cb212 100644
--- a/bridges/relays/client-bridge-hub-rococo/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-rococo/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_rococo::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_rococo::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -39,8 +39,7 @@ pub type BridgeBulletinMessagesCall = runtime_types::pallet_bridge_messages::pal
 pub type BridgeGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeBulletinGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call2;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_rococo::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_rococo::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Rococo chain definition
@@ -90,7 +89,7 @@ impl ChainWithTransactions for BridgeHubRococo {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-bridge-hub-westend/src/lib.rs b/bridges/relays/client-bridge-hub-westend/src/lib.rs
index 5e4462f863aa1804f8629d0aa18f33ad6fa3c33d..049fa4649609eab482501b6e2614b94a2e56c0c5 100644
--- a/bridges/relays/client-bridge-hub-westend/src/lib.rs
+++ b/bridges/relays/client-bridge-hub-westend/src/lib.rs
@@ -18,8 +18,8 @@
 
 pub mod codegen_runtime;
 
-use bp_bridge_hub_westend::{TransactionExtension, AVERAGE_BLOCK_INTERVAL};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_bridge_hub_westend::{SignedExtension, AVERAGE_BLOCK_INTERVAL};
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	calls::UtilityCall as MockUtilityCall, Chain, ChainWithBalances, ChainWithMessages,
@@ -37,8 +37,7 @@ pub type RuntimeCall = runtime_types::bridge_hub_westend_runtime::RuntimeCall;
 pub type BridgeMessagesCall = runtime_types::pallet_bridge_messages::pallet::Call;
 pub type BridgeGrandpaCall = runtime_types::pallet_bridge_grandpa::pallet::Call;
 pub type BridgeParachainCall = runtime_types::pallet_bridge_parachains::pallet::Call;
-type UncheckedExtrinsic =
-	bp_bridge_hub_westend::UncheckedExtrinsic<RuntimeCall, TransactionExtension>;
+type UncheckedExtrinsic = bp_bridge_hub_westend::UncheckedExtrinsic<RuntimeCall, SignedExtension>;
 type UtilityCall = runtime_types::pallet_utility::pallet::Call;
 
 /// Westend chain definition
@@ -88,7 +87,7 @@ impl ChainWithTransactions for BridgeHubWestend {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			TransactionExtension::from_params(
+			SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-kusama/src/lib.rs b/bridges/relays/client-kusama/src/lib.rs
index a953a383e1290672168606e734a81f50e8d845cf..24a6adfe95d4c2a55d1b0aef08913878319835d1 100644
--- a/bridges/relays/client-kusama/src/lib.rs
+++ b/bridges/relays/client-kusama/src/lib.rs
@@ -19,7 +19,7 @@
 pub mod codegen_runtime;
 
 use bp_kusama::{AccountInfoStorageMapKeyProvider, KUSAMA_SYNCED_HEADERS_GRANDPA_INFO_METHOD};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	Chain, ChainWithBalances, ChainWithGrandpa, ChainWithRuntimeVersion, ChainWithTransactions,
@@ -84,7 +84,7 @@ impl RelayChain for Kusama {
 impl ChainWithTransactions for Kusama {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_kusama::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_kusama::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Kusama {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_kusama::TransactionExtension::from_params(
+			bp_kusama::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-polkadot-bulletin/src/lib.rs b/bridges/relays/client-polkadot-bulletin/src/lib.rs
index a6cdd8ee675cf2ed3a0395a1dd8b1cdcbf8f56fe..1f18b25a9054a62cd552ad9a3662e319b2a976eb 100644
--- a/bridges/relays/client-polkadot-bulletin/src/lib.rs
+++ b/bridges/relays/client-polkadot-bulletin/src/lib.rs
@@ -100,10 +100,8 @@ impl ChainWithBalances for PolkadotBulletin {
 
 impl ChainWithTransactions for PolkadotBulletin {
 	type AccountKeyPair = sp_core::sr25519::Pair;
-	type SignedTransaction = bp_polkadot_bulletin::UncheckedExtrinsic<
-		Self::Call,
-		bp_polkadot_bulletin::TransactionExtension,
-	>;
+	type SignedTransaction =
+		bp_polkadot_bulletin::UncheckedExtrinsic<Self::Call, bp_polkadot_bulletin::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -111,7 +109,7 @@ impl ChainWithTransactions for PolkadotBulletin {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_polkadot_bulletin::TransactionExtension::from_params(
+			bp_polkadot_bulletin::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-polkadot/src/lib.rs b/bridges/relays/client-polkadot/src/lib.rs
index af75f0dbb8713b4c16bbad852340c3e478ec0d44..9b655528b6e4e6cc5d8253306650e4ced7887e27 100644
--- a/bridges/relays/client-polkadot/src/lib.rs
+++ b/bridges/relays/client-polkadot/src/lib.rs
@@ -19,7 +19,7 @@
 mod codegen_runtime;
 
 use bp_polkadot::{AccountInfoStorageMapKeyProvider, POLKADOT_SYNCED_HEADERS_GRANDPA_INFO_METHOD};
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use codec::Encode;
 use relay_substrate_client::{
 	Chain, ChainWithBalances, ChainWithGrandpa, ChainWithRuntimeVersion, ChainWithTransactions,
@@ -84,7 +84,7 @@ impl RelayChain for Polkadot {
 impl ChainWithTransactions for Polkadot {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_polkadot::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_polkadot::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Polkadot {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_polkadot::TransactionExtension::from_params(
+			bp_polkadot::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-rococo/src/lib.rs b/bridges/relays/client-rococo/src/lib.rs
index 5fad80d5c9886a34ad33c02409b4847edea29087..575660504b3a51b05005bfe25f92ee21ec3024a0 100644
--- a/bridges/relays/client-rococo/src/lib.rs
+++ b/bridges/relays/client-rococo/src/lib.rs
@@ -18,7 +18,7 @@
 
 pub mod codegen_runtime;
 
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use bp_rococo::ROCOCO_SYNCED_HEADERS_GRANDPA_INFO_METHOD;
 use codec::Encode;
 use relay_substrate_client::{
@@ -84,7 +84,7 @@ impl RelayChain for Rococo {
 impl ChainWithTransactions for Rococo {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_rococo::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_rococo::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Rococo {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_rococo::TransactionExtension::from_params(
+			bp_rococo::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/client-substrate/Cargo.toml b/bridges/relays/client-substrate/Cargo.toml
index ea02a0ad20159adbd2446c9664ec10f849d5ad93..48895eff39dae46a4175aa4d333d554af7188d88 100644
--- a/bridges/relays/client-substrate/Cargo.toml
+++ b/bridges/relays/client-substrate/Cargo.toml
@@ -10,7 +10,7 @@ workspace = true
 
 [dependencies]
 async-std = { version = "1.6.5", features = ["attributes"] }
-async-trait = "0.1"
+async-trait = "0.1.74"
 codec = { package = "parity-scale-codec", version = "3.1.5" }
 futures = "0.3.30"
 jsonrpsee = { version = "0.17", features = ["macros", "ws-client"] }
diff --git a/bridges/relays/client-westend/src/lib.rs b/bridges/relays/client-westend/src/lib.rs
index 737c6c08519e1f340fab299f5970bbd1030ba300..42206baecf8f6f3f446004ba2a2a41f552fcbbbe 100644
--- a/bridges/relays/client-westend/src/lib.rs
+++ b/bridges/relays/client-westend/src/lib.rs
@@ -18,7 +18,7 @@
 
 pub mod codegen_runtime;
 
-use bp_polkadot_core::SuffixedCommonTransactionExtensionExt;
+use bp_polkadot_core::SuffixedCommonSignedExtensionExt;
 use bp_westend::WESTEND_SYNCED_HEADERS_GRANDPA_INFO_METHOD;
 use codec::Encode;
 use relay_substrate_client::{
@@ -84,7 +84,7 @@ impl ChainWithBalances for Westend {
 impl ChainWithTransactions for Westend {
 	type AccountKeyPair = sp_core::sr25519::Pair;
 	type SignedTransaction =
-		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_westend::TransactionExtension>;
+		bp_polkadot_core::UncheckedExtrinsic<Self::Call, bp_westend::SignedExtension>;
 
 	fn sign_transaction(
 		param: SignParam<Self>,
@@ -92,7 +92,7 @@ impl ChainWithTransactions for Westend {
 	) -> Result<Self::SignedTransaction, SubstrateError> {
 		let raw_payload = SignedPayload::new(
 			unsigned.call,
-			bp_westend::TransactionExtension::from_params(
+			bp_westend::SignedExtension::from_params(
 				param.spec_version,
 				param.transaction_version,
 				unsigned.era,
diff --git a/bridges/relays/equivocation/Cargo.toml b/bridges/relays/equivocation/Cargo.toml
index 0b4a7e983a3a8bf8a9f0e6bcc1430f03da22bf63..23dd4a087f97b0b8e1645e3df9b4a06b4325d4fc 100644
--- a/bridges/relays/equivocation/Cargo.toml
+++ b/bridges/relays/equivocation/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 
 [dependencies]
 async-std = { version = "1.6.5", features = ["attributes"] }
-async-trait = "0.1"
+async-trait = "0.1.74"
 bp-header-chain = { path = "../../primitives/header-chain" }
 finality-relay = { path = "../finality" }
 frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" }
diff --git a/bridges/relays/finality/Cargo.toml b/bridges/relays/finality/Cargo.toml
index 9e8bf56f53aaac0ec4fd1b9b51ff57334570340d..80159b4db62a79d3e905662b269ab5e61a3e5f56 100644
--- a/bridges/relays/finality/Cargo.toml
+++ b/bridges/relays/finality/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 
 [dependencies]
 async-std = "1.6.5"
-async-trait = "0.1"
+async-trait = "0.1.74"
 backoff = "0.4"
 bp-header-chain = { path = "../../primitives/header-chain" }
 futures = "0.3.30"
diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml
index d85e2762a4e337f5ff10f947bc88f0904aa912d3..27c5ee02c759f8357a401e77d2d2598b92262fb1 100644
--- a/bridges/relays/lib-substrate-relay/Cargo.toml
+++ b/bridges/relays/lib-substrate-relay/Cargo.toml
@@ -11,7 +11,7 @@ workspace = true
 [dependencies]
 anyhow = "1.0"
 async-std = "1.9.0"
-async-trait = "0.1"
+async-trait = "0.1.74"
 codec = { package = "parity-scale-codec", version = "3.1.5" }
 futures = "0.3.30"
 hex = "0.4"
diff --git a/bridges/relays/messages/Cargo.toml b/bridges/relays/messages/Cargo.toml
index 3367e4bbd443900298aae2e87bab5ed0ef50fa1a..699649dd619d756d9818966603227cae5022c5a3 100644
--- a/bridges/relays/messages/Cargo.toml
+++ b/bridges/relays/messages/Cargo.toml
@@ -10,7 +10,7 @@ workspace = true
 
 [dependencies]
 async-std = { version = "1.6.5", features = ["attributes"] }
-async-trait = "0.1"
+async-trait = "0.1.74"
 env_logger = "0.11"
 futures = "0.3.30"
 hex = "0.4"
diff --git a/bridges/relays/parachains/Cargo.toml b/bridges/relays/parachains/Cargo.toml
index 9dc35343b48c607048bd9219dc34dce3c6c80bfb..d2eea9eb6a77a261253ebf5ca0165148e81d5043 100644
--- a/bridges/relays/parachains/Cargo.toml
+++ b/bridges/relays/parachains/Cargo.toml
@@ -10,7 +10,7 @@ workspace = true
 
 [dependencies]
 async-std = "1.6.5"
-async-trait = "0.1"
+async-trait = "0.1.74"
 futures = "0.3.30"
 log = { workspace = true }
 relay-utils = { path = "../utils" }
diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml
index ed6093318a0dcf0bbfb76250f7bcd2d04b942387..81574a6855148dabbea69a8c68c46767d6b5c562 100644
--- a/bridges/relays/utils/Cargo.toml
+++ b/bridges/relays/utils/Cargo.toml
@@ -12,7 +12,7 @@ workspace = true
 ansi_term = "0.12"
 anyhow = "1.0"
 async-std = "1.6.5"
-async-trait = "0.1"
+async-trait = "0.1.74"
 backoff = "0.4"
 isahc = "1.2"
 env_logger = "0.11.3"
diff --git a/bridges/testing/README.md b/bridges/testing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bd467a410d013c363913a8e4b2be8ca7b184e2dc
--- /dev/null
+++ b/bridges/testing/README.md
@@ -0,0 +1,31 @@
+# Bridges Tests for Local Rococo <> Westend Bridge
+
+This folder contains [zombienet](https://github.com/paritytech/zombienet/) based integration tests for both
+onchain and offchain bridges code. Due to some
+[technical difficulties](https://github.com/paritytech/parity-bridges-common/pull/2649#issue-1965339051), we
+are using native zombienet provider, which means that you need to build some binaries locally.
+
+To start those tests, you need to:
+
+- download latest [zombienet release](https://github.com/paritytech/zombienet/releases);
+
+- build Polkadot binary by running `cargo build -p polkadot --release  --features fast-runtime` command in the
+[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone;
+
+- build Polkadot Parachain binary by running `cargo build -p polkadot-parachain-bin --release` command in the
+[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone;
+
+- ensure that you have [`node`](https://nodejs.org/en) installed. Additionally, we'll need globally installed
+`polkadot/api-cli` package (use `npm install -g @polkadot/api-cli@beta` to install it);
+
+- build Substrate relay by running `cargo build -p substrate-relay --release` command in the
+[`parity-bridges-common`](https://github.com/paritytech/parity-bridges-common) repository clone.
+
+- copy fresh `substrate-relay` binary, built in previous point, to the `~/local_bridge_testing/bin/substrate-relay`;
+
+- change the `POLKADOT_SDK_PATH` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables
+have correct values) in the `./run-tests.sh`.
+
+After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the
+"All tests have completed successfully" message in the end. Otherwise, it'll print paths to zombienet
+process logs, which, in turn, may be used to track locations of all spinned relay and parachain nodes.
diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml
new file mode 100644
index 0000000000000000000000000000000000000000..52271f9442131923f8a758b16df7610e73813d15
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml
@@ -0,0 +1,88 @@
+[settings]
+node_spawn_timeout = 240
+
+[relaychain]
+default_command = "{{POLKADOT_BINARY}}"
+default_args = [ "-lparachain=debug,xcm=trace" ]
+chain = "rococo-local"
+
+	[[relaychain.nodes]]
+	name = "alice-rococo-validator"
+	validator = true
+	rpc_port = 9932
+	ws_port = 9942
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "bob-rococo-validator"
+	validator = true
+	rpc_port = 9933
+	ws_port = 9943
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "charlie-rococo-validator"
+	validator = true
+	rpc_port = 9934
+	ws_port = 9944
+	balance = 2000000000000
+
+[[parachains]]
+id = 1013
+chain = "bridge-hub-rococo-local"
+cumulus_based = true
+
+	# run alice as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-rococo-collator1"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8933
+	ws_port = 8943
+	args = [
+		"-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+	# run bob as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-rococo-collator2"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8934
+	ws_port = 8944
+	args = [
+		"-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+[[parachains]]
+id = 1000
+chain = "asset-hub-rococo-local"
+cumulus_based = true
+
+	[[parachains.collators]]
+	name = "asset-hub-rococo-collator1"
+	rpc_port = 9911
+	ws_port = 9910
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+	[[parachains.collators]]
+	name = "asset-hub-rococo-collator2"
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+#[[hrmp_channels]]
+#sender = 1000
+#recipient = 1013
+#max_capacity = 4
+#max_message_size = 524288
+#
+#[[hrmp_channels]]
+#sender = 1013
+#recipient = 1000
+#max_capacity = 4
+#max_message_size = 524288
diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml
new file mode 100644
index 0000000000000000000000000000000000000000..f2550bcc9959638b21ea78043cca3bc12d3d23ea
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml
@@ -0,0 +1,88 @@
+[settings]
+node_spawn_timeout = 240
+
+[relaychain]
+default_command = "{{POLKADOT_BINARY}}"
+default_args = [ "-lparachain=debug,xcm=trace" ]
+chain = "westend-local"
+
+	[[relaychain.nodes]]
+	name = "alice-westend-validator"
+	validator = true
+	rpc_port = 9935
+	ws_port = 9945
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "bob-westend-validator"
+	validator = true
+	rpc_port = 9936
+	ws_port = 9946
+	balance = 2000000000000
+
+	[[relaychain.nodes]]
+	name = "charlie-westend-validator"
+	validator = true
+	rpc_port = 9937
+	ws_port = 9947
+	balance = 2000000000000
+
+[[parachains]]
+id = 1002
+chain = "bridge-hub-westend-local"
+cumulus_based = true
+
+	# run alice as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-westend-collator1"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8935
+	ws_port = 8945
+	args = [
+		"-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+	# run bob as parachain collator
+	[[parachains.collators]]
+	name = "bridge-hub-westend-collator2"
+	validator = true
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	rpc_port = 8936
+	ws_port = 8946
+	args = [
+		"-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
+	]
+
+[[parachains]]
+id = 1000
+chain = "asset-hub-westend-local"
+cumulus_based = true
+
+	[[parachains.collators]]
+	name = "asset-hub-westend-collator1"
+	rpc_port = 9011
+	ws_port = 9010
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+	[[parachains.collators]]
+	name = "asset-hub-westend-collator2"
+	command = "{{POLKADOT_PARACHAIN_BINARY}}"
+	args = [
+		"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
+	]
+
+#[[hrmp_channels]]
+#sender = 1000
+#recipient = 1002
+#max_capacity = 4
+#max_message_size = 524288
+#
+#[[hrmp_channels]]
+#sender = 1002
+#recipient = 1000
+#max_capacity = 4
+#max_message_size = 524288
diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
new file mode 100755
index 0000000000000000000000000000000000000000..66c9ddc037b8efb005d2239b174eb5710dddaf53
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
@@ -0,0 +1,401 @@
+#!/bin/bash
+
+# import common functions
+source "$FRAMEWORK_PATH/utils/bridges.sh"
+
+# Expected sovereign accounts.
+#
+# Generated by:
+#
+#  #[test]
+#	fn generate_sovereign_accounts() {
+#		use sp_core::crypto::Ss58Codec;
+#		use polkadot_parachain_primitives::primitives::Sibling;
+#
+#		parameter_types! {
+#			pub UniversalLocationAHR: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(1000));
+#			pub UniversalLocationAHW: InteriorMultiLocation = X2(GlobalConsensus(Westend), Parachain(1000));
+#		}
+#
+#		// SS58=42
+#		println!("GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 GlobalConsensusConvertsFor::<UniversalLocationAHW, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Rococo)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#		println!("ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 SiblingParachainConvertsVia::<Sibling, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#
+#		// SS58=42
+#		println!("GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 GlobalConsensusConvertsFor::<UniversalLocationAHR, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Westend)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#		println!("ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO=\"{}\"",
+#				 frame_support::sp_runtime::AccountId32::new(
+#					 SiblingParachainConvertsVia::<Sibling, [u8; 32]>::convert_location(
+#						 &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap()
+#				 ).to_ss58check_with_version(42_u16.into())
+#		);
+#	}
+GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT="5GxRGwT8bU1JeBPTUXc7LEjZMxNrK8MyL2NJnkWFQJTQ4sii"
+ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV"
+GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT="5He2Qdztyxxa4GoagY6q1jaiLMmKy1gXS7PdZkhfj8ZG9hk5"
+ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV"
+
+# Expected sovereign accounts for rewards on BridgeHubs.
+#
+# Generated by:
+#	#[test]
+#	fn generate_sovereign_accounts_for_rewards() {
+#		use bp_messages::LaneId;
+#		use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams};
+#		use sp_core::crypto::Ss58Codec;
+#
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhwd",
+#					RewardsAccountOwner::ThisChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhwd",
+#					RewardsAccountOwner::BridgedChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhro",
+#					RewardsAccountOwner::ThisChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#		// SS58=42
+#		println!(
+#			"ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"",
+#			frame_support::sp_runtime::AccountId32::new(
+#				PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new(
+#					LaneId([0, 0, 0, 2]),
+#					*b"bhro",
+#					RewardsAccountOwner::BridgedChain
+#				))
+#			)
+#				.to_ss58check_with_version(42_u16.into())
+#		);
+#	}
+ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5BhiSGP5hbdsoVGtzi2sQVgpDNToTxLYeQvKoMPEm"
+ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5BhiSGP5hbdt5EJSapXYbxEv678jyWHEUskCXcjqo"
+ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5BhiSGP5h9Rg8sgUJqoLym3iEaWUiboT8S9AT5xFh"
+ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5BhiSGP5h9RgQci1txJ2BDbp7KBRE9k8xty3BMUSi"
+
+LANE_ID="00000002"
+XCM_VERSION=3
+
+function init_ro_wnd() {
+    local relayer_path=$(ensure_relayer)
+
+    RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
+        $relayer_path init-bridge rococo-to-bridge-hub-westend \
+	--source-host localhost \
+	--source-port 9942 \
+	--source-version-mode Auto \
+	--target-host localhost \
+	--target-port 8945 \
+	--target-version-mode Auto \
+	--target-signer //Bob
+}
+
+function init_wnd_ro() {
+    local relayer_path=$(ensure_relayer)
+
+    RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
+        $relayer_path init-bridge westend-to-bridge-hub-rococo \
+        --source-host localhost \
+        --source-port 9945 \
+        --source-version-mode Auto \
+        --target-host localhost \
+        --target-port 8943 \
+        --target-version-mode Auto \
+        --target-signer //Bob
+}
+
+function run_relay() {
+    local relayer_path=$(ensure_relayer)
+
+    RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
+        $relayer_path relay-headers-and-messages bridge-hub-rococo-bridge-hub-westend \
+        --rococo-host localhost \
+        --rococo-port 9942 \
+        --rococo-version-mode Auto \
+        --bridge-hub-rococo-host localhost \
+        --bridge-hub-rococo-port 8943 \
+        --bridge-hub-rococo-version-mode Auto \
+        --bridge-hub-rococo-signer //Charlie \
+        --bridge-hub-rococo-transactions-mortality 4 \
+        --westend-host localhost \
+        --westend-port 9945 \
+        --westend-version-mode Auto \
+        --bridge-hub-westend-host localhost \
+        --bridge-hub-westend-port 8945 \
+        --bridge-hub-westend-version-mode Auto \
+        --bridge-hub-westend-signer //Charlie \
+        --bridge-hub-westend-transactions-mortality 4 \
+        --lane "${LANE_ID}"
+}
+
+case "$1" in
+  run-relay)
+    init_wnd_ro
+    init_ro_wnd
+    run_relay
+    ;;
+  init-asset-hub-rococo-local)
+      ensure_polkadot_js_api
+      # create foreign assets for native Westend token (governance call on Rococo)
+      force_create_foreign_asset \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9910" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } }')" \
+          "$GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT" \
+          10000000000 \
+          true
+      # HRMP
+      open_hrmp_channels \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1000 1013 4 524288
+      open_hrmp_channels \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1013 1000 4 524288
+      # set XCM version of remote AssetHubWestend
+      force_xcm_version \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9910" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  init-bridge-hub-rococo-local)
+      ensure_polkadot_js_api
+      # SA of sibling asset hub pays for the execution
+      transfer_balance \
+          "ws://127.0.0.1:8943" \
+          "//Alice" \
+          "$ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO" \
+          $((1000000000000 + 50000000000 * 20))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery
+      transfer_balance \
+          "ws://127.0.0.1:8943" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain" \
+          $((1000000000000 + 2000000000000))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation
+      transfer_balance \
+          "ws://127.0.0.1:8943" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain" \
+          $((1000000000000 + 2000000000000))
+      # set XCM version of remote BridgeHubWestend
+      force_xcm_version \
+          "ws://127.0.0.1:9942" \
+          "//Alice" \
+          1013 \
+          "ws://127.0.0.1:8943" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1002 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  init-asset-hub-westend-local)
+      ensure_polkadot_js_api
+      # create foreign assets for native Rococo token (governance call on Westend)
+      force_create_foreign_asset \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9010" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } }')" \
+          "$GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT" \
+          10000000000 \
+          true
+      # HRMP
+      open_hrmp_channels \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1000 1002 4 524288
+      open_hrmp_channels \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1002 1000 4 524288
+      # set XCM version of remote AssetHubRococo
+      force_xcm_version \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1000 \
+          "ws://127.0.0.1:9010" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  init-bridge-hub-westend-local)
+      # SA of sibling asset hub pays for the execution
+      transfer_balance \
+          "ws://127.0.0.1:8945" \
+          "//Alice" \
+          "$ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND" \
+          $((1000000000000000 + 50000000000 * 20))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery
+      transfer_balance \
+          "ws://127.0.0.1:8945" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain" \
+          $((1000000000000000 + 2000000000000))
+      # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation
+      transfer_balance \
+          "ws://127.0.0.1:8945" \
+          "//Alice" \
+          "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain" \
+          $((1000000000000000 + 2000000000000))
+      # set XCM version of remote BridgeHubRococo
+      force_xcm_version \
+          "ws://127.0.0.1:9945" \
+          "//Alice" \
+          1002 \
+          "ws://127.0.0.1:8945" \
+          "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1013 } ] } }')" \
+          $XCM_VERSION
+      ;;
+  reserve-transfer-assets-from-asset-hub-rococo-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send ROCs to Alice account on AHW
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9910" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  withdraw-reserve-assets-from-asset-hub-rococo-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send back only 100000000000 wrappedWNDs to Alice account on AHW
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9910" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  reserve-transfer-assets-from-asset-hub-westend-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send WNDs to Alice account on AHR
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9010" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  withdraw-reserve-assets-from-asset-hub-westend-local)
+      amount=$2
+      ensure_polkadot_js_api
+      # send back only 100000000000 wrappedROCs to Alice account on AHR
+      limited_reserve_transfer_assets \
+          "ws://127.0.0.1:9010" \
+          "//Alice" \
+          "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } } }')" \
+          "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \
+          "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } } }, "fun": { "Fungible": '$amount' } } ] }')" \
+          0 \
+          "Unlimited"
+      ;;
+  claim-rewards-bridge-hub-rococo-local)
+      ensure_polkadot_js_api
+      # bhwd -> [62, 68, 77, 64] -> 0x62687764
+      claim_rewards \
+          "ws://127.0.0.1:8943" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x62687764" \
+          "ThisChain"
+      claim_rewards \
+          "ws://127.0.0.1:8943" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x62687764" \
+          "BridgedChain"
+      ;;
+  claim-rewards-bridge-hub-westend-local)
+      # bhro -> [62, 68, 72, 6f] -> 0x6268726f
+      claim_rewards \
+          "ws://127.0.0.1:8945" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x6268726f" \
+          "ThisChain"
+      claim_rewards \
+          "ws://127.0.0.1:8945" \
+          "//Charlie" \
+          "0x${LANE_ID}" \
+          "0x6268726f" \
+          "BridgedChain"
+      ;;
+  stop)
+    pkill -f polkadot
+    pkill -f parachain
+    ;;
+  import)
+    # to avoid trigger anything here
+    ;;
+  *)
+    echo "A command is require. Supported commands for:
+    Local (zombienet) run:
+          - run-relay
+          - init-asset-hub-rococo-local
+          - init-bridge-hub-rococo-local
+          - init-asset-hub-westend-local
+          - init-bridge-hub-westend-local
+          - reserve-transfer-assets-from-asset-hub-rococo-local
+          - withdraw-reserve-assets-from-asset-hub-rococo-local
+          - reserve-transfer-assets-from-asset-hub-westend-local
+          - withdraw-reserve-assets-from-asset-hub-westend-local
+          - claim-rewards-bridge-hub-rococo-local
+          - claim-rewards-bridge-hub-westend-local";
+    exit 1
+    ;;
+esac
diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0a13ded213f5d3a0920cb466fc974c129e9ad79a
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/helper.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$ENV_PATH/bridges_rococo_westend.sh "$@"
diff --git a/bridges/testing/environments/rococo-westend/rococo-init.zndsl b/bridges/testing/environments/rococo-westend/rococo-init.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..c913e4db31f49184eb8214fda4d525c3594b358b
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/rococo-init.zndsl
@@ -0,0 +1,8 @@
+Description: Check if the HRMP channel between Rococo BH and Rococo AH was opened successfully
+Network: ./bridge_hub_rococo_local_network.toml
+Creds: config
+
+# ensure that initialization has completed
+asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds
+
+
diff --git a/bridges/testing/environments/rococo-westend/rococo.zndsl b/bridges/testing/environments/rococo-westend/rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..5b49c7c632fa4dd0ce77134858a2f697acbfff16
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/rococo.zndsl
@@ -0,0 +1,7 @@
+Description: Check if the with-Westend GRANPDA pallet was initialized at Rococo BH
+Network: ./bridge_hub_rococo_local_network.toml
+Creds: config
+
+# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo
+bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds
+
diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cbd0b1bc623ab77876ed5ce3beefd7ab72db2d37
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/spawn.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+set -e
+
+trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT
+
+source "$FRAMEWORK_PATH/utils/zombienet.sh"
+
+# whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc)
+init=0
+start_relayer=0
+while [ $# -ne 0 ]
+do
+    arg="$1"
+    case "$arg" in
+        --init)
+            init=1
+            ;;
+        --start-relayer)
+            start_relayer=1
+            ;;
+    esac
+    shift
+done
+
+logs_dir=$TEST_DIR/logs
+helper_script="${BASH_SOURCE%/*}/helper.sh"
+
+rococo_def=${BASH_SOURCE%/*}/bridge_hub_rococo_local_network.toml
+start_zombienet $TEST_DIR $rococo_def rococo_dir rococo_pid
+echo
+
+westend_def=${BASH_SOURCE%/*}/bridge_hub_westend_local_network.toml
+start_zombienet $TEST_DIR $westend_def westend_dir westend_pid
+echo
+
+if [[ $init -eq 1 ]]; then
+  rococo_init_log=$logs_dir/rococo-init.log
+  echo -e "Setting up the rococo side of the bridge. Logs available at: $rococo_init_log\n"
+
+  westend_init_log=$logs_dir/westend-init.log
+  echo -e "Setting up the westend side of the bridge. Logs available at: $westend_init_log\n"
+
+  $helper_script init-asset-hub-rococo-local >> $rococo_init_log 2>&1 &
+  rococo_init_pid=$!
+  $helper_script init-asset-hub-westend-local >> $westend_init_log 2>&1 &
+  westend_init_pid=$!
+  wait -n $rococo_init_pid $westend_init_pid
+
+
+  $helper_script init-bridge-hub-rococo-local >> $rococo_init_log 2>&1 &
+  rococo_init_pid=$!
+  $helper_script init-bridge-hub-westend-local >> $westend_init_log 2>&1 &
+  westend_init_pid=$!
+  wait -n $rococo_init_pid $westend_init_pid
+
+  run_zndsl ${BASH_SOURCE%/*}/rococo-init.zndsl $rococo_dir
+  run_zndsl ${BASH_SOURCE%/*}/westend-init.zndsl $westend_dir
+fi
+
+if [[ $start_relayer -eq 1 ]]; then
+  ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid
+fi
+
+echo $rococo_dir > $TEST_DIR/rococo.env
+echo $westend_dir > $TEST_DIR/westend.env
+echo
+
+wait -n $rococo_pid $westend_pid $relayer_pid
+kill -9 -$$
diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7ddd312d395aa8733d2afea59277b48721c8a36b
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/start_relayer.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -e
+
+source "$FRAMEWORK_PATH/utils/common.sh"
+source "$FRAMEWORK_PATH/utils/zombienet.sh"
+
+rococo_dir=$1
+westend_dir=$2
+__relayer_pid=$3
+
+logs_dir=$TEST_DIR/logs
+helper_script="${BASH_SOURCE%/*}/helper.sh"
+
+relayer_log=$logs_dir/relayer.log
+echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n"
+start_background_process "$helper_script run-relay" $relayer_log relayer_pid
+
+run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir
+run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir
+
+eval $__relayer_pid="'$relayer_pid'"
+
diff --git a/bridges/testing/environments/rococo-westend/westend-init.zndsl b/bridges/testing/environments/rococo-westend/westend-init.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..0f5428eed3b01c042f8aad3b3df51c3a800a9b72
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/westend-init.zndsl
@@ -0,0 +1,7 @@
+Description: Check if the HRMP channel between Westend BH and Westend AH was opened successfully
+Network: ./bridge_hub_westend_local_network.toml
+Creds: config
+
+# ensure that initialization has completed
+asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds
+
diff --git a/bridges/testing/environments/rococo-westend/westend.zndsl b/bridges/testing/environments/rococo-westend/westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..07968838852f7c0a00131db3080c460c07d08206
--- /dev/null
+++ b/bridges/testing/environments/rococo-westend/westend.zndsl
@@ -0,0 +1,6 @@
+Description: Check if the with-Rococo GRANPDA pallet was initialized at Westend BH
+Network: ./bridge_hub_westend_local_network.toml
+Creds: config
+
+# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend
+bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds
diff --git a/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js
new file mode 100644
index 0000000000000000000000000000000000000000..af4f18aee9b2710612ed142c50b28caf8313326d
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js
@@ -0,0 +1,25 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
+    const bridgedChainName = args[0];
+    const expectedBridgedChainHeaderNumber = Number(args[1]);
+    const runtimeApiMethod = bridgedChainName + "FinalityApi_best_finalized";
+
+    while (true) {
+        const encodedBestFinalizedHeaderId = await api.rpc.state.call(runtimeApiMethod, []);
+        const bestFinalizedHeaderId = api.createType("Option<BpRuntimeHeaderId>", encodedBestFinalizedHeaderId);
+        if (bestFinalizedHeaderId.isSome) {
+            const bestFinalizedHeaderNumber = Number(bestFinalizedHeaderId.unwrap().toHuman()[0]);
+            if (bestFinalizedHeaderNumber > expectedBridgedChainHeaderNumber) {
+                return bestFinalizedHeaderNumber;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js
new file mode 100644
index 0000000000000000000000000000000000000000..bcce3b3a303f55a16e766c6558878650ed03ab80
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js
@@ -0,0 +1,6 @@
+module.exports = {
+    grandpaPalletName: "bridgeRococoGrandpa",
+    parachainsPalletName: "bridgeRococoParachains",
+    messagesPalletName: "bridgeRococoMessages",
+    bridgedBridgeHubParaId: 1013,
+}
diff --git a/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js
new file mode 100644
index 0000000000000000000000000000000000000000..6a15b64a23b7c28f2b66a6491caebafc4c93dff5
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js
@@ -0,0 +1,6 @@
+module.exports = {
+    grandpaPalletName: "bridgeWestendGrandpa",
+    parachainsPalletName: "bridgeWestendParachains",
+    messagesPalletName: "bridgeWestendMessages",
+    bridgedBridgeHubParaId: 1002,
+}
diff --git a/bridges/testing/framework/js-helpers/native-assets-balance-increased.js b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js
new file mode 100644
index 0000000000000000000000000000000000000000..749c3e2fec32ac0af4d244c53cb4ac1c6237817a
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js
@@ -0,0 +1,21 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    const accountAddress = args[0];
+    const expectedIncrease = BigInt(args[1]);
+    const initialAccountData = await api.query.system.account(accountAddress);
+    const initialAccountBalance = initialAccountData.data['free'];
+    while (true) {
+        const accountData = await api.query.system.account(accountAddress);
+        const accountBalance = accountData.data['free'];
+        if (accountBalance > initialAccountBalance + expectedIncrease) {
+            return accountBalance;
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = {run}
diff --git a/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js
new file mode 100644
index 0000000000000000000000000000000000000000..979179245ebe9f5b250efca6f2e6199ef0ac86d7
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js
@@ -0,0 +1,44 @@
+const utils = require("./utils");
+
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // parse arguments
+    const exitAfterSeconds = Number(args[0]);
+    const bridgedChain = require("./chains/" + args[1]);
+
+    // start listening to new blocks
+    let totalGrandpaHeaders = 0;
+    let initialParachainHeaderImported = false;
+    api.rpc.chain.subscribeNewHeads(async function (header) {
+        const apiAtParent = await api.at(header.parentHash);
+        const apiAtCurrent = await api.at(header.hash);
+        const currentEvents = await apiAtCurrent.query.system.events();
+
+        totalGrandpaHeaders += await utils.ensureOnlyMandatoryGrandpaHeadersImported(
+            bridgedChain,
+            apiAtParent,
+            apiAtCurrent,
+            currentEvents,
+        );
+        initialParachainHeaderImported = await utils.ensureOnlyInitialParachainHeaderImported(
+            bridgedChain,
+            apiAtParent,
+            apiAtCurrent,
+            currentEvents,
+        );
+    });
+
+    // wait given time
+    await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000));
+    // if we haven't seen any new GRANDPA or parachain headers => fail
+    if (totalGrandpaHeaders == 0) {
+        throw new Error("No bridged relay chain headers imported");
+    }
+    if (!initialParachainHeaderImported) {
+        throw new Error("No bridged parachain headers imported");
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c3130e4fd960601d377dde5101520c95531cdf6
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js
@@ -0,0 +1,81 @@
+const utils = require("./utils");
+
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // parse arguments
+    const exitAfterSeconds = Number(args[0]);
+    const bridgedChain = require("./chains/" + args[1]);
+
+    // start listening to new blocks
+    let atLeastOneMessageReceived = false;
+    let atLeastOneMessageDelivered = false;
+    const unsubscribe = await api.rpc.chain.subscribeNewHeads(async function (header) {
+        const apiAtParent = await api.at(header.parentHash);
+        const apiAtCurrent = await api.at(header.hash);
+        const currentEvents = await apiAtCurrent.query.system.events();
+
+        const messagesReceived = currentEvents.find((record) => {
+            return record.event.section == bridgedChain.messagesPalletName
+                && record.event.method == "MessagesReceived";
+        }) != undefined;
+        const messagesDelivered = currentEvents.find((record) => {
+            return record.event.section == bridgedChain.messagesPalletName &&
+                record.event.method == "MessagesDelivered";
+        }) != undefined;
+        const hasMessageUpdates = messagesReceived || messagesDelivered;
+        atLeastOneMessageReceived = atLeastOneMessageReceived || messagesReceived;
+        atLeastOneMessageDelivered = atLeastOneMessageDelivered || messagesDelivered;
+
+        if (!hasMessageUpdates) {
+            // if there are no any message update transactions, we only expect mandatory GRANDPA
+            // headers and initial parachain headers
+            await utils.ensureOnlyMandatoryGrandpaHeadersImported(
+                bridgedChain,
+                apiAtParent,
+                apiAtCurrent,
+                currentEvents,
+            );
+            await utils.ensureOnlyInitialParachainHeaderImported(
+                bridgedChain,
+                apiAtParent,
+                apiAtCurrent,
+                currentEvents,
+            );
+        } else {
+            const messageTransactions = (messagesReceived ? 1 : 0) + (messagesDelivered ? 1 : 0);
+
+            // otherwise we only accept at most one GRANDPA header
+            const newGrandpaHeaders = utils.countGrandpaHeaderImports(bridgedChain, currentEvents);
+            if (newGrandpaHeaders > 1) {
+                utils.logEvents(currentEvents);
+                throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + messageTransactions);
+            }
+
+            // ...and at most one parachain header
+            const newParachainHeaders = utils.countParachainHeaderImports(bridgedChain, currentEvents);
+            if (newParachainHeaders > 1) {
+                utils.logEvents(currentEvents);
+                throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + messageTransactions);
+            }
+        }
+    });
+
+    // wait until we have received + delivered messages OR until timeout
+    await utils.pollUntil(
+        exitAfterSeconds,
+        () => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; },
+        () => { unsubscribe(); },
+        () => {
+            if (!atLeastOneMessageReceived) {
+                throw new Error("No messages received from bridged chain");
+            }
+            if (!atLeastOneMessageDelivered) {
+                throw new Error("No messages delivered to bridged chain");
+            }
+        },
+    );
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/relayer-rewards.js b/bridges/testing/framework/js-helpers/relayer-rewards.js
new file mode 100644
index 0000000000000000000000000000000000000000..5347c649604fc209042725c9cf269c9d3ca0290f
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/relayer-rewards.js
@@ -0,0 +1,28 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
+    const relayerAccountAddress = args[0];
+    const laneId = args[1];
+    const bridgedChainId = args[2];
+    const relayerFundOwner = args[3];
+    const expectedRelayerReward = BigInt(args[4]);
+    while (true) {
+        const relayerReward = await api.query.bridgeRelayers.relayerRewards(
+            relayerAccountAddress,
+            { laneId: laneId, bridgedChainId: bridgedChainId, owner: relayerFundOwner }
+        );
+        if (relayerReward.isSome) {
+            const relayerRewardBalance = relayerReward.unwrap().toBigInt();
+            if (relayerRewardBalance > expectedRelayerReward) {
+                return relayerRewardBalance;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/utils.js b/bridges/testing/framework/js-helpers/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..f6e9f5623b47b3cb3c642245e86654ae9f65358a
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/utils.js
@@ -0,0 +1,103 @@
+module.exports = {
+    logEvents: function(events) {
+        let stringifiedEvents = "";
+        events.forEach((record) => {
+            if (stringifiedEvents != "") {
+                stringifiedEvents += ", ";
+            }
+            stringifiedEvents += record.event.section + "::" + record.event.method;
+        });
+        console.log("Block events: " + stringifiedEvents);
+    },
+    countGrandpaHeaderImports: function(bridgedChain, events) {
+        return events.reduce(
+            (count, record) => {
+                const { event } = record;
+                if (event.section == bridgedChain.grandpaPalletName && event.method == "UpdatedBestFinalizedHeader") {
+                    count += 1;
+                }
+                return count;
+            },
+            0,
+        );
+    },
+    countParachainHeaderImports: function(bridgedChain, events) {
+        return events.reduce(
+            (count, record) => {
+                const { event } = record;
+                if (event.section == bridgedChain.parachainsPalletName && event.method == "UpdatedParachainHead") {
+                    count += 1;
+                }
+                return count;
+            },
+            0,
+        );
+    },
+    pollUntil: async function(
+        timeoutInSecs,
+        predicate,
+        cleanup,
+        onFailure,
+    )  {
+        const begin = new Date().getTime();
+        const end = begin + timeoutInSecs * 1000;
+        while (new Date().getTime() < end) {
+            if (predicate()) {
+                cleanup();
+                return;
+            }
+            await new Promise(resolve => setTimeout(resolve, 100));
+        }
+
+        cleanup();
+        onFailure();
+    },
+    ensureOnlyMandatoryGrandpaHeadersImported: async function(
+        bridgedChain,
+        apiAtParent,
+        apiAtCurrent,
+        currentEvents,
+    ) {
+        // remember id of bridged relay chain GRANDPA authorities set at parent block
+        const authoritySetAtParent = await apiAtParent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
+        const authoritySetIdAtParent = authoritySetAtParent["setId"];
+
+        // now read the id of bridged relay chain GRANDPA authorities set at current block
+        const authoritySetAtCurrent = await apiAtCurrent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
+        const authoritySetIdAtCurrent = authoritySetAtCurrent["setId"];
+
+        // we expect to see no more than `authoritySetIdAtCurrent - authoritySetIdAtParent` new GRANDPA headers
+        const maxNewGrandpaHeaders = authoritySetIdAtCurrent - authoritySetIdAtParent;
+        const newGrandpaHeaders = module.exports.countGrandpaHeaderImports(bridgedChain, currentEvents);
+
+        // check that our assumptions are correct
+        if (newGrandpaHeaders > maxNewGrandpaHeaders) {
+            module.exports.logEvents(currentEvents);
+            throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + maxNewGrandpaHeaders);
+        }
+
+        return newGrandpaHeaders;
+    },
+    ensureOnlyInitialParachainHeaderImported: async function(
+        bridgedChain,
+        apiAtParent,
+        apiAtCurrent,
+        currentEvents,
+    ) {
+        // remember whether we already know bridged parachain header at a parent block
+        const bestBridgedParachainHeader = await apiAtParent.query[bridgedChain.parachainsPalletName].parasInfo(bridgedChain.bridgedBridgeHubParaId);;
+        const hasBestBridgedParachainHeader = bestBridgedParachainHeader.isSome;
+
+        // we expect to see: no more than `1` bridged parachain header if there were no parachain header before.
+        const maxNewParachainHeaders = hasBestBridgedParachainHeader ? 0 : 1;
+        const newParachainHeaders = module.exports.countParachainHeaderImports(bridgedChain, currentEvents);
+
+        // check that our assumptions are correct
+        if (newParachainHeaders > maxNewParachainHeaders) {
+            module.exports.logEvents(currentEvents);
+            throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders);
+        }
+
+        return hasBestBridgedParachainHeader;
+    },
+}
diff --git a/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js
new file mode 100644
index 0000000000000000000000000000000000000000..765d48cc49848ab7a4389f6e0d9b9b3b8cb38f2b
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js
@@ -0,0 +1,22 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    const sibling = args[0];
+
+    while (true) {
+        const messagingStateAsObj = await api.query.parachainSystem.relevantMessagingState();
+        const messagingState = api.createType("Option<CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot>", messagingStateAsObj);
+        if (messagingState.isSome) {
+            const egressChannels = messagingState.unwrap().egressChannels;
+            if (egressChannels.find(x => x[0] == sibling)) {
+                return;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/js-helpers/wrapped-assets-balance.js b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js
new file mode 100644
index 0000000000000000000000000000000000000000..27287118547f702b3e94eb635f9e3855d1cab535
--- /dev/null
+++ b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js
@@ -0,0 +1,26 @@
+async function run(nodeName, networkInfo, args) {
+    const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
+    const api = await zombie.connect(wsUri, userDefinedTypes);
+
+    // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
+    const accountAddress = args[0];
+    const expectedForeignAssetBalance = BigInt(args[1]);
+    const bridgedNetworkName = args[2];
+    while (true) {
+        const foreignAssetAccount = await api.query.foreignAssets.account(
+            { parents: 2, interior: { X1: { GlobalConsensus: bridgedNetworkName } } },
+            accountAddress
+        );
+        if (foreignAssetAccount.isSome) {
+            const foreignAssetAccountBalance = foreignAssetAccount.unwrap().balance.toBigInt();
+            if (foreignAssetAccountBalance > expectedForeignAssetBalance) {
+                return foreignAssetAccountBalance;
+            }
+        }
+
+        // else sleep and retry
+        await new Promise((resolve) => setTimeout(resolve, 6000));
+    }
+}
+
+module.exports = { run }
diff --git a/bridges/testing/framework/utils/bridges.sh b/bridges/testing/framework/utils/bridges.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7c8399461584a85e4e8eedf5f347d9d74725f1c9
--- /dev/null
+++ b/bridges/testing/framework/utils/bridges.sh
@@ -0,0 +1,309 @@
+#!/bin/bash
+
+function relayer_path() {
+    local default_path=~/local_bridge_testing/bin/substrate-relay
+    local path="${SUBSTRATE_RELAY_BINARY:-$default_path}"
+    echo "$path"
+}
+
+function ensure_relayer() {
+    local path=$(relayer_path)
+    if [[ ! -f "$path" ]]; then
+        echo "  Required substrate-relay binary '$path' does not exist!"
+        echo "  You need to build it and copy to this location!"
+        echo "  Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)"
+        exit 1
+    fi
+
+    echo $path
+}
+
+function ensure_polkadot_js_api() {
+    if ! which polkadot-js-api &> /dev/null; then
+        echo ''
+        echo 'Required command `polkadot-js-api` not in PATH, please, install, e.g.:'
+        echo "npm install -g @polkadot/api-cli@beta"
+        echo "      or"
+        echo "yarn global add @polkadot/api-cli"
+        echo ''
+        exit 1
+    fi
+    if ! which jq &> /dev/null; then
+        echo ''
+        echo 'Required command `jq` not in PATH, please, install, e.g.:'
+        echo "apt install -y jq"
+        echo ''
+        exit 1
+    fi
+    generate_hex_encoded_call_data "check" "--"
+    local retVal=$?
+    if [ $retVal -ne 0 ]; then
+        echo ""
+        echo ""
+        echo "-------------------"
+        echo "Installing (nodejs) sub module: ${BASH_SOURCE%/*}/generate_hex_encoded_call"
+        pushd ${BASH_SOURCE%/*}/generate_hex_encoded_call
+        npm install
+        popd
+    fi
+}
+
+function call_polkadot_js_api() {
+    # --noWait: without that argument `polkadot-js-api` waits until transaction is included into the block.
+    #           With it, it just submits it to the tx pool and exits.
+    # --nonce -1: means to compute transaction nonce using `system_accountNextIndex` RPC, which includes all
+    #             transaction that are in the tx pool.
+    polkadot-js-api --noWait --nonce -1 "$@"
+}
+
+function generate_hex_encoded_call_data() {
+    local type=$1
+    local endpoint=$2
+    local output=$3
+    shift
+    shift
+    shift
+    echo "Input params: $@"
+
+    node ${BASH_SOURCE%/*}/../utils/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@"
+    local retVal=$?
+
+    if [ $type != "check" ]; then
+        local hex_encoded_data=$(cat $output)
+        echo "Generated hex-encoded bytes to file '$output': $hex_encoded_data"
+    fi
+
+    return $retVal
+}
+
+function transfer_balance() {
+    local runtime_para_endpoint=$1
+    local seed=$2
+    local target_account=$3
+    local amount=$4
+    echo "  calling transfer_balance:"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      seed: ${seed}"
+    echo "      target_account: ${target_account}"
+    echo "      amount: ${amount}"
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${runtime_para_endpoint}" \
+        --seed "${seed?}" \
+        tx.balances.transferAllowDeath \
+            "${target_account}" \
+            "${amount}"
+}
+
+function send_governance_transact() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local para_id=$3
+    local hex_encoded_data=$4
+    local require_weight_at_most_ref_time=$5
+    local require_weight_at_most_proof_size=$6
+    echo "  calling send_governance_transact:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      para_id: ${para_id}"
+    echo "      hex_encoded_data: ${hex_encoded_data}"
+    echo "      require_weight_at_most_ref_time: ${require_weight_at_most_ref_time}"
+    echo "      require_weight_at_most_proof_size: ${require_weight_at_most_proof_size}"
+    echo "      params:"
+
+    local dest=$(jq --null-input \
+                    --arg para_id "$para_id" \
+                    '{ "V3": { "parents": 0, "interior": { "X1": { "Parachain": $para_id } } } }')
+
+    local message=$(jq --null-input \
+                       --argjson hex_encoded_data $hex_encoded_data \
+                       --arg require_weight_at_most_ref_time "$require_weight_at_most_ref_time" \
+                       --arg require_weight_at_most_proof_size "$require_weight_at_most_proof_size" \
+                       '
+                       {
+                          "V3": [
+                                  {
+                                    "UnpaidExecution": {
+                                        "weight_limit": "Unlimited"
+                                    }
+                                  },
+                                  {
+                                    "Transact": {
+                                      "origin_kind": "Superuser",
+                                      "require_weight_at_most": {
+                                        "ref_time": $require_weight_at_most_ref_time,
+                                        "proof_size": $require_weight_at_most_proof_size,
+                                      },
+                                      "call": {
+                                        "encoded": $hex_encoded_data
+                                      }
+                                    }
+                                  }
+                          ]
+                        }
+                        ')
+
+    echo ""
+    echo "          dest:"
+    echo "${dest}"
+    echo ""
+    echo "          message:"
+    echo "${message}"
+    echo ""
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${relay_url?}" \
+        --seed "${relay_chain_seed?}" \
+        --sudo \
+        tx.xcmPallet.send \
+            "${dest}" \
+            "${message}"
+}
+
+function open_hrmp_channels() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local sender_para_id=$3
+    local recipient_para_id=$4
+    local max_capacity=$5
+    local max_message_size=$6
+    echo "  calling open_hrmp_channels:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      sender_para_id: ${sender_para_id}"
+    echo "      recipient_para_id: ${recipient_para_id}"
+    echo "      max_capacity: ${max_capacity}"
+    echo "      max_message_size: ${max_message_size}"
+    echo "      params:"
+    echo "--------------------------------------------------"
+    call_polkadot_js_api \
+        --ws "${relay_url?}" \
+        --seed "${relay_chain_seed?}" \
+        --sudo \
+        tx.hrmp.forceOpenHrmpChannel \
+            ${sender_para_id} \
+            ${recipient_para_id} \
+            ${max_capacity} \
+            ${max_message_size}
+}
+
+function force_xcm_version() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local runtime_para_id=$3
+    local runtime_para_endpoint=$4
+    local dest=$5
+    local xcm_version=$6
+    echo "  calling force_xcm_version:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      runtime_para_id: ${runtime_para_id}"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      dest: ${dest}"
+    echo "      xcm_version: ${xcm_version}"
+    echo "      params:"
+
+    # 1. generate data for Transact (PolkadotXcm::force_xcm_version)
+    local tmp_output_file=$(mktemp)
+    generate_hex_encoded_call_data "force-xcm-version" "${runtime_para_endpoint}" "${tmp_output_file}" "$dest" "$xcm_version"
+    local hex_encoded_data=$(cat $tmp_output_file)
+
+    # 2. trigger governance call
+    send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000
+}
+
+function force_create_foreign_asset() {
+    local relay_url=$1
+    local relay_chain_seed=$2
+    local runtime_para_id=$3
+    local runtime_para_endpoint=$4
+    local asset_multilocation=$5
+    local asset_owner_account_id=$6
+    local min_balance=$7
+    local is_sufficient=$8
+    echo "  calling force_create_foreign_asset:"
+    echo "      relay_url: ${relay_url}"
+    echo "      relay_chain_seed: ${relay_chain_seed}"
+    echo "      runtime_para_id: ${runtime_para_id}"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      asset_multilocation: ${asset_multilocation}"
+    echo "      asset_owner_account_id: ${asset_owner_account_id}"
+    echo "      min_balance: ${min_balance}"
+    echo "      is_sufficient: ${is_sufficient}"
+    echo "      params:"
+
+    # 1. generate data for Transact (ForeignAssets::force_create)
+    local tmp_output_file=$(mktemp)
+    generate_hex_encoded_call_data "force-create-asset" "${runtime_para_endpoint}" "${tmp_output_file}" "$asset_multilocation" "$asset_owner_account_id" $is_sufficient $min_balance
+    local hex_encoded_data=$(cat $tmp_output_file)
+
+    # 2. trigger governance call
+    send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000
+}
+
+function limited_reserve_transfer_assets() {
+    local url=$1
+    local seed=$2
+    local destination=$3
+    local beneficiary=$4
+    local assets=$5
+    local fee_asset_item=$6
+    local weight_limit=$7
+    echo "  calling limited_reserve_transfer_assets:"
+    echo "      url: ${url}"
+    echo "      seed: ${seed}"
+    echo "      destination: ${destination}"
+    echo "      beneficiary: ${beneficiary}"
+    echo "      assets: ${assets}"
+    echo "      fee_asset_item: ${fee_asset_item}"
+    echo "      weight_limit: ${weight_limit}"
+    echo ""
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${url?}" \
+        --seed "${seed?}" \
+        tx.polkadotXcm.limitedReserveTransferAssets \
+            "${destination}" \
+            "${beneficiary}" \
+            "${assets}" \
+            "${fee_asset_item}" \
+            "${weight_limit}"
+}
+
+function claim_rewards() {
+    local runtime_para_endpoint=$1
+    local seed=$2
+    local lane_id=$3
+    local bridged_chain_id=$4
+    local owner=$5
+    echo "  calling claim_rewards:"
+    echo "      runtime_para_endpoint: ${runtime_para_endpoint}"
+    echo "      seed: ${seed}"
+    echo "      lane_id: ${lane_id}"
+    echo "      bridged_chain_id: ${bridged_chain_id}"
+    echo "      owner: ${owner}"
+    echo ""
+
+    local rewards_account_params=$(jq --null-input \
+                                      --arg lane_id "$lane_id" \
+                                      --arg bridged_chain_id "$bridged_chain_id" \
+                                      --arg owner "$owner" \
+                    '{
+                        "laneId": $lane_id,
+                        "bridgedChainId": $bridged_chain_id,
+                        "owner": $owner
+                     }')
+
+    echo "          rewards_account_params:"
+    echo "${rewards_account_params}"
+    echo "--------------------------------------------------"
+
+    call_polkadot_js_api \
+        --ws "${runtime_para_endpoint}" \
+        --seed "${seed?}" \
+        tx.bridgeRelayers.claimRewards \
+            "${rewards_account_params}"
+}
\ No newline at end of file
diff --git a/bridges/testing/framework/utils/common.sh b/bridges/testing/framework/utils/common.sh
new file mode 100644
index 0000000000000000000000000000000000000000..06f41320be1353720fccc76b7b76e69ba56a3b94
--- /dev/null
+++ b/bridges/testing/framework/utils/common.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+function start_background_process() {
+    local command=$1
+    local log_file=$2
+    local __pid=$3
+
+    $command > $log_file 2>&1 &
+    eval $__pid="'$!'"
+}
+
+function wait_for_process_file() {
+    local pid=$1
+    local file=$2
+    local timeout=$3
+    local __found=$4
+
+    local time=0
+    until [ -e $file ]; do
+      if ! kill -0 $pid; then
+        echo "Process finished unsuccessfully"
+        return
+      fi
+      if (( time++ >= timeout )); then
+        echo "Timeout waiting for file $file: $timeout seconds"
+        eval $__found=0
+        return
+      fi
+      sleep 1
+    done
+
+    echo "File $file found after $time seconds"
+    eval $__found=1
+}
+
+function ensure_process_file() {
+    local pid=$1
+    local file=$2
+    local timeout=$3
+
+    wait_for_process_file $pid $file $timeout file_found
+    if [ "$file_found" != "1" ]; then
+      exit 1
+    fi
+}
diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/index.js b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..30f89d754ceb7de1b24bc31413e09c862a461256
--- /dev/null
+++ b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js
@@ -0,0 +1,165 @@
+const fs = require("fs");
+const { exit } = require("process");
+const { WsProvider, ApiPromise } = require("@polkadot/api");
+const util = require("@polkadot/util");
+
+// connect to a substrate chain and return the api object
+async function connect(endpoint, types = {}) {
+	const provider = new WsProvider(endpoint);
+	const api = await ApiPromise.create({
+		provider,
+		types,
+		throwOnConnect: false,
+	});
+	return api;
+}
+
+function writeHexEncodedBytesToOutput(method, outputFile) {
+	console.log("Payload (hex): ", method.toHex());
+	console.log("Payload (bytes): ", Array.from(method.toU8a()));
+	console.log("Payload (plain): ", JSON.stringify(method));
+	fs.writeFileSync(outputFile, JSON.stringify(Array.from(method.toU8a())));
+}
+
+function remarkWithEvent(endpoint, outputFile) {
+	console.log(`Generating remarkWithEvent from RPC endpoint: ${endpoint} to outputFile: ${outputFile}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.system.remarkWithEvent("Hello");
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function addExporterConfig(endpoint, outputFile, bridgedNetwork, bridgeConfig) {
+	console.log(`Generating addExporterConfig from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on bridgedNetwork: ${bridgedNetwork}, bridgeConfig: ${bridgeConfig}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.addExporterConfig(bridgedNetwork, JSON.parse(bridgeConfig));
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function addUniversalAlias(endpoint, outputFile, location, junction) {
+	console.log(`Generating addUniversalAlias from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on location: ${location}, junction: ${junction}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.addUniversalAlias(JSON.parse(location), JSON.parse(junction));
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function addReserveLocation(endpoint, outputFile, reserve_location) {
+	console.log(`Generating addReserveLocation from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on reserve_location: ${reserve_location}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.addReserveLocation(JSON.parse(reserve_location));
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function removeExporterConfig(endpoint, outputFile, bridgedNetwork) {
+	console.log(`Generating removeExporterConfig from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on bridgedNetwork: ${bridgedNetwork}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.bridgeTransfer.removeExporterConfig(bridgedNetwork);
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function forceCreateAsset(endpoint, outputFile, assetId, assetOwnerAccountId, isSufficient, minBalance) {
+	var isSufficient = isSufficient == "true" ? true : false;
+	console.log(`Generating forceCreateAsset from RPC endpoint: ${endpoint} to outputFile: ${outputFile} based on assetId: ${assetId}, assetOwnerAccountId: ${assetOwnerAccountId}, isSufficient: ${isSufficient}, minBalance: ${minBalance}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.foreignAssets.forceCreate(JSON.parse(assetId), assetOwnerAccountId, isSufficient, minBalance);
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+function forceXcmVersion(endpoint, outputFile, dest, xcm_version) {
+	console.log(`Generating forceXcmVersion from RPC endpoint: ${endpoint} to outputFile: ${outputFile}, dest: ${dest}, xcm_version: ${xcm_version}`);
+	connect(endpoint)
+		.then((api) => {
+			const call = api.tx.polkadotXcm.forceXcmVersion(JSON.parse(dest), xcm_version);
+			writeHexEncodedBytesToOutput(call.method, outputFile);
+			exit(0);
+		})
+		.catch((e) => {
+			console.error(e);
+			exit(1);
+		});
+}
+
+if (!process.argv[2] || !process.argv[3]) {
+	console.log("usage: node ./script/generate_hex_encoded_call <type> <endpoint> <output hex-encoded data file> <input message>");
+	exit(1);
+}
+
+const type = process.argv[2];
+const rpcEnpoint = process.argv[3];
+const output = process.argv[4];
+const inputArgs = process.argv.slice(5, process.argv.length);
+console.log(`Generating hex-encoded call data for:`);
+console.log(`	type: ${type}`);
+console.log(`	rpcEnpoint: ${rpcEnpoint}`);
+console.log(`	output: ${output}`);
+console.log(`	inputArgs: ${inputArgs}`);
+
+switch (type) {
+	case 'remark-with-event':
+		remarkWithEvent(rpcEnpoint, output);
+		break;
+	case 'add-exporter-config':
+		addExporterConfig(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'remove-exporter-config':
+		removeExporterConfig(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'add-universal-alias':
+		addUniversalAlias(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'add-reserve-location':
+		addReserveLocation(rpcEnpoint, output, inputArgs[0]);
+		break;
+	case 'force-create-asset':
+		forceCreateAsset(rpcEnpoint, output, inputArgs[0], inputArgs[1], inputArgs[2], inputArgs[3]);
+		break;
+	case 'force-xcm-version':
+		forceXcmVersion(rpcEnpoint, output, inputArgs[0], inputArgs[1]);
+		break;
+	case 'check':
+		console.log(`Checking nodejs installation, if you see this everything is ready!`);
+		break;
+	default:
+		console.log(`Sorry, we are out of ${type} - not yet supported!`);
+}
diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..b2dddaa19ed1561b98422b3a28f6777308b3ba47
--- /dev/null
+++ b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json
@@ -0,0 +1,759 @@
+{
+  "name": "y",
+  "version": "y",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "y",
+      "version": "y",
+      "license": "MIT",
+      "dependencies": {
+        "@polkadot/api": "^10.11",
+        "@polkadot/util": "^12.6"
+      }
+    },
+    "node_modules/@noble/curves": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz",
+      "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==",
+      "dependencies": {
+        "@noble/hashes": "1.3.3"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@noble/hashes": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
+      "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@polkadot/api": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.11.2.tgz",
+      "integrity": "sha512-AorCZxCWCoTtdbl4DPUZh+ACe/pbLIS1BkdQY0AFJuZllm0x/yWzjgampcPd5jQAA/O3iKShRBkZqj6Mk9yG/A==",
+      "dependencies": {
+        "@polkadot/api-augment": "10.11.2",
+        "@polkadot/api-base": "10.11.2",
+        "@polkadot/api-derive": "10.11.2",
+        "@polkadot/keyring": "^12.6.2",
+        "@polkadot/rpc-augment": "10.11.2",
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/rpc-provider": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-augment": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/types-create": "10.11.2",
+        "@polkadot/types-known": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "eventemitter3": "^5.0.1",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/api-augment": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.11.2.tgz",
+      "integrity": "sha512-PTpnqpezc75qBqUtgrc0GYB8h9UHjfbHSRZamAbecIVAJ2/zc6CqtnldeaBlIu1IKTgBzi3FFtTyYu+ZGbNT2Q==",
+      "dependencies": {
+        "@polkadot/api-base": "10.11.2",
+        "@polkadot/rpc-augment": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-augment": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/api-base": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.11.2.tgz",
+      "integrity": "sha512-4LIjaUfO9nOzilxo7XqzYKCNMtmUypdk8oHPdrRnSjKEsnK7vDsNi+979z2KXNXd2KFSCFHENmI523fYnMnReg==",
+      "dependencies": {
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/api-derive": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.11.2.tgz",
+      "integrity": "sha512-m3BQbPionkd1iSlknddxnL2hDtolPIsT+aRyrtn4zgMRPoLjHFmTmovvg8RaUyYofJtZeYrnjMw0mdxiSXx7eA==",
+      "dependencies": {
+        "@polkadot/api": "10.11.2",
+        "@polkadot/api-augment": "10.11.2",
+        "@polkadot/api-base": "10.11.2",
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/keyring": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.6.2.tgz",
+      "integrity": "sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw==",
+      "dependencies": {
+        "@polkadot/util": "12.6.2",
+        "@polkadot/util-crypto": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "12.6.2",
+        "@polkadot/util-crypto": "12.6.2"
+      }
+    },
+    "node_modules/@polkadot/networks": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.6.2.tgz",
+      "integrity": "sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w==",
+      "dependencies": {
+        "@polkadot/util": "12.6.2",
+        "@substrate/ss58-registry": "^1.44.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/rpc-augment": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.11.2.tgz",
+      "integrity": "sha512-9AhT0WW81/8jYbRcAC6PRmuxXqNhJje8OYiulBQHbG1DTCcjAfz+6VQBke9BwTStzPq7d526+yyBKD17O3zlAA==",
+      "dependencies": {
+        "@polkadot/rpc-core": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/rpc-core": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.11.2.tgz",
+      "integrity": "sha512-Ot0CFLWx8sZhLZog20WDuniPA01Bk2StNDsdAQgcFKPwZw6ShPaZQCHuKLQK6I6DodOrem9FXX7c1hvoKJP5Ww==",
+      "dependencies": {
+        "@polkadot/rpc-augment": "10.11.2",
+        "@polkadot/rpc-provider": "10.11.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/rpc-provider": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.11.2.tgz",
+      "integrity": "sha512-he5jWMpDJp7e+vUzTZDzpkB7ps3H8psRally+/ZvZZScPvFEjfczT7I1WWY9h58s8+ImeVP/lkXjL9h/gUOt3Q==",
+      "dependencies": {
+        "@polkadot/keyring": "^12.6.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-support": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "@polkadot/x-fetch": "^12.6.2",
+        "@polkadot/x-global": "^12.6.2",
+        "@polkadot/x-ws": "^12.6.2",
+        "eventemitter3": "^5.0.1",
+        "mock-socket": "^9.3.1",
+        "nock": "^13.4.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@substrate/connect": "0.7.35"
+      }
+    },
+    "node_modules/@polkadot/types": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.11.2.tgz",
+      "integrity": "sha512-d52j3xXni+C8GdYZVTSfu8ROAnzXFMlyRvXtor0PudUc8UQHOaC4+mYAkTBGA2gKdmL8MHSfRSbhcxHhsikY6Q==",
+      "dependencies": {
+        "@polkadot/keyring": "^12.6.2",
+        "@polkadot/types-augment": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/types-create": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/util-crypto": "^12.6.2",
+        "rxjs": "^7.8.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-augment": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.11.2.tgz",
+      "integrity": "sha512-8eB8ew04wZiE5GnmFvEFW1euJWmF62SGxb1O+8wL3zoUtB9Xgo1vB6w6xbTrd+HLV6jNSeXXnbbF1BEUvi9cNg==",
+      "dependencies": {
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-codec": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.11.2.tgz",
+      "integrity": "sha512-3xjOQL+LOOMzYqlgP9ROL0FQnzU8lGflgYewzau7AsDlFziSEtb49a9BpYo6zil4koC+QB8zQ9OHGFumG08T8w==",
+      "dependencies": {
+        "@polkadot/util": "^12.6.2",
+        "@polkadot/x-bigint": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-create": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.11.2.tgz",
+      "integrity": "sha512-SJt23NxYvefRxVZZm6mT9ed1pR6FDoIGQ3xUpbjhTLfU2wuhpKjekMVorYQ6z/gK2JLMu2kV92Ardsz+6GX5XQ==",
+      "dependencies": {
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-known": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.11.2.tgz",
+      "integrity": "sha512-kbEIX7NUQFxpDB0FFGNyXX/odY7jbp56RGD+Z4A731fW2xh/DgAQrI994xTzuh0c0EqPE26oQm3kATSpseqo9w==",
+      "dependencies": {
+        "@polkadot/networks": "^12.6.2",
+        "@polkadot/types": "10.11.2",
+        "@polkadot/types-codec": "10.11.2",
+        "@polkadot/types-create": "10.11.2",
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/types-support": {
+      "version": "10.11.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.11.2.tgz",
+      "integrity": "sha512-X11hoykFYv/3efg4coZy2hUOUc97JhjQMJLzDhHniFwGLlYU8MeLnPdCVGkXx0xDDjTo4/ptS1XpZ5HYcg+gRw==",
+      "dependencies": {
+        "@polkadot/util": "^12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/util": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.6.2.tgz",
+      "integrity": "sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw==",
+      "dependencies": {
+        "@polkadot/x-bigint": "12.6.2",
+        "@polkadot/x-global": "12.6.2",
+        "@polkadot/x-textdecoder": "12.6.2",
+        "@polkadot/x-textencoder": "12.6.2",
+        "@types/bn.js": "^5.1.5",
+        "bn.js": "^5.2.1",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/util-crypto": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz",
+      "integrity": "sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg==",
+      "dependencies": {
+        "@noble/curves": "^1.3.0",
+        "@noble/hashes": "^1.3.3",
+        "@polkadot/networks": "12.6.2",
+        "@polkadot/util": "12.6.2",
+        "@polkadot/wasm-crypto": "^7.3.2",
+        "@polkadot/wasm-util": "^7.3.2",
+        "@polkadot/x-bigint": "12.6.2",
+        "@polkadot/x-randomvalues": "12.6.2",
+        "@scure/base": "^1.1.5",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "12.6.2"
+      }
+    },
+    "node_modules/@polkadot/wasm-bridge": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz",
+      "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==",
+      "dependencies": {
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*",
+        "@polkadot/x-randomvalues": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz",
+      "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==",
+      "dependencies": {
+        "@polkadot/wasm-bridge": "7.3.2",
+        "@polkadot/wasm-crypto-asmjs": "7.3.2",
+        "@polkadot/wasm-crypto-init": "7.3.2",
+        "@polkadot/wasm-crypto-wasm": "7.3.2",
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*",
+        "@polkadot/x-randomvalues": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto-asmjs": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz",
+      "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto-init": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz",
+      "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==",
+      "dependencies": {
+        "@polkadot/wasm-bridge": "7.3.2",
+        "@polkadot/wasm-crypto-asmjs": "7.3.2",
+        "@polkadot/wasm-crypto-wasm": "7.3.2",
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*",
+        "@polkadot/x-randomvalues": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-crypto-wasm": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz",
+      "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==",
+      "dependencies": {
+        "@polkadot/wasm-util": "7.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*"
+      }
+    },
+    "node_modules/@polkadot/wasm-util": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz",
+      "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "*"
+      }
+    },
+    "node_modules/@polkadot/x-bigint": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz",
+      "integrity": "sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-fetch": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz",
+      "integrity": "sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "node-fetch": "^3.3.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-global": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.6.2.tgz",
+      "integrity": "sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g==",
+      "dependencies": {
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-randomvalues": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz",
+      "integrity": "sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@polkadot/util": "12.6.2",
+        "@polkadot/wasm-util": "*"
+      }
+    },
+    "node_modules/@polkadot/x-textdecoder": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz",
+      "integrity": "sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-textencoder": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz",
+      "integrity": "sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@polkadot/x-ws": {
+      "version": "12.6.2",
+      "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.6.2.tgz",
+      "integrity": "sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw==",
+      "dependencies": {
+        "@polkadot/x-global": "12.6.2",
+        "tslib": "^2.6.2",
+        "ws": "^8.15.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@scure/base": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz",
+      "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==",
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@substrate/connect": {
+      "version": "0.7.35",
+      "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.35.tgz",
+      "integrity": "sha512-Io8vkalbwaye+7yXfG1Nj52tOOoJln2bMlc7Q9Yy3vEWqZEVkgKmcPVzbwV0CWL3QD+KMPDA2Dnw/X7EdwgoLw==",
+      "hasInstallScript": true,
+      "optional": true,
+      "dependencies": {
+        "@substrate/connect-extension-protocol": "^1.0.1",
+        "smoldot": "2.0.7"
+      }
+    },
+    "node_modules/@substrate/connect-extension-protocol": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz",
+      "integrity": "sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==",
+      "optional": true
+    },
+    "node_modules/@substrate/ss58-registry": {
+      "version": "1.44.0",
+      "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.44.0.tgz",
+      "integrity": "sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A=="
+    },
+    "node_modules/@types/bn.js": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz",
+      "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "20.10.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz",
+      "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/bn.js": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
+      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
+    },
+    "node_modules/data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eventemitter3": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
+    },
+    "node_modules/fetch-blob": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "dependencies": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      },
+      "engines": {
+        "node": "^12.20 || >= 14.13"
+      }
+    },
+    "node_modules/formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "dependencies": {
+        "fetch-blob": "^3.1.2"
+      },
+      "engines": {
+        "node": ">=12.20.0"
+      }
+    },
+    "node_modules/json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+    },
+    "node_modules/mock-socket": {
+      "version": "9.3.1",
+      "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
+      "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "node_modules/nock": {
+      "version": "13.4.0",
+      "resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz",
+      "integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==",
+      "dependencies": {
+        "debug": "^4.1.0",
+        "json-stringify-safe": "^5.0.1",
+        "propagate": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      }
+    },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+      "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+      "dependencies": {
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
+      }
+    },
+    "node_modules/propagate": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
+      "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "7.8.1",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/smoldot": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.7.tgz",
+      "integrity": "sha512-VAOBqEen6vises36/zgrmAT1GWk2qE3X8AGnO7lmQFdskbKx8EovnwS22rtPAG+Y1Rk23/S22kDJUdPANyPkBA==",
+      "optional": true,
+      "dependencies": {
+        "ws": "^8.8.1"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+    },
+    "node_modules/web-streams-polyfill": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.16.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    }
+  }
+}
diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/package.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..ecf0a2483db100e688e06da4233f036c2383b3a6
--- /dev/null
+++ b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "y",
+  "version": "y",
+  "description": "create a scale hex-encoded call values from given message",
+  "main": "index.js",
+  "license": "MIT",
+  "dependencies": {
+    "@polkadot/api": "^10.11",
+    "@polkadot/util": "^12.6"
+  }
+}
diff --git a/bridges/testing/framework/utils/zombienet.sh b/bridges/testing/framework/utils/zombienet.sh
new file mode 100644
index 0000000000000000000000000000000000000000..bbcd1a30620252d8740473c3924e0988e5bff4d6
--- /dev/null
+++ b/bridges/testing/framework/utils/zombienet.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+source "${BASH_SOURCE%/*}/common.sh"
+
+function start_zombienet() {
+    local test_dir=$1
+    local definition_path=$2
+    local __zombienet_dir=$3
+    local __zombienet_pid=$4
+
+    local zombienet_name=`basename $definition_path .toml`
+    local zombienet_dir=$test_dir/$zombienet_name
+    eval $__zombienet_dir="'$zombienet_dir'"
+    mkdir -p $zombienet_dir
+    rm -rf $zombienet_dir
+
+    local logs_dir=$test_dir/logs
+    mkdir -p $logs_dir
+    local zombienet_log=$logs_dir/$zombienet_name.log
+
+    echo "Starting $zombienet_name zombienet. Logs available at: $zombienet_log"
+    start_background_process \
+        "$ZOMBIENET_BINARY spawn --dir $zombienet_dir --provider native $definition_path" \
+        "$zombienet_log" zombienet_pid
+
+    ensure_process_file $zombienet_pid "$zombienet_dir/zombie.json" 180
+    echo "$zombienet_name zombienet started successfully"
+
+    eval $__zombienet_pid="'$zombienet_pid'"
+}
+
+function run_zndsl() {
+    local zndsl_file=$1
+    local zombienet_dir=$2
+
+    echo "Running $zndsl_file."
+    $ZOMBIENET_BINARY test --dir $zombienet_dir --provider native $zndsl_file $zombienet_dir/zombie.json
+    echo
+}
diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-new-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7c84a69aa47de84439091cb7b908233d02238175
--- /dev/null
+++ b/bridges/testing/run-new-test.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+set -e
+
+trap 'kill -9 -$$ || echo "Environment already teared down"' SIGINT SIGTERM EXIT
+
+test=$1
+shift
+
+# whether to use paths for zombienet+bridges tests container or for local testing
+ZOMBIENET_DOCKER_PATHS=0
+while [ $# -ne 0 ]
+do
+    arg="$1"
+    case "$arg" in
+        --docker)
+            ZOMBIENET_DOCKER_PATHS=1
+            ;;
+    esac
+    shift
+done
+
+export POLKADOT_SDK_PATH=`realpath ${BASH_SOURCE%/*}/../..`
+export FRAMEWORK_PATH=`realpath ${BASH_SOURCE%/*}/framework`
+
+# set path to binaries
+if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then
+    # otherwise zombienet uses some hardcoded paths
+    unset RUN_IN_CONTAINER
+    unset ZOMBIENET_IMAGE
+
+    export POLKADOT_BINARY=/usr/local/bin/polkadot
+    export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain
+
+    export ZOMBIENET_BINARY=/usr/local/bin/zombie
+    export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay
+else
+    export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot
+    export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain
+
+    export ZOMBIENET_BINARY=~/local_bridge_testing/bin/zombienet-linux-x64
+    export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay
+fi
+
+export TEST_DIR=`mktemp -d /tmp/bridges-tests-run-XXXXX`
+echo -e "Test folder: $TEST_DIR\n"
+
+${BASH_SOURCE%/*}/tests/$test/run.sh
diff --git a/bridges/testing/run-tests.sh b/bridges/testing/run-tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6149d9912653c79968a0229759c8f1bf46f68a9f
--- /dev/null
+++ b/bridges/testing/run-tests.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+set -x
+shopt -s nullglob
+
+trap "trap - SIGINT SIGTERM EXIT && killall -q -9 substrate-relay && kill -- -$$" SIGINT SIGTERM EXIT
+
+# run tests in range [TESTS_BEGIN; TESTS_END)
+TESTS_BEGIN=1
+TESTS_END=1000
+# whether to use paths for zombienet+bridges tests container or for local testing
+ZOMBIENET_DOCKER_PATHS=0
+while [ $# -ne 0 ]
+do
+    arg="$1"
+    case "$arg" in
+        --docker)
+            ZOMBIENET_DOCKER_PATHS=1
+            ;;
+        --test)
+            shift
+            TESTS_BEGIN="$1"
+            TESTS_END="$1"
+            ;;
+    esac
+    shift
+done
+
+# assuming that we'll be using native provide && all processes will be executing locally
+# (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders)
+export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..`
+export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/testing/tests
+
+# set pathc to binaries
+if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then
+    export POLKADOT_BINARY=/usr/local/bin/polkadot
+    export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain
+
+    export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay
+    export ZOMBIENET_BINARY_PATH=/usr/local/bin/zombie
+else
+    export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot
+    export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain
+
+    export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay
+    export ZOMBIENET_BINARY_PATH=~/local_bridge_testing/bin/zombienet-linux
+fi
+
+# check if `wait` supports -p flag
+if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi
+
+# bridge configuration
+export LANE_ID="00000002"
+
+# tests configuration
+ALL_TESTS_FOLDER=`mktemp -d /tmp/bridges-zombienet-tests.XXXXX`
+
+function start_coproc() {
+    local command=$1
+    local name=$2
+    local logname=`basename $name`
+    local coproc_log=`mktemp -p $TEST_FOLDER $logname.XXXXX`
+    coproc COPROC {
+        # otherwise zombienet uses some hardcoded paths
+        unset RUN_IN_CONTAINER
+        unset ZOMBIENET_IMAGE
+
+        $command >$coproc_log 2>&1
+    }
+    TEST_COPROCS[$COPROC_PID, 0]=$name
+    TEST_COPROCS[$COPROC_PID, 1]=$coproc_log
+    echo "Spawned $name coprocess. StdOut + StdErr: $coproc_log"
+
+    return $COPROC_PID
+}
+
+# execute every test from tests folder
+TEST_INDEX=$TESTS_BEGIN
+while true
+do
+    declare -A TEST_COPROCS
+    TEST_COPROCS_COUNT=0
+    TEST_PREFIX=$(printf "%04d" $TEST_INDEX)
+
+    # it'll be used by the `sync-exit.sh` script
+    export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER test-$TEST_PREFIX.XXXXX`
+
+    # check if there are no more tests
+    zndsl_files=($BRIDGE_TESTS_FOLDER/$TEST_PREFIX-*.zndsl)
+    if [ ${#zndsl_files[@]} -eq 0 ]; then
+        break
+    fi
+
+    # start tests
+    for zndsl_file in "${zndsl_files[@]}"; do
+        start_coproc "$ZOMBIENET_BINARY_PATH --provider native test $zndsl_file" "$zndsl_file"
+        echo -n "1">>$TEST_FOLDER/exit-sync
+        ((TEST_COPROCS_COUNT++))
+    done
+    # wait until all tests are completed
+    for n in `seq 1 $TEST_COPROCS_COUNT`; do
+        if [ "$IS_BASH_5_1" -eq 1 ]; then
+            wait -n -p COPROC_PID
+            exit_code=$?
+            coproc_name=${TEST_COPROCS[$COPROC_PID, 0]}
+            coproc_log=${TEST_COPROCS[$COPROC_PID, 1]}
+            coproc_stdout=$(cat $coproc_log)
+        else
+            wait -n
+            exit_code=$?
+            coproc_name="<unknown>"
+            coproc_stdout="<unknown>"
+        fi
+        echo "Process $coproc_name has finished with exit code: $exit_code"
+
+        # if exit code is not zero, exit
+        if [ $exit_code -ne 0 ]; then
+            echo "====================================================================="
+            echo "=== Shutting down. Log of failed process below                    ==="
+            echo "====================================================================="
+            echo "$coproc_stdout"
+
+            exit 1
+        fi
+    done
+
+    # proceed to next index
+    ((TEST_INDEX++))
+    if [ "$TEST_INDEX" -ge "$TESTS_END" ]; then
+        break
+    fi
+
+    # kill relay here - it is started manually by tests
+    killall substrate-relay
+done
+
+echo "====================================================================="
+echo "=== All tests have completed successfully                         ==="
+echo "====================================================================="
diff --git a/bridges/testing/scripts/invoke-script.sh b/bridges/testing/scripts/invoke-script.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cd0557b071bbadc41e056a2e50c9f1aa0b677312
--- /dev/null
+++ b/bridges/testing/scripts/invoke-script.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX`
+
+pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend
+./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1
+popd
diff --git a/bridges/testing/scripts/start-relayer.sh b/bridges/testing/scripts/start-relayer.sh
new file mode 100755
index 0000000000000000000000000000000000000000..38ea62fad524486c40cf88943c48a2e4df4b86e8
--- /dev/null
+++ b/bridges/testing/scripts/start-relayer.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX`
+
+pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend
+./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1&
+popd
diff --git a/bridges/testing/scripts/sync-exit.sh b/bridges/testing/scripts/sync-exit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cc20b098e7830fc164f3a0a643840c1e8188b7f2
--- /dev/null
+++ b/bridges/testing/scripts/sync-exit.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e
+
+# every network adds a char to the file, let's remove ours
+truncate -s -1 $TEST_FOLDER/exit-sync
+
+# when all chars are removed, then our test is done
+while true
+do
+    if [ `stat --printf="%s" $TEST_FOLDER/exit-sync` -eq 0 ]; then
+        exit
+    fi
+    sleep 100
+done
diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..cdb7d28e940cf1ac90562e761cdbad00e95e1748
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl
@@ -0,0 +1,12 @@
+Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# send 5 ROC to //Alice from Rococo AH to Westend AH
+asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds
+
+# check that //Alice received at least 4.8 ROC on Westend AH
+asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Rococo" within 600 seconds
+
+# check that the relayer //Charlie is rewarded by Westend AH
+bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds
diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a7bb122919b40187c49e89c489d2271d646bff40
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/run.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+
+source "${BASH_SOURCE%/*}/../../framework/utils/common.sh"
+source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh"
+
+export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend`
+
+$ENV_PATH/spawn.sh --init --start-relayer &
+env_pid=$!
+
+ensure_process_file $env_pid $TEST_DIR/rococo.env 600
+rococo_dir=`cat $TEST_DIR/rococo.env`
+echo
+
+ensure_process_file $env_pid $TEST_DIR/westend.env 300
+westend_dir=`cat $TEST_DIR/westend.env`
+echo
+
+run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir
+run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir
+
+run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir
+run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir
diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..dbc03864e2b6e5e10636532ad965860b381fa8f2
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl
@@ -0,0 +1,12 @@
+Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# send 5 WND to //Alice from Westend AH to Rococo AH
+asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds
+
+# check that //Alice received at least 4.8 WND on Rococo AH
+asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Westend" within 600 seconds
+
+# check that the relayer //Charlie is rewarded by Rococo AH
+bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds
diff --git a/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..9967732cabe15e020060eb0a53fd8ce1440180e5
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl
@@ -0,0 +1,10 @@
+Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# send 3 wROC back to Alice from Westend AH to Rococo AH
+asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local 3000000000000" within 120 seconds
+
+# check that //Alice received at least 2.8 wROC on Rococo AH
+# (we wait until //Alice account increases here - there are no other transactions that may increase it)
+asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,2800000000000" within 600 seconds
diff --git a/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..2037b0baf3c0aac2a0e1a8f297c6baa155ac680f
--- /dev/null
+++ b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl
@@ -0,0 +1,10 @@
+Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# send 3 wWND back to Alice from Rococo AH to Westend AH
+asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local 3000000000000" within 120 seconds
+
+# check that //Alice received at least 2.8 wWND on Westend AH
+# (we wait until //Alice account increases here - there are no other transactions that may increase it)
+asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,2800000000000" within 600 seconds
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..6e381f5377329430c0d7a8723f9ea9081556bfeb
--- /dev/null
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl
@@ -0,0 +1,8 @@
+Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH.
+Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
+Creds: config
+
+# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were
+# generated while relay was offline and those in the next 100 seconds while script is active.
+bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds
+
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7d5b8d9273664b0861e8ffe1c528e9e1718c4df4
--- /dev/null
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+set -e
+
+source "${BASH_SOURCE%/*}/../../framework/utils/common.sh"
+source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh"
+
+export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend`
+
+$ENV_PATH/spawn.sh &
+env_pid=$!
+
+ensure_process_file $env_pid $TEST_DIR/rococo.env 600
+rococo_dir=`cat $TEST_DIR/rococo.env`
+echo
+
+ensure_process_file $env_pid $TEST_DIR/westend.env 300
+westend_dir=`cat $TEST_DIR/westend.env`
+echo
+
+# Sleep for some time before starting the relayer. We want to sleep for at least 1 session,
+# which is expected to be 60 seconds for the test environment.
+echo -e "Sleeping 90s before starting relayer ...\n"
+sleep 90
+${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid
+
+# Sometimes the relayer syncs multiple parachain heads in the begining leading to test failures.
+# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838.
+# TODO: Remove this sleep after the issue is fixed.
+echo -e "Sleeping 180s before runing the tests ...\n"
+sleep 180
+
+run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir
+run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir
+
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..b4b3e43679162feb8c3c5253f3f963d950f31d55
--- /dev/null
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl
@@ -0,0 +1,7 @@
+Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH.
+Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were
+# generated while relay was offline and those in the next 100 seconds while script is active.
+bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds
diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..07b91481dc7cf995b913a9bf84edd3728982eaae
--- /dev/null
+++ b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH.
+Network: ../environments/rococo-westend/bridge_hub_westend_local_network.toml
+Creds: config
+
+# step 1: initialize Westend AH
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds
+
+# step 2: initialize Westend bridge hub
+bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-westend-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds
+
+# step 4: send message from Westend to Rococo
+asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds
+
+# step 5: start relayer
+# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script
+# will be started at step 6)
+# (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl)
+
+# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations
+bridge-hub-westend-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds
diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl
new file mode 100644
index 0000000000000000000000000000000000000000..a6b11fc24052aadf562bc34704aeda9ee115eccf
--- /dev/null
+++ b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl
@@ -0,0 +1,26 @@
+Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH.
+Network: ../environments/rococo-westend/bridge_hub_rococo_local_network.toml
+Creds: config
+
+# step 1: initialize Rococo AH
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds
+
+# step 2: initialize Rococo bridge hub
+bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds
+
+# step 3: ensure that initialization has completed
+asset-hub-rococo-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds
+
+# step 4: send message from Rococo to Westend
+asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds
+
+# step 5: start relayer
+# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script
+# will be started at step 6)
+bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds
+
+# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations
+bridge-hub-rococo-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds
+
+# wait until other network test has completed OR exit with an error too
+asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds