From a75c28d5b29b3646aedd53dacf09eafa95e57c58 Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Thu, 23 Mar 2023 10:44:53 +0300
Subject: [PATCH] Remove deprecated code from bridge-runtime-common (#1983)

* removed FromBridgedChainMessageDispatch in favor of XcmBlobMessageDispatch

* use HaulBlobExporter/HaulBlobExporterAdapter instead of XcmBridge/XcmBridgeAdapter

* tests for sending/dispatching messages

* use new schema in testnet bridges + some cleanup

* clippy

* spelling + added TODO

* cleanup some checks

* benchmarks compilation

* all is XCM

* updated README.md

* ref issue from TODO
---
 bridges/README.md                             |   9 +-
 bridges/bin/millau/runtime/src/lib.rs         |   8 +-
 .../bin/millau/runtime/src/rialto_messages.rs |  68 ++--
 .../runtime/src/rialto_parachain_messages.rs  |  70 ++--
 bridges/bin/millau/runtime/src/xcm_config.rs  | 304 +++++++++------
 .../bin/rialto-parachain/runtime/Cargo.toml   |   2 +
 .../bin/rialto-parachain/runtime/src/lib.rs   | 177 ++++-----
 .../runtime/src/millau_messages.rs            |  64 ++--
 .../bin/rialto/runtime/src/millau_messages.rs |  68 ++--
 bridges/bin/rialto/runtime/src/xcm_config.rs  | 177 +++++----
 bridges/bin/runtime-common/src/integrity.rs   |   2 +-
 bridges/bin/runtime-common/src/messages.rs    | 355 ++----------------
 .../src/messages_xcm_extension.rs             |  32 +-
 bridges/bin/runtime-common/src/mock.rs        |  46 +--
 bridges/primitives/runtime/src/lib.rs         |   4 +-
 .../relays/bin-substrate/src/chains/millau.rs |  29 +-
 .../relays/bin-substrate/src/chains/rialto.rs |  21 +-
 .../src/chains/rialto_parachain.rs            |  22 +-
 .../relays/bin-substrate/src/cli/bridge.rs    |  22 --
 .../bin-substrate/src/cli/encode_message.rs   |  18 +-
 .../bin-substrate/src/cli/send_message.rs     |   7 +-
 21 files changed, 610 insertions(+), 895 deletions(-)

diff --git a/bridges/README.md b/bridges/README.md
index b5885026426..aab6007d2cd 100644
--- a/bridges/README.md
+++ b/bridges/README.md
@@ -215,18 +215,19 @@ In this section we'll show you how to quickly send a bridge message. The message
 After sending a message you will see the following logs showing a message was successfully sent:
 
 ```
-INFO bridge Sending message to Rialto. Size: 5.
+INFO bridge Sending message to Rialto. Size: 11.
 TRACE bridge Sent transaction to Millau node: 0x5e68...
 ```
 
 And at the Rialto node logs you'll something like this:
 
 ```
-... runtime::bridge-dispatch: Going to execute message ([0, 0, 0, 0], 1) (...), Trap(43)])    
-... runtime::bridge-dispatch: Incoming message ([0, 0, 0, 0], 1) dispatched with result: Incomplete(2000000000, Trap(43))    
+... runtime::bridge-messages: Received messages: total=1, valid=1. Weight used: Weight(ref_time: 1215065371, proof_size: 48559)/Weight(ref_time: 1215065371, proof_size: 54703).
 ``` 
 
-It means that the message has been delivered and successfully dispatched.
+It means that the message has been delivered and dispatched. Message may be dispatched with an
+error, though - the goal of our test bridge is to ensure that messages are successfully delivered
+and all involved components are working.
 
 ## Full Network Docker Compose Setup
 
diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs
index 574b0569875..f08f5e1c749 100644
--- a/bridges/bin/millau/runtime/src/lib.rs
+++ b/bridges/bin/millau/runtime/src/lib.rs
@@ -1021,10 +1021,8 @@ impl_runtime_apis! {
 				}
 
 				fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool {
-					use bridge_runtime_common::messages::MessageBridge;
-
 					let lane = <Self as MessagesConfig<WithRialtoParachainMessagesInstance>>::bench_lane_id();
-					let bridged_chain_id = WithRialtoParachainMessageBridge::BRIDGED_CHAIN_ID;
+					let bridged_chain_id = bp_runtime::RIALTO_PARACHAIN_CHAIN_ID;
 					pallet_bridge_relayers::Pallet::<Runtime>::relayer_reward(
 						relayer,
 						RewardsAccountParams::new(lane, bridged_chain_id, RewardsAccountOwner::BridgedChain)
@@ -1054,10 +1052,8 @@ impl_runtime_apis! {
 				}
 
 				fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool {
-					use bridge_runtime_common::messages::MessageBridge;
-
 					let lane = <Self as MessagesConfig<WithRialtoMessagesInstance>>::bench_lane_id();
-					let bridged_chain_id = WithRialtoMessageBridge::BRIDGED_CHAIN_ID;
+					let bridged_chain_id = bp_runtime::RIALTO_CHAIN_ID;
 					pallet_bridge_relayers::Pallet::<Runtime>::relayer_reward(
 						relayer,
 						RewardsAccountParams::new(lane, bridged_chain_id, RewardsAccountOwner::BridgedChain)
diff --git a/bridges/bin/millau/runtime/src/rialto_messages.rs b/bridges/bin/millau/runtime/src/rialto_messages.rs
index 919fac86899..311805c9849 100644
--- a/bridges/bin/millau/runtime/src/rialto_messages.rs
+++ b/bridges/bin/millau/runtime/src/rialto_messages.rs
@@ -16,14 +16,18 @@
 
 //! Everything required to serve Millau <-> Rialto messages.
 
-use crate::{RialtoGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin};
-
-use bp_messages::{LaneId, MessageNonce};
-use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
-use bridge_runtime_common::messages::{
-	self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+use crate::{RialtoGrandpaInstance, Runtime, RuntimeOrigin, WithRialtoMessagesInstance};
+
+use bp_messages::LaneId;
+use bridge_runtime_common::{
+	messages::{
+		self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+	},
+	messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter},
 };
 use frame_support::{parameter_types, weights::Weight, RuntimeDebug};
+use xcm::latest::prelude::*;
+use xcm_builder::HaulBlobExporter;
 
 /// Default lane that is used to send messages to Rialto.
 pub const XCM_LANE: LaneId = LaneId([0, 0, 0, 0]);
@@ -48,7 +52,7 @@ pub type ToRialtoMessageVerifier =
 	messages::source::FromThisChainMessageVerifier<WithRialtoMessageBridge>;
 
 /// Message payload for Rialto -> Millau messages.
-pub type FromRialtoMessagePayload = messages::target::FromBridgedChainMessagePayload<RuntimeCall>;
+pub type FromRialtoMessagePayload = messages::target::FromBridgedChainMessagePayload;
 
 /// Messages proof for Rialto -> Millau messages.
 pub type FromRialtoMessagesProof = messages::target::FromBridgedChainMessagesProof<bp_rialto::Hash>;
@@ -58,12 +62,13 @@ pub type ToRialtoMessagesDeliveryProof =
 	messages::source::FromBridgedChainMessagesDeliveryProof<bp_rialto::Hash>;
 
 /// Call-dispatch based message dispatch for Rialto -> Millau messages.
-pub type FromRialtoMessageDispatch = messages::target::FromBridgedChainMessageDispatch<
-	WithRialtoMessageBridge,
-	xcm_executor::XcmExecutor<crate::xcm_config::XcmConfig>,
-	crate::xcm_config::XcmWeigher,
-	WeightCredit,
->;
+pub type FromRialtoMessageDispatch =
+	bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch<
+		bp_millau::Millau,
+		bp_rialto::Rialto,
+		crate::xcm_config::OnMillauBlobDispatcher,
+		bridge_runtime_common::messages_xcm_extension::XcmRouterWeigher<crate::DbWeight>,
+	>;
 
 /// Maximal outbound payload size of Millau -> Rialto messages.
 pub type ToRialtoMaximalOutboundPayloadSize =
@@ -74,8 +79,6 @@ pub type ToRialtoMaximalOutboundPayloadSize =
 pub struct WithRialtoMessageBridge;
 
 impl MessageBridge for WithRialtoMessageBridge {
-	const THIS_CHAIN_ID: ChainId = MILLAU_CHAIN_ID;
-	const BRIDGED_CHAIN_ID: ChainId = RIALTO_CHAIN_ID;
 	const BRIDGED_MESSAGES_PALLET_NAME: &'static str = bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME;
 
 	type ThisChain = Millau;
@@ -94,15 +97,6 @@ impl messages::UnderlyingChainProvider for Millau {
 
 impl messages::ThisChainWithMessages for Millau {
 	type RuntimeOrigin = RuntimeOrigin;
-	type RuntimeCall = RuntimeCall;
-
-	fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool {
-		true
-	}
-
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce {
-		MessageNonce::MAX
-	}
 }
 
 /// Rialto chain from message lane point of view.
@@ -117,9 +111,29 @@ impl messages::UnderlyingChainProvider for Rialto {
 	type Chain = bp_rialto::Rialto;
 }
 
-impl messages::BridgedChainWithMessages for Rialto {
-	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
-		true
+impl messages::BridgedChainWithMessages for Rialto {}
+
+/// Export XCM messages to be relayed to Rialto.
+pub type ToRialtoBlobExporter = HaulBlobExporter<
+	XcmBlobHaulerAdapter<ToRialtoXcmBlobHauler>,
+	crate::xcm_config::RialtoNetwork,
+	(),
+>;
+
+/// To-Rialto XCM hauler.
+pub struct ToRialtoXcmBlobHauler;
+
+impl XcmBlobHauler for ToRialtoXcmBlobHauler {
+	type MessageSender = pallet_bridge_messages::Pallet<Runtime, WithRialtoMessagesInstance>;
+	type MessageSenderOrigin = RuntimeOrigin;
+
+	fn message_sender_origin() -> RuntimeOrigin {
+		pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get()))
+			.into()
+	}
+
+	fn xcm_lane() -> LaneId {
+		XCM_LANE
 	}
 }
 
diff --git a/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs b/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs
index 1cfdc9c8f49..17495e26ae2 100644
--- a/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs
+++ b/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs
@@ -16,14 +16,20 @@
 
 //! Everything required to serve Millau <-> RialtoParachain messages.
 
-use crate::{Runtime, RuntimeCall, RuntimeOrigin, WithRialtoParachainsInstance};
+use crate::{
+	Runtime, RuntimeOrigin, WithRialtoParachainMessagesInstance, WithRialtoParachainsInstance,
+};
 
-use bp_messages::{LaneId, MessageNonce};
-use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID};
-use bridge_runtime_common::messages::{
-	self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+use bp_messages::LaneId;
+use bridge_runtime_common::{
+	messages::{
+		self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+	},
+	messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter},
 };
 use frame_support::{parameter_types, weights::Weight, RuntimeDebug};
+use xcm::latest::prelude::*;
+use xcm_builder::HaulBlobExporter;
 
 /// Default lane that is used to send messages to Rialto parachain.
 pub const XCM_LANE: LaneId = LaneId([0, 0, 0, 0]);
@@ -48,16 +54,16 @@ pub type ToRialtoParachainMessageVerifier =
 	messages::source::FromThisChainMessageVerifier<WithRialtoParachainMessageBridge>;
 
 /// Message payload for RialtoParachain -> Millau messages.
-pub type FromRialtoParachainMessagePayload =
-	messages::target::FromBridgedChainMessagePayload<RuntimeCall>;
+pub type FromRialtoParachainMessagePayload = messages::target::FromBridgedChainMessagePayload;
 
 /// Call-dispatch based message dispatch for RialtoParachain -> Millau messages.
-pub type FromRialtoParachainMessageDispatch = messages::target::FromBridgedChainMessageDispatch<
-	WithRialtoParachainMessageBridge,
-	xcm_executor::XcmExecutor<crate::xcm_config::XcmConfig>,
-	crate::xcm_config::XcmWeigher,
-	WeightCredit,
->;
+pub type FromRialtoParachainMessageDispatch =
+	bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch<
+		bp_millau::Millau,
+		bp_rialto::Rialto,
+		crate::xcm_config::OnMillauBlobDispatcher,
+		bridge_runtime_common::messages_xcm_extension::XcmRouterWeigher<crate::DbWeight>,
+	>;
 
 /// Maximal outbound payload size of Millau -> RialtoParachain messages.
 pub type ToRialtoParachainMaximalOutboundPayloadSize =
@@ -68,8 +74,6 @@ pub type ToRialtoParachainMaximalOutboundPayloadSize =
 pub struct WithRialtoParachainMessageBridge;
 
 impl MessageBridge for WithRialtoParachainMessageBridge {
-	const THIS_CHAIN_ID: ChainId = MILLAU_CHAIN_ID;
-	const BRIDGED_CHAIN_ID: ChainId = RIALTO_PARACHAIN_CHAIN_ID;
 	const BRIDGED_MESSAGES_PALLET_NAME: &'static str = bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME;
 
 	type ThisChain = Millau;
@@ -90,16 +94,7 @@ impl messages::UnderlyingChainProvider for Millau {
 }
 
 impl messages::ThisChainWithMessages for Millau {
-	type RuntimeCall = RuntimeCall;
 	type RuntimeOrigin = RuntimeOrigin;
-
-	fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool {
-		true
-	}
-
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce {
-		MessageNonce::MAX
-	}
 }
 
 /// RialtoParachain chain from message lane point of view.
@@ -116,8 +111,29 @@ impl messages::UnderlyingChainProvider for RialtoParachain {
 	type Chain = bp_rialto_parachain::RialtoParachain;
 }
 
-impl messages::BridgedChainWithMessages for RialtoParachain {
-	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
-		true
+impl messages::BridgedChainWithMessages for RialtoParachain {}
+
+/// Export XCM messages to be relayed to Rialto.
+pub type ToRialtoParachainBlobExporter = HaulBlobExporter<
+	XcmBlobHaulerAdapter<ToRialtoParachainXcmBlobHauler>,
+	crate::xcm_config::RialtoParachainNetwork,
+	(),
+>;
+
+/// To-RialtoParachain XCM hauler.
+pub struct ToRialtoParachainXcmBlobHauler;
+
+impl XcmBlobHauler for ToRialtoParachainXcmBlobHauler {
+	type MessageSender =
+		pallet_bridge_messages::Pallet<Runtime, WithRialtoParachainMessagesInstance>;
+	type MessageSenderOrigin = RuntimeOrigin;
+
+	fn message_sender_origin() -> RuntimeOrigin {
+		pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get()))
+			.into()
+	}
+
+	fn xcm_lane() -> LaneId {
+		XCM_LANE
 	}
 }
diff --git a/bridges/bin/millau/runtime/src/xcm_config.rs b/bridges/bin/millau/runtime/src/xcm_config.rs
index aaaa7bf642d..c477a684526 100644
--- a/bridges/bin/millau/runtime/src/xcm_config.rs
+++ b/bridges/bin/millau/runtime/src/xcm_config.rs
@@ -17,18 +17,12 @@
 //! XCM configurations for the Millau runtime.
 
 use super::{
-	rialto_messages::{WithRialtoMessageBridge, XCM_LANE},
-	rialto_parachain_messages::{WithRialtoParachainMessageBridge, XCM_LANE as XCM_LANE_PARACHAIN},
-	AccountId, AllPalletsWithSystem, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
-	WithRialtoMessagesInstance, WithRialtoParachainMessagesInstance, XcmPallet,
+	rialto_messages::ToRialtoBlobExporter,
+	rialto_parachain_messages::ToRialtoParachainBlobExporter, AccountId, AllPalletsWithSystem,
+	Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmPallet,
 };
-use bp_messages::LaneId;
 use bp_millau::WeightToFee;
-use bp_rialto_parachain::RIALTO_PARACHAIN_ID;
-use bridge_runtime_common::{
-	messages::source::{XcmBridge, XcmBridgeAdapter},
-	CustomNetworkId,
-};
+use bridge_runtime_common::CustomNetworkId;
 use frame_support::{
 	parameter_types,
 	traits::{ConstU32, Everything, Nothing},
@@ -36,10 +30,11 @@ use frame_support::{
 };
 use xcm::latest::prelude::*;
 use xcm_builder::{
-	AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom,
-	CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, MintLocation, SignedAccountId32AsNative,
-	SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
+	AccountId32Aliases, CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, MintLocation,
+	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
+	UsingComponents,
 };
+use xcm_executor::traits::ExportXcm;
 
 parameter_types! {
 	/// The location of the `MLAU` token, from the context of this chain. Since this token is native to this
@@ -101,32 +96,28 @@ parameter_types! {
 	pub const MaxInstructions: u32 = 100;
 }
 
-/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our
-/// individual routers.
-pub type XcmRouter = (
-	// Router to send messages to Rialto.
-	XcmBridgeAdapter<ToRialtoBridge>,
-	// Router to send messages to RialtoParachains.
-	XcmBridgeAdapter<ToRialtoParachainBridge>,
-);
+/// The XCM router. We are not sending messages to sibling/parent/child chains here.
+pub type XcmRouter = ();
 
 /// The barriers one of which must be passed for an XCM message to be executed.
 pub type Barrier = (
 	// Weight that is paid for may be consumed.
 	TakeWeightCredit,
-	// If the message is one that immediately attemps to pay for execution, then allow it.
-	AllowTopLevelPaidExecutionFrom<Everything>,
-	// Expected responses are OK.
-	AllowKnownQueryResponses<XcmPallet>,
 );
 
+/// Dispatches received XCM messages from other chain.
+pub type OnMillauBlobDispatcher = xcm_builder::BridgeBlobDispatcher<
+	crate::xcm_config::XcmRouter,
+	crate::xcm_config::UniversalLocation,
+>;
+
 /// XCM weigher type.
 pub type XcmWeigher = xcm_builder::FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
 
 pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
-	type XcmSender = XcmRouter;
+	type XcmSender = ();
 	type AssetTransactor = LocalAssetTransactor;
 	type OriginConverter = LocalOriginConverter;
 	type IsReserve = ();
@@ -145,7 +136,7 @@ impl xcm_executor::Config for XcmConfig {
 	type PalletInstancesInfo = AllPalletsWithSystem;
 	type MaxAssetsIntoHolding = ConstU32<64>;
 	type FeeManager = ();
-	type MessageExporter = ();
+	type MessageExporter = ToRialtoOrRialtoParachainSwitchExporter;
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
 	type SafeCallFilter = Everything;
@@ -160,7 +151,7 @@ pub type LocalOriginToLocation = (
 
 #[cfg(feature = "runtime-benchmarks")]
 parameter_types! {
-	pub ReachableDest: Option<MultiLocation> = todo!("We dont use benchmarks for pallet_xcm, so if you hit this message, you need to remove this and define value instead");
+	pub ReachableDest: Option<MultiLocation> = None;
 }
 
 impl pallet_xcm::Config for Runtime {
@@ -170,7 +161,7 @@ impl pallet_xcm::Config for Runtime {
 	// the DOT to send from the Relay-chain). But it's useless until we bring in XCM v3 which will
 	// make `DescendOrigin` a bit more useful.
 	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
-	type XcmRouter = XcmRouter;
+	type XcmRouter = ();
 	// Anyone can execute XCM messages locally.
 	type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
 	type XcmExecuteFilter = Everything;
@@ -197,70 +188,66 @@ impl pallet_xcm::Config for Runtime {
 	type ReachableDest = ReachableDest;
 }
 
-/// With-Rialto bridge.
-pub struct ToRialtoBridge;
-
-impl XcmBridge for ToRialtoBridge {
-	type MessageBridge = WithRialtoMessageBridge;
-	type MessageSender = pallet_bridge_messages::Pallet<Runtime, WithRialtoMessagesInstance>;
-
-	fn universal_location() -> InteriorMultiLocation {
-		UniversalLocation::get()
+pub struct ToRialtoOrRialtoParachainSwitchExporter;
+
+impl ExportXcm for ToRialtoOrRialtoParachainSwitchExporter {
+	type Ticket = (NetworkId, (sp_std::prelude::Vec<u8>, XcmHash));
+
+	fn validate(
+		network: NetworkId,
+		channel: u32,
+		universal_source: &mut Option<InteriorMultiLocation>,
+		destination: &mut Option<InteriorMultiLocation>,
+		message: &mut Option<Xcm<()>>,
+	) -> SendResult<Self::Ticket> {
+		if network == RialtoNetwork::get() {
+			ToRialtoBlobExporter::validate(network, channel, universal_source, destination, message)
+				.map(|result| ((RialtoNetwork::get(), result.0), result.1))
+		} else if network == RialtoParachainNetwork::get() {
+			ToRialtoParachainBlobExporter::validate(
+				network,
+				channel,
+				universal_source,
+				destination,
+				message,
+			)
+			.map(|result| ((RialtoParachainNetwork::get(), result.0), result.1))
+		} else {
+			Err(SendError::Unroutable)
+		}
 	}
 
-	fn verify_destination(dest: &MultiLocation) -> bool {
-		matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == RialtoNetwork::get())
-	}
-
-	fn build_destination() -> MultiLocation {
-		let dest: InteriorMultiLocation = RialtoNetwork::get().into();
-		let here = UniversalLocation::get();
-		dest.relative_to(&here)
-	}
-
-	fn xcm_lane() -> LaneId {
-		XCM_LANE
-	}
-}
-
-/// With-RialtoParachain bridge.
-pub struct ToRialtoParachainBridge;
-
-impl XcmBridge for ToRialtoParachainBridge {
-	type MessageBridge = WithRialtoParachainMessageBridge;
-	type MessageSender =
-		pallet_bridge_messages::Pallet<Runtime, WithRialtoParachainMessagesInstance>;
-
-	fn universal_location() -> InteriorMultiLocation {
-		UniversalLocation::get()
-	}
-
-	fn verify_destination(dest: &MultiLocation) -> bool {
-		matches!(*dest, MultiLocation { parents: 1, interior: X2(GlobalConsensus(r), Parachain(RIALTO_PARACHAIN_ID)) } if r == RialtoNetwork::get())
-	}
-
-	fn build_destination() -> MultiLocation {
-		let dest: InteriorMultiLocation = RialtoParachainNetwork::get().into();
-		let here = UniversalLocation::get();
-		dest.relative_to(&here)
-	}
-
-	fn xcm_lane() -> LaneId {
-		XCM_LANE_PARACHAIN
+	fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
+		let (network, ticket) = ticket;
+		if network == RialtoNetwork::get() {
+			ToRialtoBlobExporter::deliver(ticket)
+		} else if network == RialtoParachainNetwork::get() {
+			ToRialtoParachainBlobExporter::deliver(ticket)
+		} else {
+			Err(SendError::Unroutable)
+		}
 	}
 }
 
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::rialto_messages::WeightCredit;
+	use crate::{
+		rialto_messages::FromRialtoMessageDispatch,
+		rialto_parachain_messages::FromRialtoParachainMessageDispatch, DbWeight,
+		WithRialtoMessagesInstance, WithRialtoParachainMessagesInstance,
+	};
 	use bp_messages::{
 		target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
-		MessageKey,
+		LaneId, MessageKey,
+	};
+	use bridge_runtime_common::messages_xcm_extension::{
+		XcmBlobMessageDispatchResult, XcmRouterWeigher,
 	};
-	use bp_runtime::messages::MessageDispatchResult;
-	use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch;
 	use codec::Encode;
+	use pallet_bridge_messages::OutboundLanes;
+	use sp_core::Get;
+	use xcm_executor::XcmExecutor;
 
 	fn new_test_ext() -> sp_io::TestExternalities {
 		sp_io::TestExternalities::new(
@@ -268,59 +255,128 @@ mod tests {
 		)
 	}
 
+	fn prepare_outbound_xcm_message(destination: NetworkId) -> Xcm<RuntimeCall> {
+		vec![ExportMessage {
+			network: destination,
+			destination: destination.into(),
+			xcm: vec![Instruction::Trap(42)].into(),
+		}]
+		.into()
+	}
+
 	#[test]
-	fn xcm_messages_are_sent_using_bridge_router() {
+	fn xcm_messages_to_rialto_are_sent_using_bridge_exporter() {
 		new_test_ext().execute_with(|| {
-			let xcm: Xcm<()> = vec![Instruction::Trap(42)].into();
-			let expected_fee = MultiAssets::from((Here, 1_000_000_u128));
-			let expected_hash =
-				([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256);
-
-			// message 1 to Rialto
-			let dest = (Parent, X1(GlobalConsensus(RialtoNetwork::get())));
-			let send_result = send_xcm::<XcmRouter>(dest.into(), xcm.clone());
-			assert_eq!(send_result, Ok((expected_hash, expected_fee.clone())));
-
-			// message 2 to RialtoParachain (expected hash is the same, since other lane is used)
-			let dest =
-				(Parent, X2(GlobalConsensus(RialtoNetwork::get()), Parachain(RIALTO_PARACHAIN_ID)));
-			let send_result = send_xcm::<XcmRouter>(dest.into(), xcm);
-			assert_eq!(send_result, Ok((expected_hash, expected_fee)));
+			// ensure that the there are no messages queued
+			assert_eq!(
+				OutboundLanes::<Runtime, WithRialtoMessagesInstance>::get(
+					crate::rialto_messages::XCM_LANE
+				)
+				.latest_generated_nonce,
+				0,
+			);
+
+			// export message instruction "sends" message to Rialto
+			XcmExecutor::<XcmConfig>::execute_xcm_in_credit(
+				Here,
+				prepare_outbound_xcm_message(RialtoNetwork::get()),
+				Default::default(),
+				Weight::MAX,
+				Weight::MAX,
+			)
+			.ensure_complete()
+			.expect("runtime configuration must be correct");
+
+			// ensure that the message has been queued
+			assert_eq!(
+				OutboundLanes::<Runtime, WithRialtoMessagesInstance>::get(
+					crate::rialto_messages::XCM_LANE
+				)
+				.latest_generated_nonce,
+				1,
+			);
 		})
 	}
 
 	#[test]
-	fn xcm_messages_from_rialto_are_dispatched() {
-		type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
-		type MessageDispatcher = FromBridgedChainMessageDispatch<
-			WithRialtoMessageBridge,
-			XcmExecutor,
-			XcmWeigher,
-			WeightCredit,
-		>;
-
+	fn xcm_messages_to_rialto_parachain_are_sent_using_bridge_exporter() {
 		new_test_ext().execute_with(|| {
-			let location: MultiLocation =
-				(Parent, X1(GlobalConsensus(RialtoNetwork::get()))).into();
-			let xcm: Xcm<RuntimeCall> = vec![Instruction::Trap(42)].into();
-
-			let mut incoming_message = DispatchMessage {
-				key: MessageKey { lane_id: LaneId([0, 0, 0, 0]), nonce: 1 },
-				data: DispatchMessageData { payload: Ok((location, xcm).into()) },
-			};
-
-			let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message);
-			assert_eq!(dispatch_weight, BaseXcmWeight::get());
+			// ensure that the there are no messages queued
+			assert_eq!(
+				OutboundLanes::<Runtime, WithRialtoParachainMessagesInstance>::get(
+					crate::rialto_parachain_messages::XCM_LANE
+				)
+				.latest_generated_nonce,
+				0,
+			);
 
-			let dispatch_result =
-				MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message);
+			// export message instruction "sends" message to Rialto
+			XcmExecutor::<XcmConfig>::execute_xcm_in_credit(
+				Here,
+				prepare_outbound_xcm_message(RialtoParachainNetwork::get()),
+				Default::default(),
+				Weight::MAX,
+				Weight::MAX,
+			)
+			.ensure_complete()
+			.expect("runtime configuration must be correct");
+
+			// ensure that the message has been queued
 			assert_eq!(
-				dispatch_result,
-				MessageDispatchResult {
-					unspent_weight: frame_support::weights::Weight::zero(),
-					dispatch_level_result: (),
-				}
+				OutboundLanes::<Runtime, WithRialtoParachainMessagesInstance>::get(
+					crate::rialto_parachain_messages::XCM_LANE
+				)
+				.latest_generated_nonce,
+				1,
 			);
 		})
 	}
+
+	fn prepare_inbound_bridge_message() -> DispatchMessage<Vec<u8>> {
+		let xcm = xcm::VersionedXcm::<RuntimeCall>::V3(vec![Instruction::Trap(42)].into());
+		let location =
+			xcm::VersionedInteriorMultiLocation::V3(X1(GlobalConsensus(ThisNetwork::get())));
+		// this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor
+		// or public fields, so just tuple
+		let bridge_message = (location, xcm).encode();
+		DispatchMessage {
+			key: MessageKey { lane_id: LaneId([0, 0, 0, 0]), nonce: 1 },
+			data: DispatchMessageData { payload: Ok(bridge_message) },
+		}
+	}
+
+	#[test]
+	fn xcm_messages_from_rialto_are_dispatched() {
+		let mut incoming_message = prepare_inbound_bridge_message();
+
+		let dispatch_weight = FromRialtoMessageDispatch::dispatch_weight(&mut incoming_message);
+		assert_eq!(dispatch_weight, XcmRouterWeigher::<DbWeight>::get());
+
+		// we care only about handing message to the XCM dispatcher, so we don't care about its
+		// actual dispatch
+		let dispatch_result =
+			FromRialtoMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
+		assert!(matches!(
+			dispatch_result.dispatch_level_result,
+			XcmBlobMessageDispatchResult::NotDispatched(_),
+		));
+	}
+
+	#[test]
+	fn xcm_messages_from_rialto_parachain_are_dispatched() {
+		let mut incoming_message = prepare_inbound_bridge_message();
+
+		let dispatch_weight =
+			FromRialtoParachainMessageDispatch::dispatch_weight(&mut incoming_message);
+		assert_eq!(dispatch_weight, XcmRouterWeigher::<DbWeight>::get());
+
+		// we care only about handing message to the XCM dispatcher, so we don't care about its
+		// actual dispatch
+		let dispatch_result =
+			FromRialtoMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
+		assert!(matches!(
+			dispatch_result.dispatch_level_result,
+			XcmBlobMessageDispatchResult::NotDispatched(_),
+		));
+	}
 }
diff --git a/bridges/bin/rialto-parachain/runtime/Cargo.toml b/bridges/bin/rialto-parachain/runtime/Cargo.toml
index ec8955e7fd1..ed193b4e681 100644
--- a/bridges/bin/rialto-parachain/runtime/Cargo.toml
+++ b/bridges/bin/rialto-parachain/runtime/Cargo.toml
@@ -111,6 +111,7 @@ std = [
 	"frame-support/std",
 	"frame-executive/std",
 	"frame-system/std",
+	"frame-system-rpc-runtime-api/std",
 	"pallet-balances/std",
 	"pallet-bridge-grandpa/std",
 	"pallet-bridge-messages/std",
@@ -118,6 +119,7 @@ std = [
 	"pallet-timestamp/std",
 	"pallet-sudo/std",
 	"pallet-transaction-payment/std",
+	"pallet-transaction-payment-rpc-runtime-api/std",
 	"pallet-xcm/std",
 	"parachain-info/std",
 	"polkadot-parachain/std",
diff --git a/bridges/bin/rialto-parachain/runtime/src/lib.rs b/bridges/bin/rialto-parachain/runtime/src/lib.rs
index 3614bacdc10..58c6a8c673f 100644
--- a/bridges/bin/rialto-parachain/runtime/src/lib.rs
+++ b/bridges/bin/rialto-parachain/runtime/src/lib.rs
@@ -26,12 +26,7 @@
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
-use crate::millau_messages::{WithMillauMessageBridge, XCM_LANE};
-
-use bridge_runtime_common::{
-	generate_bridge_reject_obsolete_headers_and_messages,
-	messages::source::{XcmBridge, XcmBridgeAdapter},
-};
+use bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages;
 use codec::{Decode, Encode};
 use cumulus_pallet_parachain_system::AnyRelayNumber;
 use scale_info::TypeInfo;
@@ -88,11 +83,10 @@ use pallet_xcm::XcmPassthrough;
 use polkadot_parachain::primitives::Sibling;
 use xcm::latest::prelude::*;
 use xcm_builder::{
-	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
-	EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset,
-	RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
-	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
-	UsingComponents,
+	AccountId32Aliases, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, IsConcrete,
+	NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative,
+	SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
+	SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
 };
 use xcm_executor::{Config, XcmExecutor};
 
@@ -351,7 +345,7 @@ parameter_types! {
 	pub const RelayLocation: MultiLocation = MultiLocation::parent();
 	pub const RelayNetwork: NetworkId = CustomNetworkId::Rialto.as_network_id();
 	pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into()));
+	pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into();
 	/// The Millau network ID.
 	pub const MillauNetwork: NetworkId = CustomNetworkId::Millau.as_network_id();
 	/// The RialtoParachain network ID.
@@ -412,8 +406,8 @@ pub type XcmOriginToTransactDispatchOrigin = (
 // the following constant must match the similar constant in the Millau runtime.
 
 parameter_types! {
-	/// The amount of weight an XCM operation takes. This is a safe overestimate.
-	pub const UnitWeightCost: Weight = Weight::from_parts(1_000_000, 64 * 1024);
+	/// The amount of weight an XCM operation takes. We don't care much about those values as we're on testnet.
+	pub const UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1024);
 	// One UNIT buys 1 second of weight.
 	pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), UNIT);
 	pub const MaxInstructions: u32 = 100;
@@ -428,12 +422,11 @@ match_types! {
 	};
 }
 
-pub type Barrier = (
-	TakeWeightCredit,
-	AllowTopLevelPaidExecutionFrom<Everything>,
-	AllowUnpaidExecutionFrom<ParentOrParentsUnitPlurality>,
-	// ^^^ Parent & its unit plurality gets free execution
-);
+pub type Barrier = TakeWeightCredit;
+
+/// Dispatches received XCM messages from other chain.
+pub type OnRialtoParachainBlobDispatcher =
+	xcm_builder::BridgeBlobDispatcher<XcmRouter, UniversalLocation>;
 
 /// XCM weigher type.
 pub type XcmWeigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
@@ -441,7 +434,7 @@ pub type XcmWeigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstruct
 pub struct XcmConfig;
 impl Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
-	type XcmSender = XcmRouter;
+	type XcmSender = ();
 	type AssetTransactor = LocalAssetTransactor;
 	type OriginConverter = XcmOriginToTransactDispatchOrigin;
 	type IsReserve = NativeAsset;
@@ -459,7 +452,7 @@ impl Config for XcmConfig {
 	type AssetLocker = ();
 	type AssetExchanger = ();
 	type FeeManager = ();
-	type MessageExporter = ();
+	type MessageExporter = millau_messages::ToMillauBlobExporter;
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
 	type SafeCallFilter = Everything;
@@ -468,42 +461,12 @@ impl Config for XcmConfig {
 /// No local origins on this chain are allowed to dispatch XCM sends/executions.
 pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>;
 
-/// The means for routing XCM messages which are not for local execution into the right message
-/// queues.
-pub type XcmRouter = (
-	// Bridge is used to communicate with other relay chain (Millau).
-	XcmBridgeAdapter<ToMillauBridge>,
-);
-
-/// With-Millau bridge.
-pub struct ToMillauBridge;
-
-impl XcmBridge for ToMillauBridge {
-	type MessageBridge = WithMillauMessageBridge;
-	type MessageSender = pallet_bridge_messages::Pallet<Runtime, WithMillauMessagesInstance>;
-
-	fn universal_location() -> InteriorMultiLocation {
-		UniversalLocation::get()
-	}
-
-	fn verify_destination(dest: &MultiLocation) -> bool {
-		matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get())
-	}
-
-	fn build_destination() -> MultiLocation {
-		let dest: InteriorMultiLocation = MillauNetwork::get().into();
-		let here = UniversalLocation::get();
-		dest.relative_to(&here)
-	}
-
-	fn xcm_lane() -> bp_messages::LaneId {
-		XCM_LANE
-	}
-}
+/// The XCM router. We are not sending messages to sibling/parent/child chains here.
+pub type XcmRouter = ();
 
 #[cfg(feature = "runtime-benchmarks")]
 parameter_types! {
-	pub ReachableDest: Option<MultiLocation> = todo!("We dont use benchmarks for pallet_xcm, so if you hit this message, you need to remove this and define value instead");
+	pub ReachableDest: Option<MultiLocation> = None;
 }
 
 impl pallet_xcm::Config for Runtime {
@@ -875,17 +838,20 @@ cumulus_pallet_parachain_system::register_validate_block!(
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::millau_messages::WeightCredit;
+	use crate::millau_messages::{FromMillauMessageDispatch, XCM_LANE};
 	use bp_messages::{
 		target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
 		LaneId, MessageKey,
 	};
-	use bp_runtime::messages::MessageDispatchResult;
 	use bridge_runtime_common::{
-		integrity::check_additional_signed, messages::target::FromBridgedChainMessageDispatch,
+		integrity::check_additional_signed,
+		messages_xcm_extension::{XcmBlobMessageDispatchResult, XcmRouterWeigher},
 	};
 	use codec::Encode;
+	use pallet_bridge_messages::OutboundLanes;
+	use sp_core::Get;
 	use sp_runtime::generic::Era;
+	use xcm_executor::XcmExecutor;
 
 	fn new_test_ext() -> sp_io::TestExternalities {
 		sp_io::TestExternalities::new(
@@ -893,54 +859,75 @@ mod tests {
 		)
 	}
 
+	fn prepare_outbound_xcm_message(destination: NetworkId) -> Xcm<RuntimeCall> {
+		vec![ExportMessage {
+			network: destination,
+			destination: destination.into(),
+			xcm: vec![Instruction::Trap(42)].into(),
+		}]
+		.into()
+	}
+
 	#[test]
-	fn xcm_messages_to_millau_are_sent() {
+	fn xcm_messages_to_millau_are_sent_using_bridge_exporter() {
 		new_test_ext().execute_with(|| {
-			// the encoded message (origin ++ xcm) is 0x010109020419A8
-			let dest = (Parent, X1(GlobalConsensus(MillauNetwork::get())));
-			let xcm: Xcm<()> = vec![Instruction::Trap(42)].into();
-
-			let send_result = send_xcm::<XcmRouter>(dest.into(), xcm);
-			let expected_fee = MultiAssets::from((Here, Fungibility::Fungible(1_000_000_u128)));
-			let expected_hash =
-				([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256);
-			assert_eq!(send_result, Ok((expected_hash, expected_fee)),);
+			// ensure that the there are no messages queued
+			assert_eq!(
+				OutboundLanes::<Runtime, WithMillauMessagesInstance>::get(XCM_LANE)
+					.latest_generated_nonce,
+				0,
+			);
+
+			// export message instruction "sends" message to Rialto
+			XcmExecutor::<XcmConfig>::execute_xcm_in_credit(
+				Here,
+				prepare_outbound_xcm_message(MillauNetwork::get()),
+				Default::default(),
+				Weight::MAX,
+				Weight::MAX,
+			)
+			.ensure_complete()
+			.expect("runtime configuration must be correct");
+
+			// ensure that the message has been queued
+			assert_eq!(
+				OutboundLanes::<Runtime, WithMillauMessagesInstance>::get(XCM_LANE)
+					.latest_generated_nonce,
+				1,
+			);
 		})
 	}
 
+	fn prepare_inbound_bridge_message() -> DispatchMessage<Vec<u8>> {
+		let xcm = xcm::VersionedXcm::<RuntimeCall>::V3(vec![Instruction::Trap(42)].into());
+		let location =
+			xcm::VersionedInteriorMultiLocation::V3(X1(GlobalConsensus(ThisNetwork::get())));
+		// this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor
+		// or public fields, so just tuple
+		let bridge_message = (location, xcm).encode();
+		DispatchMessage {
+			key: MessageKey { lane_id: LaneId([0, 0, 0, 0]), nonce: 1 },
+			data: DispatchMessageData { payload: Ok(bridge_message) },
+		}
+	}
+
 	#[test]
 	fn xcm_messages_from_millau_are_dispatched() {
-		type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
-		type MessageDispatcher = FromBridgedChainMessageDispatch<
-			WithMillauMessageBridge,
-			XcmExecutor,
-			XcmWeigher,
-			WeightCredit,
-		>;
-
 		new_test_ext().execute_with(|| {
-			let location: MultiLocation =
-				(Parent, X1(GlobalConsensus(MillauNetwork::get()))).into();
-			let xcm: Xcm<RuntimeCall> = vec![Instruction::Trap(42)].into();
+			let mut incoming_message = prepare_inbound_bridge_message();
 
-			let mut incoming_message = DispatchMessage {
-				key: MessageKey { lane_id: LaneId([0, 0, 0, 0]), nonce: 1 },
-				data: DispatchMessageData { payload: Ok((location, xcm).into()) },
-			};
-
-			let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message);
-			assert_eq!(dispatch_weight, UnitWeightCost::get());
+			let dispatch_weight = FromMillauMessageDispatch::dispatch_weight(&mut incoming_message);
+			assert_eq!(dispatch_weight, XcmRouterWeigher::<()>::get());
 
+			// we care only about handing message to the XCM dispatcher, so we don't care about its
+			// actual dispatch
 			let dispatch_result =
-				MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message);
-			assert_eq!(
-				dispatch_result,
-				MessageDispatchResult {
-					unspent_weight: frame_support::weights::Weight::zero(),
-					dispatch_level_result: (),
-				}
-			);
-		})
+				FromMillauMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
+			assert!(matches!(
+				dispatch_result.dispatch_level_result,
+				XcmBlobMessageDispatchResult::NotDispatched(_),
+			));
+		});
 	}
 
 	#[test]
diff --git a/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs b/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs
index ee7a089992e..3fff141bf84 100644
--- a/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs
+++ b/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs
@@ -19,14 +19,18 @@
 // TODO: this is almost exact copy of `millau_messages.rs` from Rialto runtime.
 // Should be extracted to a separate crate and reused here.
 
-use crate::{MillauGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin};
-
-use bp_messages::{LaneId, MessageNonce};
-use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID};
-use bridge_runtime_common::messages::{
-	self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+use crate::{MillauGrandpaInstance, Runtime, RuntimeOrigin, WithMillauMessagesInstance};
+
+use bp_messages::LaneId;
+use bridge_runtime_common::{
+	messages::{
+		self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+	},
+	messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter},
 };
 use frame_support::{parameter_types, weights::Weight, RuntimeDebug};
+use xcm::latest::prelude::*;
+use xcm_builder::HaulBlobExporter;
 
 /// Default lane that is used to send messages to Millau.
 pub const XCM_LANE: LaneId = LaneId([0, 0, 0, 0]);
@@ -51,15 +55,16 @@ pub type ToMillauMessageVerifier =
 	messages::source::FromThisChainMessageVerifier<WithMillauMessageBridge>;
 
 /// Message payload for Millau -> RialtoParachain messages.
-pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload<RuntimeCall>;
+pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload;
 
 /// Call-dispatch based message dispatch for Millau -> RialtoParachain messages.
-pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDispatch<
-	WithMillauMessageBridge,
-	xcm_executor::XcmExecutor<crate::XcmConfig>,
-	crate::XcmWeigher,
-	WeightCredit,
->;
+pub type FromMillauMessageDispatch =
+	bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch<
+		bp_rialto_parachain::RialtoParachain,
+		bp_millau::Millau,
+		crate::OnRialtoParachainBlobDispatcher,
+		bridge_runtime_common::messages_xcm_extension::XcmRouterWeigher<()>,
+	>;
 
 /// Messages proof for Millau -> RialtoParachain messages.
 pub type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof<bp_millau::Hash>;
@@ -77,8 +82,6 @@ pub type ToMillauMaximalOutboundPayloadSize =
 pub struct WithMillauMessageBridge;
 
 impl MessageBridge for WithMillauMessageBridge {
-	const THIS_CHAIN_ID: ChainId = RIALTO_PARACHAIN_CHAIN_ID;
-	const BRIDGED_CHAIN_ID: ChainId = MILLAU_CHAIN_ID;
 	const BRIDGED_MESSAGES_PALLET_NAME: &'static str =
 		bp_rialto_parachain::WITH_RIALTO_PARACHAIN_MESSAGES_PALLET_NAME;
 
@@ -97,16 +100,7 @@ impl messages::UnderlyingChainProvider for RialtoParachain {
 }
 
 impl messages::ThisChainWithMessages for RialtoParachain {
-	type RuntimeCall = RuntimeCall;
 	type RuntimeOrigin = RuntimeOrigin;
-
-	fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool {
-		true
-	}
-
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce {
-		MessageNonce::MAX
-	}
 }
 
 /// Millau chain from message lane point of view.
@@ -121,8 +115,24 @@ impl messages::UnderlyingChainProvider for Millau {
 	type Chain = bp_millau::Millau;
 }
 
-impl messages::BridgedChainWithMessages for Millau {
-	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
-		true
+impl messages::BridgedChainWithMessages for Millau {}
+
+/// Export XCM messages to be relayed to Millau.
+pub type ToMillauBlobExporter =
+	HaulBlobExporter<XcmBlobHaulerAdapter<ToMillauXcmBlobHauler>, crate::MillauNetwork, ()>;
+
+/// To-Millau XCM hauler.
+pub struct ToMillauXcmBlobHauler;
+
+impl XcmBlobHauler for ToMillauXcmBlobHauler {
+	type MessageSender = pallet_bridge_messages::Pallet<Runtime, WithMillauMessagesInstance>;
+	type MessageSenderOrigin = RuntimeOrigin;
+
+	fn message_sender_origin() -> RuntimeOrigin {
+		pallet_xcm::Origin::from(MultiLocation::new(1, crate::UniversalLocation::get())).into()
+	}
+
+	fn xcm_lane() -> LaneId {
+		XCM_LANE
 	}
 }
diff --git a/bridges/bin/rialto/runtime/src/millau_messages.rs b/bridges/bin/rialto/runtime/src/millau_messages.rs
index ff08c08029a..9d0c4f97c89 100644
--- a/bridges/bin/rialto/runtime/src/millau_messages.rs
+++ b/bridges/bin/rialto/runtime/src/millau_messages.rs
@@ -16,14 +16,18 @@
 
 //! Everything required to serve Millau <-> Rialto messages.
 
-use crate::{MillauGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin};
-
-use bp_messages::{LaneId, MessageNonce};
-use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
-use bridge_runtime_common::messages::{
-	self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+use crate::{MillauGrandpaInstance, Runtime, RuntimeOrigin, WithMillauMessagesInstance};
+
+use bp_messages::LaneId;
+use bridge_runtime_common::{
+	messages::{
+		self, source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, MessageBridge,
+	},
+	messages_xcm_extension::{XcmBlobHauler, XcmBlobHaulerAdapter},
 };
 use frame_support::{parameter_types, weights::Weight, RuntimeDebug};
+use xcm::latest::prelude::*;
+use xcm_builder::HaulBlobExporter;
 
 /// Lane that is used for XCM messages exchange.
 pub const XCM_LANE: LaneId = LaneId([0, 0, 0, 0]);
@@ -48,15 +52,16 @@ pub type ToMillauMessageVerifier =
 	messages::source::FromThisChainMessageVerifier<WithMillauMessageBridge>;
 
 /// Message payload for Millau -> Rialto messages.
-pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload<RuntimeCall>;
+pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload;
 
 /// Call-dispatch based message dispatch for Millau -> Rialto messages.
-pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDispatch<
-	WithMillauMessageBridge,
-	xcm_executor::XcmExecutor<crate::xcm_config::XcmConfig>,
-	crate::xcm_config::XcmWeigher,
-	WeightCredit,
->;
+pub type FromMillauMessageDispatch =
+	bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatch<
+		bp_rialto::Rialto,
+		bp_millau::Millau,
+		crate::xcm_config::OnRialtoBlobDispatcher,
+		bridge_runtime_common::messages_xcm_extension::XcmRouterWeigher<crate::DbWeight>,
+	>;
 
 /// Messages proof for Millau -> Rialto messages.
 pub type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof<bp_millau::Hash>;
@@ -74,8 +79,6 @@ pub type ToMillauMaximalOutboundPayloadSize =
 pub struct WithMillauMessageBridge;
 
 impl MessageBridge for WithMillauMessageBridge {
-	const THIS_CHAIN_ID: ChainId = RIALTO_CHAIN_ID;
-	const BRIDGED_CHAIN_ID: ChainId = MILLAU_CHAIN_ID;
 	const BRIDGED_MESSAGES_PALLET_NAME: &'static str = bp_rialto::WITH_RIALTO_MESSAGES_PALLET_NAME;
 
 	type ThisChain = Rialto;
@@ -94,15 +97,6 @@ impl messages::UnderlyingChainProvider for Rialto {
 
 impl messages::ThisChainWithMessages for Rialto {
 	type RuntimeOrigin = RuntimeOrigin;
-	type RuntimeCall = RuntimeCall;
-
-	fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool {
-		true
-	}
-
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce {
-		MessageNonce::MAX
-	}
 }
 
 /// Millau chain from message lane point of view.
@@ -117,9 +111,29 @@ impl messages::UnderlyingChainProvider for Millau {
 	type Chain = bp_millau::Millau;
 }
 
-impl messages::BridgedChainWithMessages for Millau {
-	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
-		true
+impl messages::BridgedChainWithMessages for Millau {}
+
+/// Export XCM messages to be relayed to Millau.
+pub type ToMillauBlobExporter = HaulBlobExporter<
+	XcmBlobHaulerAdapter<ToMillauXcmBlobHauler>,
+	crate::xcm_config::MillauNetwork,
+	(),
+>;
+
+/// To-Millau XCM hauler.
+pub struct ToMillauXcmBlobHauler;
+
+impl XcmBlobHauler for ToMillauXcmBlobHauler {
+	type MessageSender = pallet_bridge_messages::Pallet<Runtime, WithMillauMessagesInstance>;
+	type MessageSenderOrigin = RuntimeOrigin;
+
+	fn message_sender_origin() -> RuntimeOrigin {
+		pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get()))
+			.into()
+	}
+
+	fn xcm_lane() -> LaneId {
+		XCM_LANE
 	}
 }
 
diff --git a/bridges/bin/rialto/runtime/src/xcm_config.rs b/bridges/bin/rialto/runtime/src/xcm_config.rs
index 174ece04725..64a5a896f95 100644
--- a/bridges/bin/rialto/runtime/src/xcm_config.rs
+++ b/bridges/bin/rialto/runtime/src/xcm_config.rs
@@ -17,14 +17,11 @@
 //! XCM configurations for the Rialto runtime.
 
 use super::{
-	millau_messages::WithMillauMessageBridge, AccountId, AllPalletsWithSystem, Balances, Runtime,
-	RuntimeCall, RuntimeEvent, RuntimeOrigin, WithMillauMessagesInstance, XcmPallet,
+	millau_messages::ToMillauBlobExporter, AccountId, AllPalletsWithSystem, Balances, Runtime,
+	RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmPallet,
 };
 use bp_rialto::WeightToFee;
-use bridge_runtime_common::{
-	messages::source::{XcmBridge, XcmBridgeAdapter},
-	CustomNetworkId,
-};
+use bridge_runtime_common::CustomNetworkId;
 use frame_support::{
 	parameter_types,
 	traits::{ConstU32, Everything, Nothing},
@@ -32,9 +29,9 @@ use frame_support::{
 };
 use xcm::latest::prelude::*;
 use xcm_builder::{
-	AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom,
-	CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, MintLocation, SignedAccountId32AsNative,
-	SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
+	AccountId32Aliases, CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, MintLocation,
+	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
+	UsingComponents,
 };
 
 parameter_types! {
@@ -95,30 +92,28 @@ parameter_types! {
 	pub const MaxInstructions: u32 = 100;
 }
 
-/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our
-/// individual routers.
-pub type XcmRouter = (
-	// Router to send messages to Millau.
-	XcmBridgeAdapter<ToMillauBridge>,
-);
+/// The XCM router. We are not sending messages to sibling/parent/child chains here.
+pub type XcmRouter = ();
 
 /// The barriers one of which must be passed for an XCM message to be executed.
 pub type Barrier = (
 	// Weight that is paid for may be consumed.
 	TakeWeightCredit,
-	// If the message is one that immediately attemps to pay for execution, then allow it.
-	AllowTopLevelPaidExecutionFrom<Everything>,
-	// Expected responses are OK.
-	AllowKnownQueryResponses<XcmPallet>,
 );
 
+/// Dispatches received XCM messages from other chain.
+pub type OnRialtoBlobDispatcher = xcm_builder::BridgeBlobDispatcher<
+	crate::xcm_config::XcmRouter,
+	crate::xcm_config::UniversalLocation,
+>;
+
 /// Incoming XCM weigher type.
 pub type XcmWeigher = xcm_builder::FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
 
 pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
-	type XcmSender = XcmRouter;
+	type XcmSender = ();
 	type AssetTransactor = LocalAssetTransactor;
 	type OriginConverter = LocalOriginConverter;
 	type IsReserve = ();
@@ -137,7 +132,7 @@ impl xcm_executor::Config for XcmConfig {
 	type PalletInstancesInfo = AllPalletsWithSystem;
 	type MaxAssetsIntoHolding = ConstU32<64>;
 	type FeeManager = ();
-	type MessageExporter = ();
+	type MessageExporter = ToMillauBlobExporter;
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
 	type SafeCallFilter = Everything;
@@ -152,7 +147,7 @@ pub type LocalOriginToLocation = (
 
 #[cfg(feature = "runtime-benchmarks")]
 parameter_types! {
-	pub ReachableDest: Option<MultiLocation> = todo!("We dont use benchmarks for pallet_xcm, so if you hit this message, you need to remove this and define value instead");
+	pub ReachableDest: Option<MultiLocation> = None;
 }
 
 impl pallet_xcm::Config for Runtime {
@@ -189,43 +184,24 @@ impl pallet_xcm::Config for Runtime {
 	type ReachableDest = ReachableDest;
 }
 
-/// With-Millau bridge.
-pub struct ToMillauBridge;
-
-impl XcmBridge for ToMillauBridge {
-	type MessageBridge = WithMillauMessageBridge;
-	type MessageSender = pallet_bridge_messages::Pallet<Runtime, WithMillauMessagesInstance>;
-
-	fn universal_location() -> InteriorMultiLocation {
-		UniversalLocation::get()
-	}
-
-	fn verify_destination(dest: &MultiLocation) -> bool {
-		matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get())
-	}
-
-	fn build_destination() -> MultiLocation {
-		let dest: InteriorMultiLocation = MillauNetwork::get().into();
-		let here = UniversalLocation::get();
-		dest.relative_to(&here)
-	}
-
-	fn xcm_lane() -> bp_messages::LaneId {
-		bp_messages::LaneId([0, 0, 0, 0])
-	}
-}
-
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::millau_messages::WeightCredit;
+	use crate::{
+		millau_messages::{FromMillauMessageDispatch, XCM_LANE},
+		DbWeight, WithMillauMessagesInstance,
+	};
 	use bp_messages::{
 		target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
 		LaneId, MessageKey,
 	};
-	use bp_runtime::messages::MessageDispatchResult;
-	use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch;
+	use bridge_runtime_common::messages_xcm_extension::{
+		XcmBlobMessageDispatchResult, XcmRouterWeigher,
+	};
 	use codec::Encode;
+	use pallet_bridge_messages::OutboundLanes;
+	use sp_core::Get;
+	use xcm_executor::XcmExecutor;
 
 	fn new_test_ext() -> sp_io::TestExternalities {
 		sp_io::TestExternalities::new(
@@ -233,53 +209,72 @@ mod tests {
 		)
 	}
 
-	#[test]
-	fn xcm_messages_to_millau_are_sent() {
-		new_test_ext().execute_with(|| {
-			// the encoded message (origin ++ xcm) is 0x010109030419A8
-			let dest = (Parent, X1(GlobalConsensus(MillauNetwork::get())));
-			let xcm: Xcm<()> = vec![Instruction::Trap(42)].into();
-
-			let send_result = send_xcm::<XcmRouter>(dest.into(), xcm);
-			let expected_fee = MultiAssets::from((Here, 1_000_000_u128));
-			let expected_hash =
-				([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256);
-			assert_eq!(send_result, Ok((expected_hash, expected_fee)),);
-		})
+	fn prepare_outbound_xcm_message(destination: NetworkId) -> Xcm<RuntimeCall> {
+		vec![ExportMessage {
+			network: destination,
+			destination: destination.into(),
+			xcm: vec![Instruction::Trap(42)].into(),
+		}]
+		.into()
 	}
 
 	#[test]
-	fn xcm_messages_from_millau_are_dispatched() {
-		type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
-		type MessageDispatcher = FromBridgedChainMessageDispatch<
-			WithMillauMessageBridge,
-			XcmExecutor,
-			XcmWeigher,
-			WeightCredit,
-		>;
-
+	fn xcm_messages_to_millau_are_sent_using_bridge_exporter() {
 		new_test_ext().execute_with(|| {
-			let location: MultiLocation =
-				(Parent, X1(GlobalConsensus(MillauNetwork::get()))).into();
-			let xcm: Xcm<RuntimeCall> = vec![Instruction::Trap(42)].into();
-
-			let mut incoming_message = DispatchMessage {
-				key: MessageKey { lane_id: LaneId([0, 0, 0, 0]), nonce: 1 },
-				data: DispatchMessageData { payload: Ok((location, xcm).into()) },
-			};
-
-			let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message);
-			assert_eq!(dispatch_weight, BaseXcmWeight::get());
+			// ensure that the there are no messages queued
+			assert_eq!(
+				OutboundLanes::<Runtime, WithMillauMessagesInstance>::get(XCM_LANE)
+					.latest_generated_nonce,
+				0,
+			);
 
-			let dispatch_result =
-				MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message);
+			// export message instruction "sends" message to Rialto
+			XcmExecutor::<XcmConfig>::execute_xcm_in_credit(
+				Here,
+				prepare_outbound_xcm_message(MillauNetwork::get()),
+				Default::default(),
+				Weight::MAX,
+				Weight::MAX,
+			)
+			.ensure_complete()
+			.expect("runtime configuration must be correct");
+
+			// ensure that the message has been queued
 			assert_eq!(
-				dispatch_result,
-				MessageDispatchResult {
-					unspent_weight: frame_support::weights::Weight::zero(),
-					dispatch_level_result: (),
-				}
+				OutboundLanes::<Runtime, WithMillauMessagesInstance>::get(XCM_LANE)
+					.latest_generated_nonce,
+				1,
 			);
 		})
 	}
+
+	fn prepare_inbound_bridge_message() -> DispatchMessage<Vec<u8>> {
+		let xcm = xcm::VersionedXcm::<RuntimeCall>::V3(vec![Instruction::Trap(42)].into());
+		let location =
+			xcm::VersionedInteriorMultiLocation::V3(X1(GlobalConsensus(ThisNetwork::get())));
+		// this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor
+		// or public fields, so just tuple
+		let bridge_message = (location, xcm).encode();
+		DispatchMessage {
+			key: MessageKey { lane_id: LaneId([0, 0, 0, 0]), nonce: 1 },
+			data: DispatchMessageData { payload: Ok(bridge_message) },
+		}
+	}
+
+	#[test]
+	fn xcm_messages_from_millau_are_dispatched() {
+		let mut incoming_message = prepare_inbound_bridge_message();
+
+		let dispatch_weight = FromMillauMessageDispatch::dispatch_weight(&mut incoming_message);
+		assert_eq!(dispatch_weight, XcmRouterWeigher::<DbWeight>::get());
+
+		// we care only about handing message to the XCM dispatcher, so we don't care about its
+		// actual dispatch
+		let dispatch_result =
+			FromMillauMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
+		assert!(matches!(
+			dispatch_result.dispatch_level_result,
+			XcmBlobMessageDispatchResult::NotDispatched(_),
+		));
+	}
 }
diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs
index 5a2c796ee43..96716fe851c 100644
--- a/bridges/bin/runtime-common/src/integrity.rs
+++ b/bridges/bin/runtime-common/src/integrity.rs
@@ -83,7 +83,7 @@ macro_rules! assert_bridge_messages_pallet_types(
 			use $crate::messages::{
 				source::{FromThisChainMessagePayload, TargetHeaderChainAdapter},
 				target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
-				AccountIdOf, BalanceOf, BridgedChain, CallOf, ThisChain,
+				AccountIdOf, BalanceOf, BridgedChain, ThisChain,
 			};
 			use pallet_bridge_messages::Config as MessagesConfig;
 			use static_assertions::assert_type_eq_all;
diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs
index 4493ac66742..9d2e5811380 100644
--- a/bridges/bin/runtime-common/src/messages.rs
+++ b/bridges/bin/runtime-common/src/messages.rs
@@ -25,28 +25,18 @@ pub use bp_runtime::{UnderlyingChainOf, UnderlyingChainProvider};
 use bp_header_chain::{HeaderChain, HeaderChainError};
 use bp_messages::{
 	source_chain::{LaneMessageVerifier, TargetHeaderChain},
-	target_chain::{
-		DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain,
-	},
+	target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
 	InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
 };
-use bp_runtime::{
-	messages::MessageDispatchResult, Chain, ChainId, RawStorageProof, Size, StorageProofChecker,
-	StorageProofError,
-};
-use codec::{Decode, DecodeLimit, Encode};
+use bp_runtime::{Chain, RawStorageProof, Size, StorageProofChecker, StorageProofError};
+use codec::{Decode, Encode};
 use frame_support::{traits::Get, weights::Weight, RuntimeDebug};
 use hash_db::Hasher;
 use scale_info::TypeInfo;
 use sp_std::{convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec};
-use xcm::latest::prelude::*;
 
 /// Bidirectional message bridge.
 pub trait MessageBridge {
-	/// Identifier of this chain.
-	const THIS_CHAIN_ID: ChainId;
-	/// Identifier of the Bridged chain.
-	const BRIDGED_CHAIN_ID: ChainId;
 	/// Name of the paired messages pallet instance at the Bridged chain.
 	///
 	/// Should be the name that is used in the `construct_runtime!()` macro.
@@ -64,24 +54,10 @@ pub trait MessageBridge {
 pub trait ThisChainWithMessages: UnderlyingChainProvider {
 	/// Call origin on the chain.
 	type RuntimeOrigin;
-	/// Call type on the chain.
-	type RuntimeCall: Encode + Decode;
-
-	/// Do we accept message sent by given origin to given lane?
-	fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool;
-
-	/// Maximal number of pending (not yet delivered) messages at This chain.
-	///
-	/// Any messages over this limit, will be rejected.
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce;
 }
 
 /// Bridged chain that has `pallet-bridge-messages` module.
-pub trait BridgedChainWithMessages: UnderlyingChainProvider {
-	/// Returns `true` if message dispatch weight is withing expected limits. `false` means
-	/// that the message is too heavy to be sent over the bridge and shall be rejected.
-	fn verify_dispatch_weight(message_payload: &[u8]) -> bool;
-}
+pub trait BridgedChainWithMessages: UnderlyingChainProvider {}
 
 /// This chain in context of message bridge.
 pub type ThisChain<B> = <B as MessageBridge>::ThisChain;
@@ -97,8 +73,6 @@ pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>;
 pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>;
 /// Type of origin that is used on the chain.
 pub type OriginOf<C> = <C as ThisChainWithMessages>::RuntimeOrigin;
-/// Type of call that is used on this chain.
-pub type CallOf<C> = <C as ThisChainWithMessages>::RuntimeCall;
 
 /// Error that happens during message verification.
 #[derive(Debug, PartialEq, Eq)]
@@ -128,7 +102,7 @@ pub mod source {
 	use super::*;
 
 	/// Message payload for This -> Bridged chain messages.
-	pub type FromThisChainMessagePayload = Vec<u8>;
+	pub type FromThisChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
 
 	/// Maximal size of outbound message payload.
 	pub struct FromThisChainMaximalOutboundPayloadSize<B>(PhantomData<B>);
@@ -186,13 +160,6 @@ pub mod source {
 	#[derive(RuntimeDebug)]
 	pub struct FromThisChainMessageVerifier<B>(PhantomData<B>);
 
-	/// The error message returned from `LaneMessageVerifier` when outbound lane is disabled.
-	pub const MESSAGE_REJECTED_BY_OUTBOUND_LANE: &str =
-		"The outbound message lane has rejected the message.";
-	/// The error message returned from `LaneMessageVerifier` when too many pending messages at the
-	/// lane.
-	pub const TOO_MANY_PENDING_MESSAGES: &str = "Too many pending messages at the lane.";
-
 	impl<B> LaneMessageVerifier<OriginOf<ThisChain<B>>, FromThisChainMessagePayload>
 		for FromThisChainMessageVerifier<B>
 	where
@@ -205,24 +172,16 @@ pub mod source {
 		type Error = &'static str;
 
 		fn verify_message(
-			submitter: &OriginOf<ThisChain<B>>,
-			lane: &LaneId,
-			lane_outbound_data: &OutboundLaneData,
+			_submitter: &OriginOf<ThisChain<B>>,
+			_lane: &LaneId,
+			_lane_outbound_data: &OutboundLaneData,
 			_payload: &FromThisChainMessagePayload,
 		) -> Result<(), Self::Error> {
-			// reject message if lane is blocked
-			if !ThisChain::<B>::is_message_accepted(submitter, lane) {
-				return Err(MESSAGE_REJECTED_BY_OUTBOUND_LANE)
-			}
-
-			// reject message if there are too many pending messages at this lane
-			let max_pending_messages = ThisChain::<B>::maximal_pending_messages_at_outbound_lane();
-			let pending_messages = lane_outbound_data
-				.latest_generated_nonce
-				.saturating_sub(lane_outbound_data.latest_received_nonce);
-			if pending_messages > max_pending_messages {
-				return Err(TOO_MANY_PENDING_MESSAGES)
-			}
+			// IMPORTANT: any error that is returned here is fatal for the bridge, because
+			// this code is executed at the bridge hub and message sender actually lives
+			// at some sibling parachain. So we are failing **after** the message has been
+			// sent and we can't report it back to sender (unless error report mechanism is
+			// embedded into message and its dispatcher).
 
 			Ok(())
 		}
@@ -263,9 +222,15 @@ pub mod source {
 	pub fn verify_chain_message<B: MessageBridge>(
 		payload: &FromThisChainMessagePayload,
 	) -> Result<(), Error> {
-		if !BridgedChain::<B>::verify_dispatch_weight(payload) {
-			return Err(Error::InvalidMessageWeight)
-		}
+		// IMPORTANT: any error that is returned here is fatal for the bridge, because
+		// this code is executed at the bridge hub and message sender actually lives
+		// at some sibling parachain. So we are failing **after** the message has been
+		// sent and we can't report it back to sender (unless error report mechanism is
+		// embedded into message and its dispatcher).
+
+		// apart from maximal message size check (see below), we should also check the message
+		// dispatch weight here. But we assume that the bridged chain will just push the message
+		// to some queue (XCMP, UMP, DMP), so the weight is constant and fits the block.
 
 		// The maximal size of extrinsic at Substrate-based chain depends on the
 		// `frame_system::Config::MaximumBlockLength` and
@@ -316,92 +281,6 @@ pub mod source {
 		)
 		.map_err(Error::HeaderChain)?
 	}
-
-	/// XCM bridge.
-	pub trait XcmBridge {
-		/// Runtime message bridge configuration.
-		type MessageBridge: MessageBridge;
-		/// Runtime message sender adapter.
-		type MessageSender: bp_messages::source_chain::MessagesBridge<
-			OriginOf<ThisChain<Self::MessageBridge>>,
-			FromThisChainMessagePayload,
-		>;
-
-		/// Our location within the Consensus Universe.
-		fn universal_location() -> InteriorMultiLocation;
-		/// Verify that the adapter is responsible for handling given XCM destination.
-		fn verify_destination(dest: &MultiLocation) -> bool;
-		/// Build route from this chain to the XCM destination.
-		fn build_destination() -> MultiLocation;
-		/// Return message lane used to deliver XCM messages.
-		fn xcm_lane() -> LaneId;
-	}
-
-	/// XCM bridge adapter for `bridge-messages` pallet.
-	pub struct XcmBridgeAdapter<T>(PhantomData<T>);
-
-	impl<T: XcmBridge> SendXcm for XcmBridgeAdapter<T>
-	where
-		BalanceOf<ThisChain<T::MessageBridge>>: Into<Fungibility>,
-		OriginOf<ThisChain<T::MessageBridge>>: From<pallet_xcm::Origin>,
-	{
-		type Ticket = FromThisChainMessagePayload;
-
-		fn validate(
-			dest: &mut Option<MultiLocation>,
-			msg: &mut Option<Xcm<()>>,
-		) -> SendResult<Self::Ticket> {
-			let d = dest.take().ok_or(SendError::MissingArgument)?;
-			if !T::verify_destination(&d) {
-				*dest = Some(d);
-				return Err(SendError::NotApplicable)
-			}
-
-			let route = T::build_destination();
-			let msg = (route, msg.take().ok_or(SendError::MissingArgument)?).encode();
-
-			// let's just take fixed (out of thin air) fee per message in our test bridges
-			// (this code won't be used in production anyway)
-			let fee_assets = MultiAssets::from((Here, 1_000_000_u128));
-
-			Ok((msg, fee_assets))
-		}
-
-		fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
-			use bp_messages::source_chain::MessagesBridge;
-
-			let lane = T::xcm_lane();
-			let msg = ticket;
-			let result = T::MessageSender::send_message(
-				pallet_xcm::Origin::from(MultiLocation::from(T::universal_location())).into(),
-				lane,
-				msg,
-			);
-			result
-				.map(|artifacts| {
-					let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256);
-					log::debug!(
-						target: "runtime::bridge",
-						"Sent XCM message {:?}/{} to {:?}: {:?}",
-						lane,
-						artifacts.nonce,
-						T::MessageBridge::BRIDGED_CHAIN_ID,
-						hash,
-					);
-					hash
-				})
-				.map_err(|e| {
-					log::debug!(
-						target: "runtime::bridge",
-						"Failed to send XCM message over lane {:?} to {:?}: {:?}",
-						lane,
-						T::MessageBridge::BRIDGED_CHAIN_ID,
-						e,
-					);
-					SendError::Transport("Bridge has rejected the message")
-				})
-		}
-	}
 }
 
 /// Sub-module that is declaring types required for processing Bridged -> This chain messages.
@@ -409,35 +288,7 @@ pub mod target {
 	use super::*;
 
 	/// Decoded Bridged -> This message payload.
-	#[derive(RuntimeDebug, PartialEq, Eq)]
-	pub struct FromBridgedChainMessagePayload<Call> {
-		/// Data that is actually sent over the wire.
-		pub xcm: (xcm::v3::MultiLocation, xcm::v3::Xcm<Call>),
-		/// Weight of the message, computed by the weigher. Unknown initially.
-		pub weight: Option<Weight>,
-	}
-
-	impl<Call: Decode> Decode for FromBridgedChainMessagePayload<Call> {
-		fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
-			let _: codec::Compact<u32> = Decode::decode(input)?;
-			type XcmPairType<Call> = (xcm::v3::MultiLocation, xcm::v3::Xcm<Call>);
-			Ok(FromBridgedChainMessagePayload {
-				xcm: XcmPairType::<Call>::decode_with_depth_limit(
-					sp_api::MAX_EXTRINSIC_DEPTH,
-					input,
-				)?,
-				weight: None,
-			})
-		}
-	}
-
-	impl<Call> From<(xcm::v3::MultiLocation, xcm::v3::Xcm<Call>)>
-		for FromBridgedChainMessagePayload<Call>
-	{
-		fn from(xcm: (xcm::v3::MultiLocation, xcm::v3::Xcm<Call>)) -> Self {
-			FromBridgedChainMessagePayload { xcm, weight: None }
-		}
-	}
+	pub type FromBridgedChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
 
 	/// Messages proof from bridged chain:
 	///
@@ -470,118 +321,6 @@ pub mod target {
 		}
 	}
 
-	/// Dispatching Bridged -> This chain messages.
-	#[derive(RuntimeDebug, Clone, Copy)]
-	pub struct FromBridgedChainMessageDispatch<B, XcmExecutor, XcmWeigher, WeightCredit> {
-		_marker: PhantomData<(B, XcmExecutor, XcmWeigher, WeightCredit)>,
-	}
-
-	impl<B: MessageBridge, XcmExecutor, XcmWeigher, WeightCredit>
-		MessageDispatch<AccountIdOf<ThisChain<B>>>
-		for FromBridgedChainMessageDispatch<B, XcmExecutor, XcmWeigher, WeightCredit>
-	where
-		XcmExecutor: xcm::v3::ExecuteXcm<CallOf<ThisChain<B>>>,
-		XcmWeigher: xcm_executor::traits::WeightBounds<CallOf<ThisChain<B>>>,
-		WeightCredit: Get<Weight>,
-	{
-		type DispatchPayload = FromBridgedChainMessagePayload<CallOf<ThisChain<B>>>;
-		type DispatchLevelResult = ();
-
-		fn dispatch_weight(
-			message: &mut DispatchMessage<Self::DispatchPayload>,
-		) -> frame_support::weights::Weight {
-			match message.data.payload {
-				Ok(ref mut payload) => {
-					// I have no idea why this method takes `&mut` reference and there's nothing
-					// about that in documentation. Hope it'll only mutate iff error is returned.
-					let weight = XcmWeigher::weight(&mut payload.xcm.1);
-					let weight = weight.unwrap_or_else(|e| {
-						log::debug!(
-							target: crate::LOG_TARGET_BRIDGE_DISPATCH,
-							"Failed to compute dispatch weight of incoming XCM message {:?}/{}: {:?}",
-							message.key.lane_id,
-							message.key.nonce,
-							e,
-						);
-
-						// we shall return 0 and then the XCM executor will fail to execute XCM
-						// if we'll return something else (e.g. maximal value), the lane may stuck
-						Weight::zero()
-					});
-
-					payload.weight = Some(weight);
-					weight
-				},
-				_ => Weight::zero(),
-			}
-		}
-
-		fn dispatch(
-			_relayer_account: &AccountIdOf<ThisChain<B>>,
-			message: DispatchMessage<Self::DispatchPayload>,
-		) -> MessageDispatchResult<Self::DispatchLevelResult> {
-			let message_id = (message.key.lane_id, message.key.nonce);
-			let do_dispatch = move || -> sp_std::result::Result<Outcome, codec::Error> {
-				let FromBridgedChainMessagePayload { xcm: (location, xcm), weight: weight_limit } =
-					message.data.payload?;
-				log::trace!(
-					target: crate::LOG_TARGET_BRIDGE_DISPATCH,
-					"Going to execute message {:?} (weight limit: {:?}): {:?} {:?}",
-					message_id,
-					weight_limit,
-					location,
-					xcm,
-				);
-				let hash = message_id.using_encoded(sp_io::hashing::blake2_256);
-
-				// if this cod will end up in production, this most likely needs to be set to zero
-				let weight_credit = WeightCredit::get();
-
-				let xcm_outcome = XcmExecutor::execute_xcm_in_credit(
-					location,
-					xcm,
-					hash,
-					weight_limit.unwrap_or_else(Weight::zero),
-					weight_credit,
-				);
-				Ok(xcm_outcome)
-			};
-
-			let xcm_outcome = do_dispatch();
-			match xcm_outcome {
-				Ok(outcome) => {
-					log::trace!(
-						target: crate::LOG_TARGET_BRIDGE_DISPATCH,
-						"Incoming message {:?} dispatched with result: {:?}",
-						message_id,
-						outcome,
-					);
-					match outcome.ensure_execution() {
-						Ok(_weight) => (),
-						Err(e) => {
-							log::error!(
-								target: crate::LOG_TARGET_BRIDGE_DISPATCH,
-								"Incoming message {:?} was not dispatched, error: {:?}",
-								message_id,
-								e,
-							);
-						},
-					}
-				},
-				Err(e) => {
-					log::error!(
-						target: crate::LOG_TARGET_BRIDGE_DISPATCH,
-						"Incoming message {:?} was not dispatched, codec error: {:?}",
-						message_id,
-						e,
-					);
-				},
-			}
-
-			MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
-		}
-	}
-
 	/// Return maximal dispatch weight of the message we're able to receive.
 	pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
 		maximal_extrinsic_weight / 2
@@ -745,54 +484,6 @@ mod tests {
 	use sp_core::H256;
 	use sp_runtime::traits::Header as _;
 
-	fn test_lane_outbound_data() -> OutboundLaneData {
-		OutboundLaneData::default()
-	}
-
-	fn regular_outbound_message_payload() -> source::FromThisChainMessagePayload {
-		vec![42]
-	}
-
-	#[test]
-	fn message_is_rejected_when_sent_using_disabled_lane() {
-		assert_eq!(
-			source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
-				&frame_system::RawOrigin::Root.into(),
-				&LaneId(*b"dsbl"),
-				&test_lane_outbound_data(),
-				&regular_outbound_message_payload(),
-			),
-			Err(source::MESSAGE_REJECTED_BY_OUTBOUND_LANE)
-		);
-	}
-
-	#[test]
-	fn message_is_rejected_when_there_are_too_many_pending_messages_at_outbound_lane() {
-		assert_eq!(
-			source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
-				&frame_system::RawOrigin::Root.into(),
-				&TEST_LANE_ID,
-				&OutboundLaneData {
-					latest_received_nonce: 100,
-					latest_generated_nonce: 100 + MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE + 1,
-					..Default::default()
-				},
-				&regular_outbound_message_payload(),
-			),
-			Err(source::TOO_MANY_PENDING_MESSAGES)
-		);
-	}
-
-	#[test]
-	fn verify_chain_message_rejects_message_with_too_small_declared_weight() {
-		assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
-			42;
-			BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT -
-				1
-		])
-		.is_err());
-	}
-
 	#[test]
 	fn verify_chain_message_rejects_message_with_too_large_declared_weight() {
 		assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
diff --git a/bridges/bin/runtime-common/src/messages_xcm_extension.rs b/bridges/bin/runtime-common/src/messages_xcm_extension.rs
index 42ac67a0f26..a8ca86c85c3 100644
--- a/bridges/bin/runtime-common/src/messages_xcm_extension.rs
+++ b/bridges/bin/runtime-common/src/messages_xcm_extension.rs
@@ -28,13 +28,43 @@ use bp_messages::{
 };
 use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, Chain};
 use codec::{Decode, Encode};
-use frame_support::{dispatch::Weight, traits::Get, CloneNoBound, EqNoBound, PartialEqNoBound};
+use frame_support::{
+	dispatch::Weight, traits::Get, weights::RuntimeDbWeight, CloneNoBound, EqNoBound,
+	PartialEqNoBound,
+};
 use scale_info::TypeInfo;
+use sp_std::marker::PhantomData;
 use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob, HaulBlobError};
 
 /// Plain "XCM" payload, which we transfer through bridge
 pub type XcmAsPlainPayload = sp_std::prelude::Vec<u8>;
 
+// TODO: below are just rough estimations. Other things also happen there (including hashing and so
+// on). Shall we do some benchmarking??? TODO: add proof_size component here
+// https://github.com/paritytech/parity-bridges-common/issues/1986
+
+/// Simple weigher for incoming XCM dispatch at **bridge hubs** to use with
+/// `XcmBlobMessageDispatch`.
+///
+/// By our design, message at bridge hub is simply pushed to some other queue. This implementation
+/// is for this case only. If your runtime performs some other actions with incoming XCM messages,
+/// you shall use your own implementation.
+///
+/// If message is redirected to the relay chain, then `ParentAsUmp` is used and it roughly does
+/// 1 db read and 1 db write (in its `send_upward_message` method).
+///
+/// If message is redirected to some sibling parachain, then `XcmpQueue` is used and
+/// it roughly does 2 db reads and 2 db writes (in its `SendXcm` implementation).
+///
+/// The difference is not that big, so let's choose maximal.
+pub struct XcmRouterWeigher<T>(PhantomData<T>);
+
+impl<T: Get<RuntimeDbWeight>> Get<Weight> for XcmRouterWeigher<T> {
+	fn get() -> Weight {
+		T::get().reads_writes(2, 2)
+	}
+}
+
 /// Message dispatch result type for single message
 #[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)]
 pub enum XcmBlobMessageDispatchResult {
diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs
index 056fd3a2ccf..5c2c967c060 100644
--- a/bridges/bin/runtime-common/src/mock.rs
+++ b/bridges/bin/runtime-common/src/mock.rs
@@ -33,7 +33,7 @@ use crate::messages::{
 };
 
 use bp_header_chain::{ChainWithGrandpa, HeaderChain};
-use bp_messages::{target_chain::ForbidInboundMessages, LaneId, MessageNonce};
+use bp_messages::{target_chain::ForbidInboundMessages, LaneId};
 use bp_parachains::SingleParaStoredHeaderDataBuilder;
 use bp_runtime::{Chain, ChainId, Parachain, UnderlyingChainProvider};
 use codec::{Decode, Encode};
@@ -87,10 +87,6 @@ pub type BridgedChainHeader =
 pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 0]);
 /// Bridged chain id used in tests.
 pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
-/// Maximal number of queued messages at the test lane.
-pub const MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE: MessageNonce = 32;
-/// Minimal extrinsic weight at the `BridgedChain`.
-pub const BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT: usize = 5;
 /// Maximal extrinsic weight at the `BridgedChain`.
 pub const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: usize = 2048;
 /// Maximal extrinsic size at the `BridgedChain`.
@@ -222,7 +218,7 @@ impl pallet_bridge_messages::Config for TestRuntime {
 	type MaximalOutboundPayloadSize = FromThisChainMaximalOutboundPayloadSize<OnThisChainBridge>;
 	type OutboundPayload = FromThisChainMessagePayload;
 
-	type InboundPayload = FromBridgedChainMessagePayload<ThisChainRuntimeCall>;
+	type InboundPayload = FromBridgedChainMessagePayload;
 	type InboundRelayer = BridgedChainAccountId;
 	type DeliveryPayments = ();
 
@@ -235,8 +231,7 @@ impl pallet_bridge_messages::Config for TestRuntime {
 	>;
 
 	type SourceHeaderChain = SourceHeaderChainAdapter<OnThisChainBridge>;
-	type MessageDispatch =
-		ForbidInboundMessages<(), FromBridgedChainMessagePayload<ThisChainRuntimeCall>>;
+	type MessageDispatch = ForbidInboundMessages<(), FromBridgedChainMessagePayload>;
 	type BridgedChainId = BridgedChainId;
 }
 
@@ -253,8 +248,6 @@ impl pallet_bridge_relayers::Config for TestRuntime {
 pub struct OnThisChainBridge;
 
 impl MessageBridge for OnThisChainBridge {
-	const THIS_CHAIN_ID: ChainId = *b"this";
-	const BRIDGED_CHAIN_ID: ChainId = TEST_BRIDGED_CHAIN_ID;
 	const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
 
 	type ThisChain = ThisChain;
@@ -268,8 +261,6 @@ impl MessageBridge for OnThisChainBridge {
 pub struct OnBridgedChainBridge;
 
 impl MessageBridge for OnBridgedChainBridge {
-	const THIS_CHAIN_ID: ChainId = TEST_BRIDGED_CHAIN_ID;
-	const BRIDGED_CHAIN_ID: ChainId = *b"this";
 	const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
 
 	type ThisChain = BridgedChain;
@@ -331,22 +322,9 @@ impl UnderlyingChainProvider for ThisChain {
 
 impl ThisChainWithMessages for ThisChain {
 	type RuntimeOrigin = ThisChainCallOrigin;
-	type RuntimeCall = ThisChainRuntimeCall;
-
-	fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool {
-		*lane == TEST_LANE_ID
-	}
-
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce {
-		MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE
-	}
 }
 
-impl BridgedChainWithMessages for ThisChain {
-	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
-		unreachable!()
-	}
-}
+impl BridgedChainWithMessages for ThisChain {}
 
 /// Underlying chain of `BridgedChain`.
 pub struct BridgedUnderlyingChain;
@@ -413,20 +391,6 @@ impl UnderlyingChainProvider for BridgedChain {
 
 impl ThisChainWithMessages for BridgedChain {
 	type RuntimeOrigin = BridgedChainOrigin;
-	type RuntimeCall = BridgedChainCall;
-
-	fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool {
-		unreachable!()
-	}
-
-	fn maximal_pending_messages_at_outbound_lane() -> MessageNonce {
-		unreachable!()
-	}
 }
 
-impl BridgedChainWithMessages for BridgedChain {
-	fn verify_dispatch_weight(message_payload: &[u8]) -> bool {
-		message_payload.len() >= BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT &&
-			message_payload.len() <= BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT
-	}
-}
+impl BridgedChainWithMessages for BridgedChain {}
diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index 1d2373de6d3..d4f551ce57a 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -502,11 +502,11 @@ pub struct StrippableError<T> {
 }
 
 impl<T: Debug> From<T> for StrippableError<T> {
-	fn from(err: T) -> Self {
+	fn from(_err: T) -> Self {
 		Self {
 			_phantom_data: Default::default(),
 			#[cfg(feature = "std")]
-			message: format!("{:?}", err),
+			message: format!("{:?}", _err),
 		}
 	}
 }
diff --git a/bridges/relays/bin-substrate/src/chains/millau.rs b/bridges/relays/bin-substrate/src/chains/millau.rs
index ca9a32a48a5..44416195c6a 100644
--- a/bridges/relays/bin-substrate/src/chains/millau.rs
+++ b/bridges/relays/bin-substrate/src/chains/millau.rs
@@ -16,37 +16,18 @@
 
 //! Millau chain specification for CLI.
 
-use crate::cli::{bridge, encode_message::CliEncodeMessage, CliChain};
-use bp_rialto_parachain::RIALTO_PARACHAIN_ID;
+use crate::cli::{encode_message::CliEncodeMessage, CliChain};
 use bp_runtime::EncodedOrDecodedCall;
 use relay_millau_client::Millau;
 use relay_substrate_client::SimpleRuntimeVersion;
-use xcm::latest::prelude::*;
 
 impl CliEncodeMessage for Millau {
-	fn encode_send_xcm(
-		message: xcm::VersionedXcm<()>,
-		bridge_instance_index: u8,
+	fn encode_execute_xcm(
+		message: xcm::VersionedXcm<Self::Call>,
 	) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
-		let dest = match bridge_instance_index {
-			bridge::MILLAU_TO_RIALTO_INDEX =>
-				(Parent, X1(GlobalConsensus(millau_runtime::xcm_config::RialtoNetwork::get()))),
-			bridge::MILLAU_TO_RIALTO_PARACHAIN_INDEX => (
-				Parent,
-				X2(
-					GlobalConsensus(millau_runtime::xcm_config::RialtoNetwork::get()),
-					Parachain(RIALTO_PARACHAIN_ID),
-				),
-			),
-			_ => anyhow::bail!(
-				"Unsupported target bridge pallet with instance index: {}",
-				bridge_instance_index
-			),
-		};
-
-		Ok(millau_runtime::RuntimeCall::XcmPallet(millau_runtime::XcmCall::send {
-			dest: Box::new(dest.into()),
+		Ok(millau_runtime::RuntimeCall::XcmPallet(millau_runtime::XcmCall::execute {
 			message: Box::new(message),
+			max_weight: Self::estimate_execute_xcm_weight(),
 		})
 		.into())
 	}
diff --git a/bridges/relays/bin-substrate/src/chains/rialto.rs b/bridges/relays/bin-substrate/src/chains/rialto.rs
index e9e2d0b3eaf..34a448ae4cb 100644
--- a/bridges/relays/bin-substrate/src/chains/rialto.rs
+++ b/bridges/relays/bin-substrate/src/chains/rialto.rs
@@ -16,29 +16,18 @@
 
 //! Rialto chain specification for CLI.
 
-use crate::cli::{bridge, encode_message::CliEncodeMessage, CliChain};
+use crate::cli::{encode_message::CliEncodeMessage, CliChain};
 use bp_runtime::EncodedOrDecodedCall;
 use relay_rialto_client::Rialto;
 use relay_substrate_client::SimpleRuntimeVersion;
-use xcm::latest::prelude::*;
 
 impl CliEncodeMessage for Rialto {
-	fn encode_send_xcm(
-		message: xcm::VersionedXcm<()>,
-		bridge_instance_index: u8,
+	fn encode_execute_xcm(
+		message: xcm::VersionedXcm<Self::Call>,
 	) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
-		let dest = match bridge_instance_index {
-			bridge::RIALTO_TO_MILLAU_INDEX =>
-				(Parent, X1(GlobalConsensus(rialto_runtime::xcm_config::MillauNetwork::get()))),
-			_ => anyhow::bail!(
-				"Unsupported target bridge pallet with instance index: {}",
-				bridge_instance_index
-			),
-		};
-
-		Ok(rialto_runtime::RuntimeCall::XcmPallet(rialto_runtime::XcmCall::send {
-			dest: Box::new(dest.into()),
+		Ok(rialto_runtime::RuntimeCall::XcmPallet(rialto_runtime::XcmCall::execute {
 			message: Box::new(message),
+			max_weight: Self::estimate_execute_xcm_weight(),
 		})
 		.into())
 	}
diff --git a/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs b/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs
index 1a5ea2e784e..8ea2c1ffd43 100644
--- a/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs
+++ b/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs
@@ -16,33 +16,21 @@
 
 //! Rialto parachain specification for CLI.
 
-use crate::cli::{bridge, encode_message::CliEncodeMessage, CliChain};
+use crate::cli::{encode_message::CliEncodeMessage, CliChain};
 use bp_runtime::EncodedOrDecodedCall;
-use bridge_runtime_common::CustomNetworkId;
 use relay_rialto_parachain_client::RialtoParachain;
 use relay_substrate_client::SimpleRuntimeVersion;
-use xcm::latest::prelude::*;
 
 impl CliEncodeMessage for RialtoParachain {
-	fn encode_send_xcm(
-		message: xcm::VersionedXcm<()>,
-		bridge_instance_index: u8,
+	fn encode_execute_xcm(
+		message: xcm::VersionedXcm<Self::Call>,
 	) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
 		type RuntimeCall = relay_rialto_parachain_client::RuntimeCall;
 		type XcmCall = relay_rialto_parachain_client::runtime_types::pallet_xcm::pallet::Call;
 
-		let dest = match bridge_instance_index {
-			bridge::RIALTO_PARACHAIN_TO_MILLAU_INDEX =>
-				(Parent, X1(GlobalConsensus(CustomNetworkId::Millau.as_network_id()))),
-			_ => anyhow::bail!(
-				"Unsupported target bridge pallet with instance index: {}",
-				bridge_instance_index
-			),
-		};
-
-		let xcm_call = XcmCall::send {
-			dest: Box::new(unsafe { std::mem::transmute(xcm::VersionedMultiLocation::from(dest)) }),
+		let xcm_call = XcmCall::execute {
 			message: Box::new(unsafe { std::mem::transmute(message) }),
+			max_weight: Self::estimate_execute_xcm_weight(),
 		};
 
 		Ok(RuntimeCall::PolkadotXcm(xcm_call).into())
diff --git a/bridges/relays/bin-substrate/src/cli/bridge.rs b/bridges/relays/bin-substrate/src/cli/bridge.rs
index a3cacec3313..62c71183a48 100644
--- a/bridges/relays/bin-substrate/src/cli/bridge.rs
+++ b/bridges/relays/bin-substrate/src/cli/bridge.rs
@@ -37,28 +37,6 @@ pub enum FullBridge {
 	BridgeHubPolkadotToBridgeHubKusama,
 }
 
-impl FullBridge {
-	/// Return instance index of the bridge pallet in source runtime.
-	pub fn bridge_instance_index(&self) -> u8 {
-		match self {
-			Self::MillauToRialto => MILLAU_TO_RIALTO_INDEX,
-			Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
-			Self::MillauToRialtoParachain => MILLAU_TO_RIALTO_PARACHAIN_INDEX,
-			Self::RialtoParachainToMillau => RIALTO_PARACHAIN_TO_MILLAU_INDEX,
-			Self::BridgeHubRococoToBridgeHubWococo |
-			Self::BridgeHubWococoToBridgeHubRococo |
-			Self::BridgeHubKusamaToBridgeHubPolkadot |
-			Self::BridgeHubPolkadotToBridgeHubKusama =>
-				unimplemented!("Relay doesn't support send-message subcommand on bridge hubs"),
-		}
-	}
-}
-
-pub const RIALTO_TO_MILLAU_INDEX: u8 = 0;
-pub const MILLAU_TO_RIALTO_INDEX: u8 = 0;
-pub const MILLAU_TO_RIALTO_PARACHAIN_INDEX: u8 = 1;
-pub const RIALTO_PARACHAIN_TO_MILLAU_INDEX: u8 = 0;
-
 /// Minimal bridge representation that can be used from the CLI.
 /// It connects a source chain to a target chain.
 pub trait CliBridgeBase: Sized {
diff --git a/bridges/relays/bin-substrate/src/cli/encode_message.rs b/bridges/relays/bin-substrate/src/cli/encode_message.rs
index c7ca5d51f79..9abf8b2df6d 100644
--- a/bridges/relays/bin-substrate/src/cli/encode_message.rs
+++ b/bridges/relays/bin-substrate/src/cli/encode_message.rs
@@ -17,6 +17,7 @@
 use crate::cli::{ExplicitOrMaximal, HexBytes};
 use bp_runtime::EncodedOrDecodedCall;
 use codec::Encode;
+use frame_support::weights::Weight;
 use relay_substrate_client::Chain;
 use structopt::StructOpt;
 
@@ -42,11 +43,16 @@ pub enum Message {
 pub type RawMessage = Vec<u8>;
 
 pub trait CliEncodeMessage: Chain {
-	/// Encode a send XCM call of the XCM pallet.
-	fn encode_send_xcm(
-		message: xcm::VersionedXcm<()>,
-		bridge_instance_index: u8,
+	/// Encode an `execute` XCM call of the XCM pallet.
+	fn encode_execute_xcm(
+		message: xcm::VersionedXcm<Self::Call>,
 	) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>>;
+
+	/// Estimate value of `max_weight` argument for the `execute` XCM call of the XCM pallet.
+	fn estimate_execute_xcm_weight() -> Weight {
+		// we are only executing XCM on our testnets and 1/100 of max extrinsic weight is ok
+		Self::max_extrinsic_weight() / 100
+	}
 }
 
 /// Encode message payload passed through CLI flags.
@@ -125,7 +131,7 @@ mod tests {
 		.unwrap();
 		assert_eq!(msg.len(), 100);
 		// check that it decodes to valid xcm
-		let _ = decode_xcm(msg).unwrap();
+		let _ = decode_xcm::<()>(msg).unwrap();
 	}
 
 	#[test]
@@ -140,6 +146,6 @@ mod tests {
 				.unwrap();
 		assert_eq!(msg.len(), maximal_size as usize);
 		// check that it decodes to valid xcm
-		let _ = decode_xcm(msg).unwrap();
+		let _ = decode_xcm::<()>(msg).unwrap();
 	}
 }
diff --git a/bridges/relays/bin-substrate/src/cli/send_message.rs b/bridges/relays/bin-substrate/src/cli/send_message.rs
index 09d7f72b7f4..8f76e501f69 100644
--- a/bridges/relays/bin-substrate/src/cli/send_message.rs
+++ b/bridges/relays/bin-substrate/src/cli/send_message.rs
@@ -75,10 +75,7 @@ where
 		let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
 
 		let payload_len = payload.encoded_size();
-		let send_message_call = Self::Source::encode_send_xcm(
-			decode_xcm(payload)?,
-			data.bridge.bridge_instance_index(),
-		)?;
+		let send_message_call = Self::Source::encode_execute_xcm(decode_xcm(payload)?)?;
 
 		source_client
 			.submit_signed_extrinsic(&source_sign, move |_, transaction_nonce| {
@@ -130,7 +127,7 @@ impl SendMessage {
 }
 
 /// Decode SCALE encoded raw XCM message.
-pub(crate) fn decode_xcm(message: RawMessage) -> anyhow::Result<xcm::VersionedXcm<()>> {
+pub(crate) fn decode_xcm<Call>(message: RawMessage) -> anyhow::Result<xcm::VersionedXcm<Call>> {
 	Decode::decode(&mut &message[..])
 		.map_err(|e| anyhow::format_err!("Failed to decode XCM program: {:?}", e))
 }
-- 
GitLab