diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs
index e8f8fc1f7f03805ce24b8be1a9f19e937bd8b963..173a501032f6ec08d16d4aefd48089b262f58449 100644
--- a/bridges/bin/runtime-common/src/messages.rs
+++ b/bridges/bin/runtime-common/src/messages.rs
@@ -860,7 +860,7 @@ pub mod target {
 			return Err(MessageProofError::Empty)
 		}
 
-		// We only support single lane messages in this schema
+		// We only support single lane messages in this generated_schema
 		let mut proved_messages = ProvedMessages::new();
 		proved_messages.insert(lane, proved_lane_messages);
 
diff --git a/bridges/relays/bin-substrate/src/cli/bridge.rs b/bridges/relays/bin-substrate/src/cli/bridge.rs
index 3ef324265c3c2af373d48ea0d07bfe8933d2d55f..e0d65e3caf9b1df4bb51d5a86e79001754bdad55 100644
--- a/bridges/relays/bin-substrate/src/cli/bridge.rs
+++ b/bridges/relays/bin-substrate/src/cli/bridge.rs
@@ -15,10 +15,14 @@
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
 use crate::cli::CliChain;
-use relay_substrate_client::{AccountKeyPairOf, Chain, TransactionSignScheme};
+use messages_relay::relay_strategy::MixStrategy;
+use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
+use parachains_relay::ParachainsPipeline;
+use relay_substrate_client::{AccountKeyPairOf, Chain, RelayChain, TransactionSignScheme};
 use strum::{EnumString, EnumVariantNames};
 use substrate_relay_helper::{
 	finality::SubstrateFinalitySyncPipeline, messages_lane::SubstrateMessageLane,
+	parachains::SubstrateParachainsPipeline,
 };
 
 #[derive(Debug, PartialEq, Eq, EnumString, EnumVariantNames)]
@@ -59,8 +63,9 @@ pub trait CliBridgeBase: Sized {
 		+ CliChain<KeyPair = AccountKeyPairOf<Self::Target>>;
 }
 
-/// Bridge representation that can be used from the CLI for relaying headers.
-pub trait HeadersCliBridge: CliBridgeBase {
+/// Bridge representation that can be used from the CLI for relaying headers
+/// from a relay chain to a relay chain.
+pub trait RelayToRelayHeadersCliBridge: CliBridgeBase {
 	/// Finality proofs synchronization pipeline.
 	type Finality: SubstrateFinalitySyncPipeline<
 		SourceChain = Self::Source,
@@ -69,6 +74,29 @@ pub trait HeadersCliBridge: CliBridgeBase {
 	>;
 }
 
+/// Bridge representation that can be used from the CLI for relaying headers
+/// from a parachain to a relay chain.
+pub trait ParachainToRelayHeadersCliBridge: CliBridgeBase {
+	// The `CliBridgeBase` type represents the parachain in this situation.
+	// We need to add an extra type for the relay chain.
+	type SourceRelay: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
+		+ CliChain
+		+ RelayChain;
+	/// Finality proofs synchronization pipeline (source parachain -> target).
+	type ParachainFinality: SubstrateParachainsPipeline<
+			SourceRelayChain = Self::SourceRelay,
+			SourceParachain = Self::Source,
+			TargetChain = Self::Target,
+			TransactionSignScheme = Self::Target,
+		> + ParachainsPipeline<SourceChain = Self::SourceRelay, TargetChain = Self::Target>;
+	/// Finality proofs synchronization pipeline (source relay chain -> target).
+	type RelayFinality: SubstrateFinalitySyncPipeline<
+		SourceChain = Self::SourceRelay,
+		TargetChain = Self::Target,
+		TransactionSignScheme = Self::Target,
+	>;
+}
+
 /// Bridge representation that can be used from the CLI for relaying messages.
 pub trait MessagesCliBridge: CliBridgeBase {
 	/// Name of the runtime method used to estimate the message dispatch and delivery fee for the
@@ -80,6 +108,7 @@ pub trait MessagesCliBridge: CliBridgeBase {
 		TargetChain = Self::Target,
 		SourceTransactionSignScheme = Self::Source,
 		TargetTransactionSignScheme = Self::Target,
+		RelayStrategy = MixStrategy,
 	>;
 }
 
@@ -91,7 +120,7 @@ impl CliBridgeBase for MillauToRialtoCliBridge {
 	type Target = relay_rialto_client::Rialto;
 }
 
-impl HeadersCliBridge for MillauToRialtoCliBridge {
+impl RelayToRelayHeadersCliBridge for MillauToRialtoCliBridge {
 	type Finality = crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
 }
 
@@ -109,7 +138,7 @@ impl CliBridgeBase for RialtoToMillauCliBridge {
 	type Target = relay_millau_client::Millau;
 }
 
-impl HeadersCliBridge for RialtoToMillauCliBridge {
+impl RelayToRelayHeadersCliBridge for RialtoToMillauCliBridge {
 	type Finality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
 }
 
@@ -127,7 +156,7 @@ impl CliBridgeBase for WestendToMillauCliBridge {
 	type Target = relay_millau_client::Millau;
 }
 
-impl HeadersCliBridge for WestendToMillauCliBridge {
+impl RelayToRelayHeadersCliBridge for WestendToMillauCliBridge {
 	type Finality = crate::chains::westend_headers_to_millau::WestendFinalityToMillau;
 }
 
@@ -139,7 +168,7 @@ impl CliBridgeBase for MillauToRialtoParachainCliBridge {
 	type Target = relay_rialto_parachain_client::RialtoParachain;
 }
 
-impl HeadersCliBridge for MillauToRialtoParachainCliBridge {
+impl RelayToRelayHeadersCliBridge for MillauToRialtoParachainCliBridge {
 	type Finality =
 		crate::chains::millau_headers_to_rialto_parachain::MillauFinalityToRialtoParachain;
 }
@@ -159,6 +188,12 @@ impl CliBridgeBase for RialtoParachainToMillauCliBridge {
 	type Target = relay_millau_client::Millau;
 }
 
+impl ParachainToRelayHeadersCliBridge for RialtoParachainToMillauCliBridge {
+	type SourceRelay = relay_rialto_client::Rialto;
+	type ParachainFinality = crate::chains::rialto_parachains_to_millau::RialtoParachainsToMillau;
+	type RelayFinality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
+}
+
 impl MessagesCliBridge for RialtoParachainToMillauCliBridge {
 	const ESTIMATE_MESSAGE_FEE_METHOD: &'static str =
 		bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD;
@@ -169,6 +204,12 @@ impl MessagesCliBridge for RialtoParachainToMillauCliBridge {
 //// `WestendParachain` to `Millau` bridge definition.
 pub struct WestmintToMillauCliBridge {}
 
+impl ParachainToRelayHeadersCliBridge for WestmintToMillauCliBridge {
+	type SourceRelay = relay_westend_client::Westend;
+	type ParachainFinality = crate::chains::westend_parachains_to_millau::WestendParachainsToMillau;
+	type RelayFinality = crate::chains::westend_headers_to_millau::WestendFinalityToMillau;
+}
+
 impl CliBridgeBase for WestmintToMillauCliBridge {
 	type Source = relay_westend_client::Westmint;
 	type Target = relay_millau_client::Millau;
diff --git a/bridges/relays/bin-substrate/src/cli/chain_schema.rs b/bridges/relays/bin-substrate/src/cli/chain_schema.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f6edd26db7a672ceb0cc0ecb0388d1f62d081ae2
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/cli/chain_schema.rs
@@ -0,0 +1,409 @@
+// Copyright 2019-2022 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+use sp_core::Pair;
+use structopt::StructOpt;
+use strum::{EnumString, EnumVariantNames};
+
+use crate::cli::CliChain;
+pub use relay_substrate_client::ChainRuntimeVersion;
+use substrate_relay_helper::TransactionParams;
+
+#[doc = "Runtime version params."]
+#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, EnumVariantNames)]
+pub enum RuntimeVersionType {
+	/// Auto query version from chain
+	Auto,
+	/// Custom `spec_version` and `transaction_version`
+	Custom,
+	/// Read version from bundle dependencies directly.
+	Bundle,
+}
+
+/// Create chain-specific set of runtime version parameters.
+#[macro_export]
+macro_rules! declare_chain_runtime_version_params_cli_schema {
+	($chain:ident, $chain_prefix:ident) => {
+		paste::item! {
+			#[doc = $chain " runtime version params."]
+			#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)]
+			pub struct [<$chain RuntimeVersionParams>] {
+				#[doc = "The type of runtime version for chain " $chain]
+				#[structopt(long, default_value = "Bundle")]
+				pub [<$chain_prefix _version_mode>]: RuntimeVersionType,
+				#[doc = "The custom sepc_version for chain " $chain]
+				#[structopt(long)]
+				pub [<$chain_prefix _spec_version>]: Option<u32>,
+				#[doc = "The custom transaction_version for chain " $chain]
+				#[structopt(long)]
+				pub [<$chain_prefix _transaction_version>]: Option<u32>,
+			}
+
+			impl [<$chain RuntimeVersionParams>] {
+				/// Converts self into `ChainRuntimeVersion`.
+				pub fn into_runtime_version(
+					self,
+					bundle_runtime_version: Option<sp_version::RuntimeVersion>,
+				) -> anyhow::Result<ChainRuntimeVersion> {
+					Ok(match self.[<$chain_prefix _version_mode>] {
+						RuntimeVersionType::Auto => ChainRuntimeVersion::Auto,
+						RuntimeVersionType::Custom => {
+							let except_spec_version = self.[<$chain_prefix _spec_version>]
+								.ok_or_else(|| anyhow::Error::msg(format!("The {}-spec-version is required when choose custom mode", stringify!($chain_prefix))))?;
+							let except_transaction_version = self.[<$chain_prefix _transaction_version>]
+								.ok_or_else(|| anyhow::Error::msg(format!("The {}-transaction-version is required when choose custom mode", stringify!($chain_prefix))))?;
+							ChainRuntimeVersion::Custom(
+								except_spec_version,
+								except_transaction_version
+							)
+						},
+						RuntimeVersionType::Bundle => match bundle_runtime_version {
+							Some(runtime_version) => ChainRuntimeVersion::Custom(
+								runtime_version.spec_version,
+								runtime_version.transaction_version
+							),
+							None => ChainRuntimeVersion::Auto
+						},
+					})
+				}
+			}
+		}
+	};
+}
+
+/// Create chain-specific set of runtime version parameters.
+#[macro_export]
+macro_rules! declare_chain_connection_params_cli_schema {
+	($chain:ident, $chain_prefix:ident) => {
+		paste::item! {
+			#[doc = $chain " connection params."]
+			#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
+			pub struct [<$chain ConnectionParams>] {
+				#[doc = "Connect to " $chain " node at given host."]
+				#[structopt(long, default_value = "127.0.0.1")]
+				pub [<$chain_prefix _host>]: String,
+				#[doc = "Connect to " $chain " node websocket server at given port."]
+				#[structopt(long, default_value = "9944")]
+				pub [<$chain_prefix _port>]: u16,
+				#[doc = "Use secure websocket connection."]
+				#[structopt(long)]
+				pub [<$chain_prefix _secure>]: bool,
+				#[doc = "Custom runtime version"]
+				#[structopt(flatten)]
+				pub [<$chain_prefix _runtime_version>]: [<$chain RuntimeVersionParams>],
+			}
+
+			impl [<$chain ConnectionParams>] {
+				/// Convert connection params into Substrate client.
+				#[allow(dead_code)]
+				pub async fn into_client<Chain: CliChain>(
+					self,
+				) -> anyhow::Result<relay_substrate_client::Client<Chain>> {
+					let chain_runtime_version = self
+						.[<$chain_prefix _runtime_version>]
+						.into_runtime_version(Some(Chain::RUNTIME_VERSION))?;
+					Ok(relay_substrate_client::Client::new(relay_substrate_client::ConnectionParams {
+						host: self.[<$chain_prefix _host>],
+						port: self.[<$chain_prefix _port>],
+						secure: self.[<$chain_prefix _secure>],
+						chain_runtime_version,
+					})
+					.await
+					)
+				}
+			}
+		}
+	};
+}
+
+/// Helper trait to override transaction parameters differently.
+pub trait TransactionParamsProvider {
+	/// Returns `true` if transaction parameters are defined by this provider.
+	fn is_defined(&self) -> bool;
+	/// Returns transaction parameters.
+	fn transaction_params<Chain: CliChain>(
+		&self,
+	) -> anyhow::Result<TransactionParams<Chain::KeyPair>>;
+
+	/// Returns transaction parameters, defined by `self` provider or, if they're not defined,
+	/// defined by `other` provider.
+	fn transaction_params_or<Chain: CliChain, T: TransactionParamsProvider>(
+		&self,
+		other: &T,
+	) -> anyhow::Result<TransactionParams<Chain::KeyPair>> {
+		if self.is_defined() {
+			self.transaction_params::<Chain>()
+		} else {
+			other.transaction_params::<Chain>()
+		}
+	}
+}
+
+/// Create chain-specific set of signing parameters.
+#[macro_export]
+macro_rules! declare_chain_signing_params_cli_schema {
+	($chain:ident, $chain_prefix:ident) => {
+		paste::item! {
+			#[doc = $chain " signing params."]
+			#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
+			pub struct [<$chain SigningParams>] {
+				#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
+				#[structopt(long)]
+				pub [<$chain_prefix _signer>]: Option<String>,
+				#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
+				#[structopt(long)]
+				pub [<$chain_prefix _signer_password>]: Option<String>,
+
+				#[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."]
+				#[structopt(long)]
+				pub [<$chain_prefix _signer_file>]: Option<std::path::PathBuf>,
+				#[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."]
+				#[structopt(long)]
+				pub [<$chain_prefix _signer_password_file>]: Option<std::path::PathBuf>,
+
+				#[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."]
+				#[structopt(long)]
+				pub [<$chain_prefix _transactions_mortality>]: Option<u32>,
+			}
+
+			impl [<$chain SigningParams>] {
+				/// Return transactions mortality.
+				#[allow(dead_code)]
+				pub fn transactions_mortality(&self) -> anyhow::Result<Option<u32>> {
+					self.[<$chain_prefix _transactions_mortality>]
+						.map(|transactions_mortality| {
+							if !(4..=65536).contains(&transactions_mortality)
+								|| !transactions_mortality.is_power_of_two()
+							{
+								Err(anyhow::format_err!(
+									"Transactions mortality {} is not a power of two in a [4; 65536] range",
+									transactions_mortality,
+								))
+							} else {
+								Ok(transactions_mortality)
+							}
+						})
+						.transpose()
+				}
+
+				/// Parse signing params into chain-specific KeyPair.
+				#[allow(dead_code)]
+				pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Chain::KeyPair> {
+					let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) {
+						(Some(suri), _) => suri.to_owned(),
+						(None, Some(suri_file)) => std::fs::read_to_string(suri_file)
+							.map_err(|err| anyhow::format_err!(
+								"Failed to read SURI from file {:?}: {}",
+								suri_file,
+								err,
+							))?,
+						(None, None) => return Err(anyhow::format_err!(
+							"One of options must be specified: '{}' or '{}'",
+							stringify!([<$chain_prefix _signer>]),
+							stringify!([<$chain_prefix _signer_file>]),
+						)),
+					};
+
+					let suri_password = match (
+						self.[<$chain_prefix _signer_password>].as_ref(),
+						self.[<$chain_prefix _signer_password_file>].as_ref(),
+					) {
+						(Some(suri_password), _) => Some(suri_password.to_owned()),
+						(None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file)
+							.map(Some)
+							.map_err(|err| anyhow::format_err!(
+								"Failed to read SURI password from file {:?}: {}",
+								suri_password_file,
+								err,
+							))?,
+						_ => None,
+					};
+
+					use sp_core::crypto::Pair;
+
+					Chain::KeyPair::from_string(
+						&suri,
+						suri_password.as_deref()
+					).map_err(|e| anyhow::format_err!("{:?}", e))
+				}
+			}
+
+			#[allow(dead_code)]
+			impl TransactionParamsProvider for [<$chain SigningParams>] {
+				fn is_defined(&self) -> bool {
+					self.[<$chain_prefix _signer>].is_some() || self.[<$chain_prefix _signer_file>].is_some()
+				}
+
+				fn transaction_params<Chain: CliChain>(&self) -> anyhow::Result<TransactionParams<Chain::KeyPair>> {
+					Ok(TransactionParams {
+						mortality: self.transactions_mortality()?,
+						signer: self.to_keypair::<Chain>()?,
+					})
+				}
+			}
+		}
+	};
+}
+
+/// Create chain-specific set of messages pallet owner signing parameters.
+#[macro_export]
+macro_rules! declare_chain_messages_pallet_owner_signing_params_cli_schema {
+	($chain:ident, $chain_prefix:ident) => {
+		paste::item! {
+			#[doc = "Parameters required to sign transaction on behalf of owner of the messages pallet at " $chain "."]
+			#[derive(StructOpt, Debug, PartialEq, Eq)]
+			pub struct [<$chain MessagesPalletOwnerSigningParams>] {
+				#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
+				#[structopt(long)]
+				pub [<$chain_prefix _messages_pallet_owner>]: Option<String>,
+				#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
+				#[structopt(long)]
+				pub [<$chain_prefix _messages_pallet_owner_password>]: Option<String>,
+			}
+
+			#[allow(dead_code)]
+			impl [<$chain MessagesPalletOwnerSigningParams>] {
+				/// Parse signing params into chain-specific KeyPair.
+				pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Option<Chain::KeyPair>> {
+					let [<$chain_prefix _messages_pallet_owner>] = match self.[<$chain_prefix _messages_pallet_owner>] {
+						Some(ref messages_pallet_owner) => messages_pallet_owner,
+						None => return Ok(None),
+					};
+					Chain::KeyPair::from_string(
+						[<$chain_prefix _messages_pallet_owner>],
+						self.[<$chain_prefix _messages_pallet_owner_password>].as_deref()
+					).map_err(|e| anyhow::format_err!("{:?}", e)).map(Some)
+				}
+			}
+		}
+	};
+}
+
+/// Create chain-specific set of configuration objects: connection parameters,
+/// signing parameters and bridge initialization parameters.
+#[macro_export]
+macro_rules! declare_chain_cli_schema {
+	($chain:ident, $chain_prefix:ident) => {
+		$crate::declare_chain_runtime_version_params_cli_schema!($chain, $chain_prefix);
+		$crate::declare_chain_connection_params_cli_schema!($chain, $chain_prefix);
+		$crate::declare_chain_signing_params_cli_schema!($chain, $chain_prefix);
+		$crate::declare_chain_messages_pallet_owner_signing_params_cli_schema!(
+			$chain,
+			$chain_prefix
+		);
+	};
+}
+
+declare_chain_cli_schema!(Source, source);
+declare_chain_cli_schema!(Target, target);
+declare_chain_cli_schema!(Relaychain, relaychain);
+declare_chain_cli_schema!(Parachain, parachain);
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use sp_core::Pair;
+
+	#[test]
+	fn reads_suri_from_file() {
+		const ALICE: &str = "//Alice";
+		const BOB: &str = "//Bob";
+		const ALICE_PASSWORD: &str = "alice_password";
+		const BOB_PASSWORD: &str = "bob_password";
+
+		let alice: sp_core::sr25519::Pair = Pair::from_string(ALICE, Some(ALICE_PASSWORD)).unwrap();
+		let bob: sp_core::sr25519::Pair = Pair::from_string(BOB, Some(BOB_PASSWORD)).unwrap();
+		let bob_with_alice_password =
+			sp_core::sr25519::Pair::from_string(BOB, Some(ALICE_PASSWORD)).unwrap();
+
+		let temp_dir = tempfile::tempdir().unwrap();
+		let mut suri_file_path = temp_dir.path().to_path_buf();
+		let mut password_file_path = temp_dir.path().to_path_buf();
+		suri_file_path.push("suri");
+		password_file_path.push("password");
+		std::fs::write(&suri_file_path, BOB.as_bytes()).unwrap();
+		std::fs::write(&password_file_path, BOB_PASSWORD.as_bytes()).unwrap();
+
+		// when both seed and password are read from file
+		assert_eq!(
+			TargetSigningParams {
+				target_signer: Some(ALICE.into()),
+				target_signer_password: Some(ALICE_PASSWORD.into()),
+
+				target_signer_file: None,
+				target_signer_password_file: None,
+
+				target_transactions_mortality: None,
+			}
+			.to_keypair::<relay_rialto_client::Rialto>()
+			.map(|p| p.public())
+			.map_err(drop),
+			Ok(alice.public()),
+		);
+
+		// when both seed and password are read from file
+		assert_eq!(
+			TargetSigningParams {
+				target_signer: None,
+				target_signer_password: None,
+
+				target_signer_file: Some(suri_file_path.clone()),
+				target_signer_password_file: Some(password_file_path.clone()),
+
+				target_transactions_mortality: None,
+			}
+			.to_keypair::<relay_rialto_client::Rialto>()
+			.map(|p| p.public())
+			.map_err(drop),
+			Ok(bob.public()),
+		);
+
+		// when password are is overriden by cli option
+		assert_eq!(
+			TargetSigningParams {
+				target_signer: None,
+				target_signer_password: Some(ALICE_PASSWORD.into()),
+
+				target_signer_file: Some(suri_file_path.clone()),
+				target_signer_password_file: Some(password_file_path.clone()),
+
+				target_transactions_mortality: None,
+			}
+			.to_keypair::<relay_rialto_client::Rialto>()
+			.map(|p| p.public())
+			.map_err(drop),
+			Ok(bob_with_alice_password.public()),
+		);
+
+		// when both seed and password are overriden by cli options
+		assert_eq!(
+			TargetSigningParams {
+				target_signer: Some(ALICE.into()),
+				target_signer_password: Some(ALICE_PASSWORD.into()),
+
+				target_signer_file: Some(suri_file_path),
+				target_signer_password_file: Some(password_file_path),
+
+				target_transactions_mortality: None,
+			}
+			.to_keypair::<relay_rialto_client::Rialto>()
+			.map(|p| p.public())
+			.map_err(drop),
+			Ok(alice.public()),
+		);
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/cli/estimate_fee.rs b/bridges/relays/bin-substrate/src/cli/estimate_fee.rs
index 30f20b681d4a22430bbdda5b4e0cb15ce21ea26a..0f4fd765f4074a7ed24d4c83b24af292a7118aec 100644
--- a/bridges/relays/bin-substrate/src/cli/estimate_fee.rs
+++ b/bridges/relays/bin-substrate/src/cli/estimate_fee.rs
@@ -16,8 +16,9 @@
 
 use crate::cli::{
 	bridge::{FullBridge, MessagesCliBridge, *},
+	chain_schema::*,
 	relay_headers_and_messages::CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
-	Balance, HexBytes, HexLaneId, SourceConnectionParams,
+	Balance, HexBytes, HexLaneId,
 };
 use async_trait::async_trait;
 use bp_runtime::BalanceOf;
@@ -80,7 +81,7 @@ where
 	<Self::Source as ChainBase>::Balance: Display + Into<u128>,
 {
 	async fn estimate_fee(data: EstimateFee) -> anyhow::Result<()> {
-		let source_client = data.source.to_client::<Self::Source>().await?;
+		let source_client = data.source.into_client::<Self::Source>().await?;
 		let lane = data.lane.into();
 		let payload =
 			crate::cli::encode_message::encode_message::<Self::Source, Self::Target>(&data.payload)
@@ -239,7 +240,6 @@ async fn do_estimate_message_delivery_and_dispatch_fee<Source: Chain, P: Encode>
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::cli::{RuntimeVersionType, SourceRuntimeVersionParams};
 
 	#[test]
 	fn should_parse_cli_options() {
diff --git a/bridges/relays/bin-substrate/src/cli/init_bridge.rs b/bridges/relays/bin-substrate/src/cli/init_bridge.rs
index b3110e69ff3495a2c8f0fdfe40a88587220af373..98f30b9f7bdfc57e781506dcc8714ed10c14a55e 100644
--- a/bridges/relays/bin-substrate/src/cli/init_bridge.rs
+++ b/bridges/relays/bin-substrate/src/cli/init_bridge.rs
@@ -21,7 +21,7 @@ use crate::cli::{
 		CliBridgeBase, MillauToRialtoCliBridge, MillauToRialtoParachainCliBridge,
 		RialtoToMillauCliBridge, WestendToMillauCliBridge,
 	},
-	SourceConnectionParams, TargetConnectionParams, TargetSigningParams,
+	chain_schema::*,
 };
 use bp_runtime::Chain as ChainBase;
 use codec::Encode;
@@ -71,8 +71,8 @@ where
 
 	/// Initialize the bridge.
 	async fn init_bridge(data: InitBridge) -> anyhow::Result<()> {
-		let source_client = data.source.to_client::<Self::Source>().await?;
-		let target_client = data.target.to_client::<Self::Target>().await?;
+		let source_client = data.source.into_client::<Self::Source>().await?;
+		let target_client = data.target.into_client::<Self::Target>().await?;
 		let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
 
 		let (spec_version, transaction_version) = target_client.simple_runtime_version().await?;
diff --git a/bridges/relays/bin-substrate/src/cli/mod.rs b/bridges/relays/bin-substrate/src/cli/mod.rs
index 6f394092e14ad6afcbf51c346805398f1c765c43..e35548c347c4247762aacfe9369af49dec875684 100644
--- a/bridges/relays/bin-substrate/src/cli/mod.rs
+++ b/bridges/relays/bin-substrate/src/cli/mod.rs
@@ -19,10 +19,8 @@
 use std::convert::TryInto;
 
 use codec::{Decode, Encode};
-use relay_substrate_client::ChainRuntimeVersion;
 use structopt::{clap::arg_enum, StructOpt};
 use strum::{EnumString, EnumVariantNames};
-use substrate_relay_helper::TransactionParams;
 
 use bp_messages::LaneId;
 
@@ -31,6 +29,7 @@ pub(crate) mod encode_message;
 pub(crate) mod estimate_fee;
 pub(crate) mod send_message;
 
+mod chain_schema;
 mod init_bridge;
 mod register_parachain;
 mod relay_headers;
@@ -296,283 +295,9 @@ pub enum RuntimeVersionType {
 	Bundle,
 }
 
-/// Helper trait to override transaction parameters differently.
-pub trait TransactionParamsProvider {
-	/// Returns `true` if transaction parameters are defined by this provider.
-	fn is_defined(&self) -> bool;
-	/// Returns transaction parameters.
-	fn transaction_params<Chain: CliChain>(
-		&self,
-	) -> anyhow::Result<TransactionParams<Chain::KeyPair>>;
-
-	/// Returns transaction parameters, defined by `self` provider or, if they're not defined,
-	/// defined by `other` provider.
-	fn transaction_params_or<Chain: CliChain, T: TransactionParamsProvider>(
-		&self,
-		other: &T,
-	) -> anyhow::Result<TransactionParams<Chain::KeyPair>> {
-		if self.is_defined() {
-			self.transaction_params::<Chain>()
-		} else {
-			other.transaction_params::<Chain>()
-		}
-	}
-}
-
-/// Create chain-specific set of configuration objects: connection parameters,
-/// signing parameters and bridge initialization parameters.
-#[macro_export]
-macro_rules! declare_chain_options {
-	($chain:ident, $chain_prefix:ident) => {
-		paste::item! {
-			#[doc = $chain " connection params."]
-			#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
-			pub struct [<$chain ConnectionParams>] {
-				#[doc = "Connect to " $chain " node at given host."]
-				#[structopt(long, default_value = "127.0.0.1")]
-				pub [<$chain_prefix _host>]: String,
-				#[doc = "Connect to " $chain " node websocket server at given port."]
-				#[structopt(long, default_value = "9944")]
-				pub [<$chain_prefix _port>]: u16,
-				#[doc = "Use secure websocket connection."]
-				#[structopt(long)]
-				pub [<$chain_prefix _secure>]: bool,
-				#[doc = "Custom runtime version"]
-				#[structopt(flatten)]
-				pub [<$chain_prefix _runtime_version>]: [<$chain RuntimeVersionParams>],
-			}
-
-			#[doc = $chain " runtime version params."]
-			#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)]
-			pub struct [<$chain RuntimeVersionParams>] {
-				#[doc = "The type of runtime version for chain " $chain]
-				#[structopt(long, default_value = "Bundle")]
-				pub [<$chain_prefix _version_mode>]: RuntimeVersionType,
-				#[doc = "The custom sepc_version for chain " $chain]
-				#[structopt(long)]
-				pub [<$chain_prefix _spec_version>]: Option<u32>,
-				#[doc = "The custom transaction_version for chain " $chain]
-				#[structopt(long)]
-				pub [<$chain_prefix _transaction_version>]: Option<u32>,
-			}
-
-			#[doc = $chain " signing params."]
-			#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
-			pub struct [<$chain SigningParams>] {
-				#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
-				#[structopt(long)]
-				pub [<$chain_prefix _signer>]: Option<String>,
-				#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
-				#[structopt(long)]
-				pub [<$chain_prefix _signer_password>]: Option<String>,
-
-				#[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."]
-				#[structopt(long)]
-				pub [<$chain_prefix _signer_file>]: Option<std::path::PathBuf>,
-				#[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."]
-				#[structopt(long)]
-				pub [<$chain_prefix _signer_password_file>]: Option<std::path::PathBuf>,
-
-				#[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."]
-				#[structopt(long)]
-				pub [<$chain_prefix _transactions_mortality>]: Option<u32>,
-			}
-
-			#[doc = "Parameters required to sign transaction on behalf of owner of the messages pallet at " $chain "."]
-			#[derive(StructOpt, Debug, PartialEq, Eq)]
-			pub struct [<$chain MessagesPalletOwnerSigningParams>] {
-				#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
-				#[structopt(long)]
-				pub [<$chain_prefix _messages_pallet_owner>]: Option<String>,
-				#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
-				#[structopt(long)]
-				pub [<$chain_prefix _messages_pallet_owner_password>]: Option<String>,
-			}
-
-			impl [<$chain SigningParams>] {
-				/// Return transactions mortality.
-				#[allow(dead_code)]
-				pub fn transactions_mortality(&self) -> anyhow::Result<Option<u32>> {
-					self.[<$chain_prefix _transactions_mortality>]
-						.map(|transactions_mortality| {
-							if !(4..=65536).contains(&transactions_mortality)
-								|| !transactions_mortality.is_power_of_two()
-							{
-								Err(anyhow::format_err!(
-									"Transactions mortality {} is not a power of two in a [4; 65536] range",
-									transactions_mortality,
-								))
-							} else {
-								Ok(transactions_mortality)
-							}
-						})
-						.transpose()
-				}
-
-				/// Parse signing params into chain-specific KeyPair.
-				#[allow(dead_code)]
-				pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Chain::KeyPair> {
-					let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) {
-						(Some(suri), _) => suri.to_owned(),
-						(None, Some(suri_file)) => std::fs::read_to_string(suri_file)
-							.map_err(|err| anyhow::format_err!(
-								"Failed to read SURI from file {:?}: {}",
-								suri_file,
-								err,
-							))?,
-						(None, None) => return Err(anyhow::format_err!(
-							"One of options must be specified: '{}' or '{}'",
-							stringify!([<$chain_prefix _signer>]),
-							stringify!([<$chain_prefix _signer_file>]),
-						)),
-					};
-
-					let suri_password = match (
-						self.[<$chain_prefix _signer_password>].as_ref(),
-						self.[<$chain_prefix _signer_password_file>].as_ref(),
-					) {
-						(Some(suri_password), _) => Some(suri_password.to_owned()),
-						(None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file)
-							.map(Some)
-							.map_err(|err| anyhow::format_err!(
-								"Failed to read SURI password from file {:?}: {}",
-								suri_password_file,
-								err,
-							))?,
-						_ => None,
-					};
-
-					use sp_core::crypto::Pair;
-
-					Chain::KeyPair::from_string(
-						&suri,
-						suri_password.as_deref()
-					).map_err(|e| anyhow::format_err!("{:?}", e))
-				}
-			}
-
-			#[allow(dead_code)]
-			impl TransactionParamsProvider for [<$chain SigningParams>] {
-				fn is_defined(&self) -> bool {
-					self.[<$chain_prefix _signer>].is_some() || self.[<$chain_prefix _signer_file>].is_some()
-				}
-
-				fn transaction_params<Chain: CliChain>(&self) -> anyhow::Result<TransactionParams<Chain::KeyPair>> {
-					Ok(TransactionParams {
-						mortality: self.transactions_mortality()?,
-						signer: self.to_keypair::<Chain>()?,
-					})
-				}
-			}
-
-			#[allow(dead_code)]
-			impl [<$chain MessagesPalletOwnerSigningParams>] {
-				/// Parse signing params into chain-specific KeyPair.
-				pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Option<Chain::KeyPair>> {
-					use sp_core::crypto::Pair;
-
-					let [<$chain_prefix _messages_pallet_owner>] = match self.[<$chain_prefix _messages_pallet_owner>] {
-						Some(ref messages_pallet_owner) => messages_pallet_owner,
-						None => return Ok(None),
-					};
-					Chain::KeyPair::from_string(
-						[<$chain_prefix _messages_pallet_owner>],
-						self.[<$chain_prefix _messages_pallet_owner_password>].as_deref()
-					).map_err(|e| anyhow::format_err!("{:?}", e)).map(Some)
-				}
-			}
-
-			impl [<$chain ConnectionParams>] {
-				/// Returns `true` if version guard can be started.
-				///
-				/// There's no reason to run version guard when version mode is set to `Auto`. It can
-				/// lead to relay shutdown when chain is upgraded, even though we have explicitly
-				/// said that we don't want to shutdown.
-				#[allow(dead_code)]
-				pub fn can_start_version_guard(&self) -> bool {
-					self.[<$chain_prefix _runtime_version>].[<$chain_prefix _version_mode>] != RuntimeVersionType::Auto
-				}
-
-				/// Convert connection params into Substrate client.
-				pub async fn to_client<Chain: CliChain>(
-					&self,
-				) -> anyhow::Result<relay_substrate_client::Client<Chain>> {
-					let chain_runtime_version = self
-						.[<$chain_prefix _runtime_version>]
-						.into_runtime_version(Some(Chain::RUNTIME_VERSION))?;
-					Ok(relay_substrate_client::Client::new(relay_substrate_client::ConnectionParams {
-						host: self.[<$chain_prefix _host>].clone(),
-						port: self.[<$chain_prefix _port>],
-						secure: self.[<$chain_prefix _secure>],
-						chain_runtime_version,
-					})
-					.await
-					)
-				}
-
-				/// Return selected `chain_spec` version.
-				///
-				/// This function only connects to the node if version mode is set to `Auto`.
-				#[allow(dead_code)]
-				pub async fn selected_chain_spec_version<Chain: CliChain>(
-					&self,
-				) -> anyhow::Result<u32> {
-					let chain_runtime_version = self
-						.[<$chain_prefix _runtime_version>]
-						.into_runtime_version(Some(Chain::RUNTIME_VERSION))?;
-					Ok(match chain_runtime_version {
-						ChainRuntimeVersion::Auto => self
-							.to_client::<Chain>()
-							.await?
-							.simple_runtime_version()
-							.await?
-							.0,
-						ChainRuntimeVersion::Custom(spec_version, _) => spec_version,
-					})
-				}
-			}
-
-			impl [<$chain RuntimeVersionParams>] {
-				/// Converts self into `ChainRuntimeVersion`.
-				pub fn into_runtime_version(
-					self,
-					bundle_runtime_version: Option<sp_version::RuntimeVersion>,
-				) -> anyhow::Result<ChainRuntimeVersion> {
-					Ok(match self.[<$chain_prefix _version_mode>] {
-						RuntimeVersionType::Auto => ChainRuntimeVersion::Auto,
-						RuntimeVersionType::Custom => {
-							let except_spec_version = self.[<$chain_prefix _spec_version>]
-								.ok_or_else(|| anyhow::Error::msg(format!("The {}-spec-version is required when choose custom mode", stringify!($chain_prefix))))?;
-							let except_transaction_version = self.[<$chain_prefix _transaction_version>]
-								.ok_or_else(|| anyhow::Error::msg(format!("The {}-transaction-version is required when choose custom mode", stringify!($chain_prefix))))?;
-							ChainRuntimeVersion::Custom(
-								except_spec_version,
-								except_transaction_version
-							)
-						},
-						RuntimeVersionType::Bundle => match bundle_runtime_version {
-							Some(runtime_version) => ChainRuntimeVersion::Custom(
-								runtime_version.spec_version,
-								runtime_version.transaction_version
-							),
-							None => ChainRuntimeVersion::Auto
-						},
-					})
-				}
-			}
-		}
-	};
-}
-
-declare_chain_options!(Source, source);
-declare_chain_options!(Target, target);
-declare_chain_options!(Relaychain, relaychain);
-declare_chain_options!(Parachain, parachain);
-
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use sp_core::Pair;
 
 	#[test]
 	fn hex_bytes_display_matches_from_str_for_clap() {
@@ -586,93 +311,4 @@ mod tests {
 		// then
 		assert_eq!(hex.0, hex2.0);
 	}
-
-	#[test]
-	fn reads_suri_from_file() {
-		const ALICE: &str = "//Alice";
-		const BOB: &str = "//Bob";
-		const ALICE_PASSWORD: &str = "alice_password";
-		const BOB_PASSWORD: &str = "bob_password";
-
-		let alice = sp_core::sr25519::Pair::from_string(ALICE, Some(ALICE_PASSWORD)).unwrap();
-		let bob = sp_core::sr25519::Pair::from_string(BOB, Some(BOB_PASSWORD)).unwrap();
-		let bob_with_alice_password =
-			sp_core::sr25519::Pair::from_string(BOB, Some(ALICE_PASSWORD)).unwrap();
-
-		let temp_dir = tempfile::tempdir().unwrap();
-		let mut suri_file_path = temp_dir.path().to_path_buf();
-		let mut password_file_path = temp_dir.path().to_path_buf();
-		suri_file_path.push("suri");
-		password_file_path.push("password");
-		std::fs::write(&suri_file_path, BOB.as_bytes()).unwrap();
-		std::fs::write(&password_file_path, BOB_PASSWORD.as_bytes()).unwrap();
-
-		// when both seed and password are read from file
-		assert_eq!(
-			TargetSigningParams {
-				target_signer: Some(ALICE.into()),
-				target_signer_password: Some(ALICE_PASSWORD.into()),
-
-				target_signer_file: None,
-				target_signer_password_file: None,
-
-				target_transactions_mortality: None,
-			}
-			.to_keypair::<relay_rialto_client::Rialto>()
-			.map(|p| p.public())
-			.map_err(drop),
-			Ok(alice.public()),
-		);
-
-		// when both seed and password are read from file
-		assert_eq!(
-			TargetSigningParams {
-				target_signer: None,
-				target_signer_password: None,
-
-				target_signer_file: Some(suri_file_path.clone()),
-				target_signer_password_file: Some(password_file_path.clone()),
-
-				target_transactions_mortality: None,
-			}
-			.to_keypair::<relay_rialto_client::Rialto>()
-			.map(|p| p.public())
-			.map_err(drop),
-			Ok(bob.public()),
-		);
-
-		// when password are is overriden by cli option
-		assert_eq!(
-			TargetSigningParams {
-				target_signer: None,
-				target_signer_password: Some(ALICE_PASSWORD.into()),
-
-				target_signer_file: Some(suri_file_path.clone()),
-				target_signer_password_file: Some(password_file_path.clone()),
-
-				target_transactions_mortality: None,
-			}
-			.to_keypair::<relay_rialto_client::Rialto>()
-			.map(|p| p.public())
-			.map_err(drop),
-			Ok(bob_with_alice_password.public()),
-		);
-
-		// when both seed and password are overriden by cli options
-		assert_eq!(
-			TargetSigningParams {
-				target_signer: Some(ALICE.into()),
-				target_signer_password: Some(ALICE_PASSWORD.into()),
-
-				target_signer_file: Some(suri_file_path),
-				target_signer_password_file: Some(password_file_path),
-
-				target_transactions_mortality: None,
-			}
-			.to_keypair::<relay_rialto_client::Rialto>()
-			.map(|p| p.public())
-			.map_err(drop),
-			Ok(alice.public()),
-		);
-	}
 }
diff --git a/bridges/relays/bin-substrate/src/cli/register_parachain.rs b/bridges/relays/bin-substrate/src/cli/register_parachain.rs
index d1647812053786234e71f268162f3d5ea932d67f..9573cde30319f5135bfbefd8969374b2c588917c 100644
--- a/bridges/relays/bin-substrate/src/cli/register_parachain.rs
+++ b/bridges/relays/bin-substrate/src/cli/register_parachain.rs
@@ -14,9 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
-use crate::cli::{
-	Balance, ParachainConnectionParams, RelaychainConnectionParams, RelaychainSigningParams,
-};
+use crate::cli::{chain_schema::*, Balance};
 
 use codec::Encode;
 use frame_support::Twox64Concat;
@@ -94,9 +92,9 @@ impl RegisterParachain {
 	/// Run the command.
 	pub async fn run(self) -> anyhow::Result<()> {
 		select_bridge!(self.parachain, {
-			let relay_client = self.relay_connection.to_client::<Relaychain>().await?;
+			let relay_client = self.relay_connection.into_client::<Relaychain>().await?;
 			let relay_sign = self.relay_sign.to_keypair::<Relaychain>()?;
-			let para_client = self.para_connection.to_client::<Parachain>().await?;
+			let para_client = self.para_connection.into_client::<Parachain>().await?;
 
 			// hopefully we're the only actor that is registering parachain right now
 			// => read next parachain id
@@ -343,9 +341,6 @@ async fn wait_para_state<Relaychain: Chain>(
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::cli::{
-		ParachainRuntimeVersionParams, RelaychainRuntimeVersionParams, RuntimeVersionType,
-	};
 
 	#[test]
 	fn register_rialto_parachain() {
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers.rs b/bridges/relays/bin-substrate/src/cli/relay_headers.rs
index f8d7eeb541830e97bc579d16d58d6df462472541..59325e452e370dbc29b6dbaf8edc650bbfb08da3 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_headers.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers.rs
@@ -15,7 +15,7 @@
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
 use async_trait::async_trait;
-use relay_substrate_client::{AccountKeyPairOf, ChainBase};
+use relay_substrate_client::{AccountIdOf, AccountKeyPairOf};
 use sp_core::Pair;
 use structopt::StructOpt;
 use strum::{EnumString, EnumVariantNames, VariantNames};
@@ -23,10 +23,7 @@ use strum::{EnumString, EnumVariantNames, VariantNames};
 use relay_utils::metrics::{GlobalMetrics, StandaloneMetric};
 use substrate_relay_helper::finality::SubstrateFinalitySyncPipeline;
 
-use crate::cli::{
-	bridge::*, PrometheusParams, SourceConnectionParams, TargetConnectionParams,
-	TargetSigningParams,
-};
+use crate::cli::{bridge::*, chain_schema::*, PrometheusParams};
 
 /// Start headers relayer process.
 #[derive(StructOpt)]
@@ -59,14 +56,14 @@ pub enum RelayHeadersBridge {
 }
 
 #[async_trait]
-trait HeadersRelayer: HeadersCliBridge
+trait HeadersRelayer: RelayToRelayHeadersCliBridge
 where
-	<Self::Target as ChainBase>::AccountId: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
+	AccountIdOf<Self::Target>: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
 {
 	/// Relay headers.
 	async fn relay_headers(data: RelayHeaders) -> anyhow::Result<()> {
-		let source_client = data.source.to_client::<Self::Source>().await?;
-		let target_client = data.target.to_client::<Self::Target>().await?;
+		let source_client = data.source.into_client::<Self::Source>().await?;
+		let target_client = data.target.into_client::<Self::Target>().await?;
 		let target_transactions_mortality = data.target_sign.target_transactions_mortality;
 		let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
 
@@ -80,7 +77,7 @@ where
 		Self::Finality::start_relay_guards(
 			&target_client,
 			&target_transactions_params,
-			data.target.can_start_version_guard(),
+			target_client.can_start_version_guard(),
 		)
 		.await?;
 
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
deleted file mode 100644
index 4d6456cc6198e944e5d993502ffe9888d7625a20..0000000000000000000000000000000000000000
--- a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
+++ /dev/null
@@ -1,910 +0,0 @@
-// Copyright 2019-2021 Parity Technologies (UK) Ltd.
-// This file is part of Parity Bridges Common.
-
-// Parity Bridges Common is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Parity Bridges Common is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
-
-//! Complex headers+messages relays support.
-//!
-//! To add new complex relay between `ChainA` and `ChainB`, you must:
-//!
-//! 1) ensure that there's a `declare_chain_options!(...)` for both chains;
-//! 2) add `declare_bridge_options!(...)` for the bridge;
-//! 3) add bridge support to the `select_bridge! { ... }` macro.
-
-use futures::{FutureExt, TryFutureExt};
-use structopt::StructOpt;
-use strum::VariantNames;
-
-use async_std::sync::Arc;
-use bp_polkadot_core::parachains::ParaHash;
-use messages_relay::relay_strategy::MixStrategy;
-use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
-use relay_substrate_client::{
-	AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, ChainRuntimeVersion, Client,
-	TransactionSignScheme,
-};
-use relay_utils::metrics::MetricsParams;
-use sp_core::Pair;
-use substrate_relay_helper::{
-	finality::SubstrateFinalitySyncPipeline,
-	messages_lane::MessagesRelayParams,
-	on_demand::{
-		headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay,
-	},
-	parachains::SubstrateParachainsPipeline,
-	TaggedAccount, TransactionParams,
-};
-
-use crate::{
-	cli::{
-		relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams, RuntimeVersionType,
-		TransactionParamsProvider,
-	},
-	declare_chain_options,
-};
-
-/// Maximal allowed conversion rate error ratio (abs(real - stored) / stored) that we allow.
-///
-/// If it is zero, then transaction will be submitted every time we see difference between
-/// stored and real conversion rates. If it is large enough (e.g. > than 10 percents, which is 0.1),
-/// then rational relayers may stop relaying messages because they were submitted using
-/// lesser conversion rate.
-pub(crate) const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
-
-/// Start headers+messages relayer process.
-#[derive(Debug, PartialEq, StructOpt)]
-pub enum RelayHeadersAndMessages {
-	MillauRialto(MillauRialtoHeadersAndMessages),
-	MillauRialtoParachain(MillauRialtoParachainHeadersAndMessages),
-}
-
-/// Parameters that have the same names across all bridges.
-#[derive(Debug, PartialEq, StructOpt)]
-pub struct HeadersAndMessagesSharedParams {
-	/// Hex-encoded lane identifiers that should be served by the complex relay.
-	#[structopt(long, default_value = "00000000")]
-	lane: Vec<HexLaneId>,
-	#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
-	relayer_mode: RelayerMode,
-	/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
-	/// are relayed.
-	#[structopt(long)]
-	only_mandatory_headers: bool,
-	#[structopt(flatten)]
-	prometheus_params: PrometheusParams,
-}
-
-// The reason behind this macro is that 'normal' relays are using source and target chains
-// terminology, which is unusable for both-way relays (if you're relaying headers from Rialto to
-// Millau and from Millau to Rialto, then which chain is source?).
-macro_rules! declare_bridge_options {
-	// chain, parachain, relay-chain-of-parachain
-	($chain1:ident, $chain2:ident, $chain3:ident) => {
-		paste::item! {
-			#[doc = $chain1 ", " $chain2 " and " $chain3 " headers+parachains+messages relay params."]
-			#[derive(Debug, PartialEq, StructOpt)]
-			pub struct [<$chain1 $chain2 HeadersAndMessages>] {
-				#[structopt(flatten)]
-				shared: HeadersAndMessagesSharedParams,
-				#[structopt(flatten)]
-				left: [<$chain1 ConnectionParams>],
-				// default signer, which is always used to sign messages relay transactions on the left chain
-				#[structopt(flatten)]
-				left_sign: [<$chain1 SigningParams>],
-				// override for right_relay->left headers signer
-				#[structopt(flatten)]
-				right_relay_headers_to_left_sign_override: [<$chain3 HeadersTo $chain1 SigningParams>],
-				// override for right->left parachains signer
-				#[structopt(flatten)]
-				right_parachains_to_left_sign_override: [<$chain3 ParachainsTo $chain1 SigningParams>],
-				#[structopt(flatten)]
-				left_messages_pallet_owner: [<$chain1 MessagesPalletOwnerSigningParams>],
-				#[structopt(flatten)]
-				right: [<$chain2 ConnectionParams>],
-				// default signer, which is always used to sign messages relay transactions on the right chain
-				#[structopt(flatten)]
-				right_sign: [<$chain2 SigningParams>],
-				// override for left->right headers signer
-				#[structopt(flatten)]
-				left_headers_to_right_sign_override: [<$chain1 HeadersTo $chain2 SigningParams>],
-				#[structopt(flatten)]
-				right_messages_pallet_owner: [<$chain2 MessagesPalletOwnerSigningParams>],
-				#[structopt(flatten)]
-				right_relay: [<$chain3 ConnectionParams>],
-			}
-		}
-
-		declare_bridge_options!({ implement }, $chain1, $chain2);
-	};
-	($chain1:ident, $chain2:ident) => {
-		paste::item! {
-			#[doc = $chain1 " and " $chain2 " headers+messages relay params."]
-			#[derive(Debug, PartialEq, StructOpt)]
-			pub struct [<$chain1 $chain2 HeadersAndMessages>] {
-				#[structopt(flatten)]
-				shared: HeadersAndMessagesSharedParams,
-				// default signer, which is always used to sign messages relay transactions on the left chain
-				#[structopt(flatten)]
-				left: [<$chain1 ConnectionParams>],
-				// override for right->left headers signer
-				#[structopt(flatten)]
-				right_headers_to_left_sign_override: [<$chain2 HeadersTo $chain1 SigningParams>],
-				#[structopt(flatten)]
-				left_sign: [<$chain1 SigningParams>],
-				#[structopt(flatten)]
-				left_messages_pallet_owner: [<$chain1 MessagesPalletOwnerSigningParams>],
-				// default signer, which is always used to sign messages relay transactions on the right chain
-				#[structopt(flatten)]
-				right: [<$chain2 ConnectionParams>],
-				// override for left->right headers signer
-				#[structopt(flatten)]
-				left_headers_to_right_sign_override: [<$chain1 HeadersTo $chain2 SigningParams>],
-				#[structopt(flatten)]
-				right_sign: [<$chain2 SigningParams>],
-				#[structopt(flatten)]
-				right_messages_pallet_owner: [<$chain2 MessagesPalletOwnerSigningParams>],
-			}
-		}
-
-		declare_bridge_options!({ implement }, $chain1, $chain2);
-	};
-	({ implement }, $chain1:ident, $chain2:ident) => {
-		paste::item! {
-			impl From<RelayHeadersAndMessages> for [<$chain1 $chain2 HeadersAndMessages>] {
-				fn from(relay_params: RelayHeadersAndMessages) -> [<$chain1 $chain2 HeadersAndMessages>] {
-					match relay_params {
-						RelayHeadersAndMessages::[<$chain1 $chain2>](params) => params,
-						_ => unreachable!(),
-					}
-				}
-			}
-		}
-	};
-}
-
-macro_rules! select_bridge {
-	($bridge: expr, $generic: tt) => {
-		match $bridge {
-			RelayHeadersAndMessages::MillauRialto(_) => {
-				type Params = MillauRialtoHeadersAndMessages;
-
-				type Left = relay_millau_client::Millau;
-				type Right = relay_rialto_client::Rialto;
-
-				use crate::chains::{
-					millau_messages_to_rialto::MillauMessagesToRialto as LeftToRightMessageLane,
-					rialto_messages_to_millau::RialtoMessagesToMillau as RightToLeftMessageLane,
-				};
-
-				async fn start_on_demand_relays(
-					params: &Params,
-					left_client: Client<Left>,
-					right_client: Client<Right>,
-					at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Left>>>,
-					at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Right>>>,
-				) -> anyhow::Result<(
-					Arc<dyn OnDemandRelay<BlockNumberOf<Left>>>,
-					Arc<dyn OnDemandRelay<BlockNumberOf<Right>>>,
-				)> {
-					start_on_demand_relay_to_relay::<
-						Left,
-						Right,
-						crate::chains::millau_headers_to_rialto::MillauFinalityToRialto,
-						crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau,
-					>(
-						left_client,
-						right_client,
-						params.left_headers_to_right_sign_override.transaction_params_or::<Right, _>(&params.right_sign)?,
-						params.right_headers_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
-						params.shared.only_mandatory_headers,
-						params.shared.only_mandatory_headers,
-						params.left.can_start_version_guard(),
-						params.right.can_start_version_guard(),
-						at_left_relay_accounts,
-						at_right_relay_accounts,
-					).await
-				}
-
-				$generic
-			},
-			RelayHeadersAndMessages::MillauRialtoParachain(_) => {
-				type Params = MillauRialtoParachainHeadersAndMessages;
-
-				type Left = relay_millau_client::Millau;
-				type Right = relay_rialto_parachain_client::RialtoParachain;
-
-				use crate::chains::{
-					millau_messages_to_rialto_parachain::MillauMessagesToRialtoParachain as LeftToRightMessageLane,
-					rialto_parachain_messages_to_millau::RialtoParachainMessagesToMillau as RightToLeftMessageLane,
-				};
-
-				async fn start_on_demand_relays(
-					params: &Params,
-					left_client: Client<Left>,
-					right_client: Client<Right>,
-					at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Left>>>,
-					at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Right>>>,
-				) -> anyhow::Result<(
-					Arc<dyn OnDemandRelay<BlockNumberOf<Left>>>,
-					Arc<dyn OnDemandRelay<BlockNumberOf<Right>>>,
-				)> {
-					type RightRelayChain = relay_rialto_client::Rialto;
-					let rialto_relay_chain_client = params.right_relay.to_client::<RightRelayChain>().await?;
-
-					start_on_demand_relay_to_parachain::<
-						Left,
-						Right,
-						RightRelayChain,
-						crate::chains::millau_headers_to_rialto_parachain::MillauFinalityToRialtoParachain,
-						crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau,
-						crate::chains::rialto_parachains_to_millau::RialtoParachainsToMillau,
-					>(
-						left_client,
-						right_client,
-						rialto_relay_chain_client,
-						params.left_headers_to_right_sign_override.transaction_params_or::<Right, _>(&params.right_sign)?,
-						params.right_relay_headers_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
-						params.right_parachains_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
-						params.shared.only_mandatory_headers,
-						params.shared.only_mandatory_headers,
-						params.left.can_start_version_guard(),
-						params.right.can_start_version_guard(),
-						at_left_relay_accounts,
-						at_right_relay_accounts,
-					).await
-				}
-
-				$generic
-			},
-		}
-	};
-}
-
-// All supported chains.
-declare_chain_options!(Millau, millau);
-declare_chain_options!(Rialto, rialto);
-declare_chain_options!(RialtoParachain, rialto_parachain);
-// Means to override signers of different layer transactions.
-declare_chain_options!(MillauHeadersToRialto, millau_headers_to_rialto);
-declare_chain_options!(MillauHeadersToRialtoParachain, millau_headers_to_rialto_parachain);
-declare_chain_options!(RialtoHeadersToMillau, rialto_headers_to_millau);
-declare_chain_options!(RialtoParachainsToMillau, rialto_parachains_to_millau);
-// All supported bridges.
-declare_bridge_options!(Millau, Rialto);
-declare_bridge_options!(Millau, RialtoParachain, Rialto);
-
-impl RelayHeadersAndMessages {
-	/// Run the command.
-	pub async fn run(self) -> anyhow::Result<()> {
-		select_bridge!(self, {
-			let params: Params = self.into();
-
-			let left_client = params.left.to_client::<Left>().await?;
-			let left_transactions_mortality = params.left_sign.transactions_mortality()?;
-			let left_sign = params.left_sign.to_keypair::<Left>()?;
-			let left_messages_pallet_owner =
-				params.left_messages_pallet_owner.to_keypair::<Left>()?;
-			let right_client = params.right.to_client::<Right>().await?;
-			let right_transactions_mortality = params.right_sign.transactions_mortality()?;
-			let right_sign = params.right_sign.to_keypair::<Right>()?;
-			let right_messages_pallet_owner =
-				params.right_messages_pallet_owner.to_keypair::<Right>()?;
-
-			let lanes = params.shared.lane.clone();
-			let relayer_mode = params.shared.relayer_mode.into();
-			let relay_strategy = MixStrategy::new(relayer_mode);
-
-			// create metrics registry and register standalone metrics
-			let metrics_params: MetricsParams = params.shared.prometheus_params.clone().into();
-			let metrics_params = relay_utils::relay_metrics(metrics_params).into_params();
-			let left_to_right_metrics =
-				substrate_relay_helper::messages_metrics::standalone_metrics::<
-					LeftToRightMessageLane,
-				>(left_client.clone(), right_client.clone())?;
-			let right_to_left_metrics = left_to_right_metrics.clone().reverse();
-			let mut at_left_relay_accounts = vec![TaggedAccount::Messages {
-				id: left_sign.public().into(),
-				bridged_chain: Right::NAME.to_string(),
-			}];
-			let mut at_right_relay_accounts = vec![TaggedAccount::Messages {
-				id: right_sign.public().into(),
-				bridged_chain: Left::NAME.to_string(),
-			}];
-
-			// start conversion rate update loops for left/right chains
-			if let Some(left_messages_pallet_owner) = left_messages_pallet_owner.clone() {
-				let left_client = left_client.clone();
-				let format_err = || {
-					anyhow::format_err!(
-						"Cannon run conversion rate updater: {} -> {}",
-						Right::NAME,
-						Left::NAME
-					)
-				};
-				substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::<
-					LeftToRightMessageLane,
-					Left,
-				>(
-					left_client,
-					TransactionParams {
-						signer: left_messages_pallet_owner.clone(),
-						mortality: left_transactions_mortality,
-					},
-					left_to_right_metrics
-						.target_to_source_conversion_rate
-						.as_ref()
-						.ok_or_else(format_err)?
-						.shared_value_ref(),
-					left_to_right_metrics
-						.target_to_base_conversion_rate
-						.as_ref()
-						.ok_or_else(format_err)?
-						.shared_value_ref(),
-					left_to_right_metrics
-						.source_to_base_conversion_rate
-						.as_ref()
-						.ok_or_else(format_err)?
-						.shared_value_ref(),
-					CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
-				);
-				at_left_relay_accounts.push(TaggedAccount::MessagesPalletOwner {
-					id: left_messages_pallet_owner.public().into(),
-					bridged_chain: Right::NAME.to_string(),
-				});
-			}
-			if let Some(right_messages_pallet_owner) = right_messages_pallet_owner.clone() {
-				let right_client = right_client.clone();
-				let format_err = || {
-					anyhow::format_err!(
-						"Cannon run conversion rate updater: {} -> {}",
-						Left::NAME,
-						Right::NAME
-					)
-				};
-				substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::<
-					RightToLeftMessageLane,
-					Right,
-				>(
-					right_client,
-					TransactionParams {
-						signer: right_messages_pallet_owner.clone(),
-						mortality: right_transactions_mortality,
-					},
-					right_to_left_metrics
-						.target_to_source_conversion_rate
-						.as_ref()
-						.ok_or_else(format_err)?
-						.shared_value_ref(),
-					right_to_left_metrics
-						.target_to_base_conversion_rate
-						.as_ref()
-						.ok_or_else(format_err)?
-						.shared_value_ref(),
-					right_to_left_metrics
-						.source_to_base_conversion_rate
-						.as_ref()
-						.ok_or_else(format_err)?
-						.shared_value_ref(),
-					CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
-				);
-				at_right_relay_accounts.push(TaggedAccount::MessagesPalletOwner {
-					id: right_messages_pallet_owner.public().into(),
-					bridged_chain: Left::NAME.to_string(),
-				});
-			}
-
-			// start on-demand header relays
-			let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) =
-				start_on_demand_relays(
-					&params,
-					left_client.clone(),
-					right_client.clone(),
-					&mut at_left_relay_accounts,
-					&mut at_right_relay_accounts,
-				)
-				.await?;
-
-			// add balance-related metrics
-			let metrics_params =
-				substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
-					left_client.clone(),
-					metrics_params,
-					at_left_relay_accounts,
-				)
-				.await?;
-			let metrics_params =
-				substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
-					right_client.clone(),
-					metrics_params,
-					at_right_relay_accounts,
-				)
-				.await?;
-
-			// Need 2x capacity since we consider both directions for each lane
-			let mut message_relays = Vec::with_capacity(lanes.len() * 2);
-			for lane in lanes {
-				let lane = lane.into();
-				let left_to_right_messages = substrate_relay_helper::messages_lane::run::<
-					LeftToRightMessageLane,
-				>(MessagesRelayParams {
-					source_client: left_client.clone(),
-					source_transaction_params: TransactionParams {
-						signer: left_sign.clone(),
-						mortality: left_transactions_mortality,
-					},
-					target_client: right_client.clone(),
-					target_transaction_params: TransactionParams {
-						signer: right_sign.clone(),
-						mortality: right_transactions_mortality,
-					},
-					source_to_target_headers_relay: Some(left_to_right_on_demand_headers.clone()),
-					target_to_source_headers_relay: Some(right_to_left_on_demand_headers.clone()),
-					lane_id: lane,
-					metrics_params: metrics_params.clone().disable(),
-					standalone_metrics: Some(left_to_right_metrics.clone()),
-					relay_strategy: relay_strategy.clone(),
-				})
-				.map_err(|e| anyhow::format_err!("{}", e))
-				.boxed();
-				let right_to_left_messages = substrate_relay_helper::messages_lane::run::<
-					RightToLeftMessageLane,
-				>(MessagesRelayParams {
-					source_client: right_client.clone(),
-					source_transaction_params: TransactionParams {
-						signer: right_sign.clone(),
-						mortality: right_transactions_mortality,
-					},
-					target_client: left_client.clone(),
-					target_transaction_params: TransactionParams {
-						signer: left_sign.clone(),
-						mortality: left_transactions_mortality,
-					},
-					source_to_target_headers_relay: Some(right_to_left_on_demand_headers.clone()),
-					target_to_source_headers_relay: Some(left_to_right_on_demand_headers.clone()),
-					lane_id: lane,
-					metrics_params: metrics_params.clone().disable(),
-					standalone_metrics: Some(right_to_left_metrics.clone()),
-					relay_strategy: relay_strategy.clone(),
-				})
-				.map_err(|e| anyhow::format_err!("{}", e))
-				.boxed();
-
-				message_relays.push(left_to_right_messages);
-				message_relays.push(right_to_left_messages);
-			}
-
-			relay_utils::relay_metrics(metrics_params)
-				.expose()
-				.await
-				.map_err(|e| anyhow::format_err!("{}", e))?;
-
-			futures::future::select_all(message_relays).await.0
-		})
-	}
-}
-
-/// Start bidirectional on-demand headers <> headers relay.
-#[allow(clippy::too_many_arguments)] // TODO: https://github.com/paritytech/parity-bridges-common/issues/1415
-async fn start_on_demand_relay_to_relay<LC, RC, LR, RL>(
-	left_client: Client<LC>,
-	right_client: Client<RC>,
-	left_to_right_transaction_params: TransactionParams<AccountKeyPairOf<RC>>,
-	right_to_left_transaction_params: TransactionParams<AccountKeyPairOf<LC>>,
-	left_to_right_only_mandatory_headers: bool,
-	right_to_left_only_mandatory_headers: bool,
-	left_can_start_version_guard: bool,
-	right_can_start_version_guard: bool,
-	at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<LC>>>,
-	at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<RC>>>,
-) -> anyhow::Result<(
-	Arc<dyn OnDemandRelay<BlockNumberOf<LC>>>,
-	Arc<dyn OnDemandRelay<BlockNumberOf<RC>>>,
-)>
-where
-	LC: Chain + TransactionSignScheme<Chain = LC> + CliChain<KeyPair = AccountKeyPairOf<LC>>,
-	RC: Chain + TransactionSignScheme<Chain = RC> + CliChain<KeyPair = AccountKeyPairOf<RC>>,
-	LR: SubstrateFinalitySyncPipeline<
-		SourceChain = LC,
-		TargetChain = RC,
-		TransactionSignScheme = RC,
-	>,
-	RL: SubstrateFinalitySyncPipeline<
-		SourceChain = RC,
-		TargetChain = LC,
-		TransactionSignScheme = LC,
-	>,
-	AccountIdOf<LC>: From<<<LC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
-	AccountIdOf<RC>: From<<<RC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
-{
-	at_left_relay_accounts.push(TaggedAccount::Headers {
-		id: right_to_left_transaction_params.signer.public().into(),
-		bridged_chain: RC::NAME.to_string(),
-	});
-	at_right_relay_accounts.push(TaggedAccount::Headers {
-		id: left_to_right_transaction_params.signer.public().into(),
-		bridged_chain: LC::NAME.to_string(),
-	});
-
-	LR::start_relay_guards(
-		&right_client,
-		&left_to_right_transaction_params,
-		right_can_start_version_guard,
-	)
-	.await?;
-	RL::start_relay_guards(
-		&left_client,
-		&right_to_left_transaction_params,
-		left_can_start_version_guard,
-	)
-	.await?;
-	let left_to_right_on_demand_headers = OnDemandHeadersRelay::new::<LR>(
-		left_client.clone(),
-		right_client.clone(),
-		left_to_right_transaction_params,
-		left_to_right_only_mandatory_headers,
-	);
-	let right_to_left_on_demand_headers = OnDemandHeadersRelay::new::<RL>(
-		right_client.clone(),
-		left_client.clone(),
-		right_to_left_transaction_params,
-		right_to_left_only_mandatory_headers,
-	);
-
-	Ok((Arc::new(left_to_right_on_demand_headers), Arc::new(right_to_left_on_demand_headers)))
-}
-
-/// Start bidirectional on-demand headers <> parachains relay.
-#[allow(clippy::too_many_arguments)] // TODO: https://github.com/paritytech/parity-bridges-common/issues/1415
-async fn start_on_demand_relay_to_parachain<LC, RC, RRC, LR, RRF, RL>(
-	left_client: Client<LC>,
-	right_client: Client<RC>,
-	right_relay_client: Client<RRC>,
-	left_headers_to_right_transaction_params: TransactionParams<AccountKeyPairOf<RC>>,
-	right_headers_to_left_transaction_params: TransactionParams<AccountKeyPairOf<LC>>,
-	right_parachains_to_left_transaction_params: TransactionParams<AccountKeyPairOf<LC>>,
-	left_to_right_only_mandatory_headers: bool,
-	right_to_left_only_mandatory_headers: bool,
-	left_can_start_version_guard: bool,
-	right_can_start_version_guard: bool,
-	at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<LC>>>,
-	at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<RC>>>,
-) -> anyhow::Result<(
-	Arc<dyn OnDemandRelay<BlockNumberOf<LC>>>,
-	Arc<dyn OnDemandRelay<BlockNumberOf<RC>>>,
-)>
-where
-	LC: Chain + TransactionSignScheme<Chain = LC> + CliChain<KeyPair = AccountKeyPairOf<LC>>,
-	RC: Chain<Hash = ParaHash>
-		+ TransactionSignScheme<Chain = RC>
-		+ CliChain<KeyPair = AccountKeyPairOf<RC>>,
-	RRC: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
-		+ TransactionSignScheme<Chain = RRC>
-		+ CliChain<KeyPair = AccountKeyPairOf<RRC>>,
-	LR: SubstrateFinalitySyncPipeline<
-		SourceChain = LC,
-		TargetChain = RC,
-		TransactionSignScheme = RC,
-	>,
-	RRF: SubstrateFinalitySyncPipeline<
-		SourceChain = RRC,
-		TargetChain = LC,
-		TransactionSignScheme = LC,
-	>,
-	RL: SubstrateParachainsPipeline<
-		SourceRelayChain = RRC,
-		SourceParachain = RC,
-		TargetChain = LC,
-		TransactionSignScheme = LC,
-	>,
-	AccountIdOf<LC>: From<<<LC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
-	AccountIdOf<RC>: From<<<RC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
-{
-	at_left_relay_accounts.push(TaggedAccount::Headers {
-		id: right_headers_to_left_transaction_params.signer.public().into(),
-		bridged_chain: RRC::NAME.to_string(),
-	});
-	at_left_relay_accounts.push(TaggedAccount::Parachains {
-		id: right_parachains_to_left_transaction_params.signer.public().into(),
-		bridged_chain: RRC::NAME.to_string(),
-	});
-	at_right_relay_accounts.push(TaggedAccount::Headers {
-		id: left_headers_to_right_transaction_params.signer.public().into(),
-		bridged_chain: LC::NAME.to_string(),
-	});
-
-	LR::start_relay_guards(
-		&right_client,
-		&left_headers_to_right_transaction_params,
-		right_can_start_version_guard,
-	)
-	.await?;
-	RRF::start_relay_guards(
-		&left_client,
-		&right_headers_to_left_transaction_params,
-		left_can_start_version_guard,
-	)
-	.await?;
-	let left_to_right_on_demand_headers = OnDemandHeadersRelay::new::<LR>(
-		left_client.clone(),
-		right_client,
-		left_headers_to_right_transaction_params,
-		left_to_right_only_mandatory_headers,
-	);
-	let right_relay_to_left_on_demand_headers = OnDemandHeadersRelay::new::<RRF>(
-		right_relay_client.clone(),
-		left_client.clone(),
-		right_headers_to_left_transaction_params,
-		right_to_left_only_mandatory_headers,
-	);
-	let right_to_left_on_demand_parachains = OnDemandParachainsRelay::new::<RL>(
-		right_relay_client,
-		left_client,
-		right_parachains_to_left_transaction_params,
-		Arc::new(right_relay_to_left_on_demand_headers),
-	);
-
-	Ok((Arc::new(left_to_right_on_demand_headers), Arc::new(right_to_left_on_demand_parachains)))
-}
-
-#[cfg(test)]
-mod tests {
-	use super::*;
-
-	#[test]
-	fn should_parse_relay_to_relay_options() {
-		// when
-		let res = RelayHeadersAndMessages::from_iter(vec![
-			"relay-headers-and-messages",
-			"millau-rialto",
-			"--millau-host",
-			"millau-node-alice",
-			"--millau-port",
-			"9944",
-			"--millau-signer",
-			"//Charlie",
-			"--millau-messages-pallet-owner",
-			"//Rialto.MessagesOwner",
-			"--millau-transactions-mortality",
-			"64",
-			"--rialto-host",
-			"rialto-node-alice",
-			"--rialto-port",
-			"9944",
-			"--rialto-signer",
-			"//Charlie",
-			"--rialto-messages-pallet-owner",
-			"//Millau.MessagesOwner",
-			"--rialto-transactions-mortality",
-			"64",
-			"--lane",
-			"00000000",
-			"--lane",
-			"73776170",
-			"--prometheus-host",
-			"0.0.0.0",
-		]);
-
-		// then
-		assert_eq!(
-			res,
-			RelayHeadersAndMessages::MillauRialto(MillauRialtoHeadersAndMessages {
-				shared: HeadersAndMessagesSharedParams {
-					lane: vec![
-						HexLaneId([0x00, 0x00, 0x00, 0x00]),
-						HexLaneId([0x73, 0x77, 0x61, 0x70])
-					],
-					relayer_mode: RelayerMode::Rational,
-					only_mandatory_headers: false,
-					prometheus_params: PrometheusParams {
-						no_prometheus: false,
-						prometheus_host: "0.0.0.0".into(),
-						prometheus_port: 9616,
-					},
-				},
-				left: MillauConnectionParams {
-					millau_host: "millau-node-alice".into(),
-					millau_port: 9944,
-					millau_secure: false,
-					millau_runtime_version: MillauRuntimeVersionParams {
-						millau_version_mode: RuntimeVersionType::Bundle,
-						millau_spec_version: None,
-						millau_transaction_version: None,
-					},
-				},
-				left_sign: MillauSigningParams {
-					millau_signer: Some("//Charlie".into()),
-					millau_signer_password: None,
-					millau_signer_file: None,
-					millau_signer_password_file: None,
-					millau_transactions_mortality: Some(64),
-				},
-				left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams {
-					millau_messages_pallet_owner: Some("//Rialto.MessagesOwner".into()),
-					millau_messages_pallet_owner_password: None,
-				},
-				left_headers_to_right_sign_override: MillauHeadersToRialtoSigningParams {
-					millau_headers_to_rialto_signer: None,
-					millau_headers_to_rialto_signer_password: None,
-					millau_headers_to_rialto_signer_file: None,
-					millau_headers_to_rialto_signer_password_file: None,
-					millau_headers_to_rialto_transactions_mortality: None,
-				},
-				right: RialtoConnectionParams {
-					rialto_host: "rialto-node-alice".into(),
-					rialto_port: 9944,
-					rialto_secure: false,
-					rialto_runtime_version: RialtoRuntimeVersionParams {
-						rialto_version_mode: RuntimeVersionType::Bundle,
-						rialto_spec_version: None,
-						rialto_transaction_version: None,
-					},
-				},
-				right_sign: RialtoSigningParams {
-					rialto_signer: Some("//Charlie".into()),
-					rialto_signer_password: None,
-					rialto_signer_file: None,
-					rialto_signer_password_file: None,
-					rialto_transactions_mortality: Some(64),
-				},
-				right_messages_pallet_owner: RialtoMessagesPalletOwnerSigningParams {
-					rialto_messages_pallet_owner: Some("//Millau.MessagesOwner".into()),
-					rialto_messages_pallet_owner_password: None,
-				},
-				right_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams {
-					rialto_headers_to_millau_signer: None,
-					rialto_headers_to_millau_signer_password: None,
-					rialto_headers_to_millau_signer_file: None,
-					rialto_headers_to_millau_signer_password_file: None,
-					rialto_headers_to_millau_transactions_mortality: None,
-				},
-			}),
-		);
-	}
-
-	#[test]
-	fn should_parse_relay_to_parachain_options() {
-		// when
-		let res = RelayHeadersAndMessages::from_iter(vec![
-			"relay-headers-and-messages",
-			"millau-rialto-parachain",
-			"--millau-host",
-			"millau-node-alice",
-			"--millau-port",
-			"9944",
-			"--millau-signer",
-			"//Iden",
-			"--rialto-headers-to-millau-signer",
-			"//Ken",
-			"--millau-messages-pallet-owner",
-			"//RialtoParachain.MessagesOwner",
-			"--millau-transactions-mortality",
-			"64",
-			"--rialto-parachain-host",
-			"rialto-parachain-collator-charlie",
-			"--rialto-parachain-port",
-			"9944",
-			"--rialto-parachain-signer",
-			"//George",
-			"--rialto-parachain-messages-pallet-owner",
-			"//Millau.MessagesOwner",
-			"--rialto-parachain-transactions-mortality",
-			"64",
-			"--rialto-host",
-			"rialto-node-alice",
-			"--rialto-port",
-			"9944",
-			"--lane",
-			"00000000",
-			"--prometheus-host",
-			"0.0.0.0",
-		]);
-
-		// then
-		assert_eq!(
-			res,
-			RelayHeadersAndMessages::MillauRialtoParachain(
-				MillauRialtoParachainHeadersAndMessages {
-					shared: HeadersAndMessagesSharedParams {
-						lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])],
-						relayer_mode: RelayerMode::Rational,
-						only_mandatory_headers: false,
-						prometheus_params: PrometheusParams {
-							no_prometheus: false,
-							prometheus_host: "0.0.0.0".into(),
-							prometheus_port: 9616,
-						},
-					},
-					left: MillauConnectionParams {
-						millau_host: "millau-node-alice".into(),
-						millau_port: 9944,
-						millau_secure: false,
-						millau_runtime_version: MillauRuntimeVersionParams {
-							millau_version_mode: RuntimeVersionType::Bundle,
-							millau_spec_version: None,
-							millau_transaction_version: None,
-						},
-					},
-					left_sign: MillauSigningParams {
-						millau_signer: Some("//Iden".into()),
-						millau_signer_password: None,
-						millau_signer_file: None,
-						millau_signer_password_file: None,
-						millau_transactions_mortality: Some(64),
-					},
-					left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams {
-						millau_messages_pallet_owner: Some(
-							"//RialtoParachain.MessagesOwner".into()
-						),
-						millau_messages_pallet_owner_password: None,
-					},
-					left_headers_to_right_sign_override:
-						MillauHeadersToRialtoParachainSigningParams {
-							millau_headers_to_rialto_parachain_signer: None,
-							millau_headers_to_rialto_parachain_signer_password: None,
-							millau_headers_to_rialto_parachain_signer_file: None,
-							millau_headers_to_rialto_parachain_signer_password_file: None,
-							millau_headers_to_rialto_parachain_transactions_mortality: None,
-						},
-					right: RialtoParachainConnectionParams {
-						rialto_parachain_host: "rialto-parachain-collator-charlie".into(),
-						rialto_parachain_port: 9944,
-						rialto_parachain_secure: false,
-						rialto_parachain_runtime_version: RialtoParachainRuntimeVersionParams {
-							rialto_parachain_version_mode: RuntimeVersionType::Bundle,
-							rialto_parachain_spec_version: None,
-							rialto_parachain_transaction_version: None,
-						},
-					},
-					right_sign: RialtoParachainSigningParams {
-						rialto_parachain_signer: Some("//George".into()),
-						rialto_parachain_signer_password: None,
-						rialto_parachain_signer_file: None,
-						rialto_parachain_signer_password_file: None,
-						rialto_parachain_transactions_mortality: Some(64),
-					},
-					right_messages_pallet_owner: RialtoParachainMessagesPalletOwnerSigningParams {
-						rialto_parachain_messages_pallet_owner: Some(
-							"//Millau.MessagesOwner".into()
-						),
-						rialto_parachain_messages_pallet_owner_password: None,
-					},
-					right_relay_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams {
-						rialto_headers_to_millau_signer: Some("//Ken".into()),
-						rialto_headers_to_millau_signer_password: None,
-						rialto_headers_to_millau_signer_file: None,
-						rialto_headers_to_millau_signer_password_file: None,
-						rialto_headers_to_millau_transactions_mortality: None,
-					},
-					right_parachains_to_left_sign_override: RialtoParachainsToMillauSigningParams {
-						rialto_parachains_to_millau_signer: None,
-						rialto_parachains_to_millau_signer_password: None,
-						rialto_parachains_to_millau_signer_file: None,
-						rialto_parachains_to_millau_signer_password_file: None,
-						rialto_parachains_to_millau_transactions_mortality: None,
-					},
-					right_relay: RialtoConnectionParams {
-						rialto_host: "rialto-node-alice".into(),
-						rialto_port: 9944,
-						rialto_secure: false,
-						rialto_runtime_version: RialtoRuntimeVersionParams {
-							rialto_version_mode: RuntimeVersionType::Bundle,
-							rialto_spec_version: None,
-							rialto_transaction_version: None,
-						},
-					},
-				}
-			),
-		);
-	}
-}
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..82f55a6d6c7aafd59090f0ae87fbcd1b909f8a11
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs
@@ -0,0 +1,694 @@
+// Copyright 2019-2022 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Complex 2-ways headers+messages relays support.
+//!
+//! To add new complex relay between `ChainA` and `ChainB`, you must:
+//!
+//! 1) ensure that there's a `declare_chain_cli_schema!(...)` for both chains.
+//! 2) add `declare_chain_to_chain_bridge_schema!(...)` or
+//!    `declare_chain_to_parachain_bridge_schema` for the bridge.
+//! 3) declare a new struct for the added bridge and implement the `Full2WayBridge` trait for it.
+
+#[macro_use]
+mod relay_to_relay;
+#[macro_use]
+mod relay_to_parachain;
+
+use async_trait::async_trait;
+use std::sync::Arc;
+use structopt::StructOpt;
+use strum::VariantNames;
+
+use futures::{FutureExt, TryFutureExt};
+use relay_to_parachain::*;
+use relay_to_relay::*;
+
+use crate::{
+	cli::{
+		bridge::{
+			CliBridgeBase, MessagesCliBridge, MillauToRialtoCliBridge,
+			MillauToRialtoParachainCliBridge, ParachainToRelayHeadersCliBridge,
+			RelayToRelayHeadersCliBridge, RialtoParachainToMillauCliBridge,
+			RialtoToMillauCliBridge,
+		},
+		chain_schema::*,
+		relay_messages::RelayerMode,
+		CliChain, HexLaneId, PrometheusParams,
+	},
+	declare_chain_cli_schema,
+};
+use bp_runtime::{BalanceOf, BlockNumberOf};
+use messages_relay::relay_strategy::MixStrategy;
+use relay_substrate_client::{
+	AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, Client, TransactionSignScheme,
+};
+use relay_utils::metrics::MetricsParams;
+use sp_core::Pair;
+use substrate_relay_helper::{
+	messages_lane::MessagesRelayParams, on_demand::OnDemandRelay, TaggedAccount, TransactionParams,
+};
+
+/// Maximal allowed conversion rate error ratio (abs(real - stored) / stored) that we allow.
+///
+/// If it is zero, then transaction will be submitted every time we see difference between
+/// stored and real conversion rates. If it is large enough (e.g. > than 10 percents, which is 0.1),
+/// then rational relayers may stop relaying messages because they were submitted using
+/// lesser conversion rate.
+pub(crate) const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
+
+/// Parameters that have the same names across all bridges.
+#[derive(Debug, PartialEq, StructOpt)]
+pub struct HeadersAndMessagesSharedParams {
+	/// Hex-encoded lane identifiers that should be served by the complex relay.
+	#[structopt(long, default_value = "00000000")]
+	pub lane: Vec<HexLaneId>,
+	#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
+	pub relayer_mode: RelayerMode,
+	/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
+	/// are relayed.
+	#[structopt(long)]
+	pub only_mandatory_headers: bool,
+	#[structopt(flatten)]
+	pub prometheus_params: PrometheusParams,
+}
+
+pub struct Full2WayBridgeCommonParams<
+	Left: TransactionSignScheme + CliChain,
+	Right: TransactionSignScheme + CliChain,
+> {
+	pub shared: HeadersAndMessagesSharedParams,
+
+	pub left: Client<Left>,
+	// default signer, which is always used to sign messages relay transactions on the left chain
+	pub left_sign: AccountKeyPairOf<Left>,
+	pub left_transactions_mortality: Option<u32>,
+	pub left_messages_pallet_owner: Option<AccountKeyPairOf<Left>>,
+	pub at_left_accounts: Vec<TaggedAccount<AccountIdOf<Left>>>,
+
+	pub right: Client<Right>,
+	// default signer, which is always used to sign messages relay transactions on the right chain
+	pub right_sign: AccountKeyPairOf<Right>,
+	pub right_transactions_mortality: Option<u32>,
+	pub right_messages_pallet_owner: Option<AccountKeyPairOf<Right>>,
+	pub at_right_accounts: Vec<TaggedAccount<AccountIdOf<Right>>>,
+}
+
+// All supported chains.
+declare_chain_cli_schema!(Millau, millau);
+declare_chain_cli_schema!(Rialto, rialto);
+declare_chain_cli_schema!(RialtoParachain, rialto_parachain);
+// Means to override signers of different layer transactions.
+declare_chain_cli_schema!(MillauHeadersToRialto, millau_headers_to_rialto);
+declare_chain_cli_schema!(MillauHeadersToRialtoParachain, millau_headers_to_rialto_parachain);
+declare_chain_cli_schema!(RialtoHeadersToMillau, rialto_headers_to_millau);
+declare_chain_cli_schema!(RialtoParachainsToMillau, rialto_parachains_to_millau);
+// All supported bridges.
+declare_relay_to_relay_bridge_schema!(Millau, Rialto);
+declare_relay_to_parachain_bridge_schema!(Millau, RialtoParachain, Rialto);
+
+#[async_trait]
+trait Full2WayBridgeBase: Sized + Send + Sync {
+	/// The CLI params for the bridge.
+	type Params;
+	/// The left relay chain.
+	type Left: TransactionSignScheme<Chain = Self::Left>
+		+ CliChain<KeyPair = AccountKeyPairOf<Self::Left>>;
+	/// The right destination chain (it can be a relay or a parachain).
+	type Right: TransactionSignScheme<Chain = Self::Right>
+		+ CliChain<KeyPair = AccountKeyPairOf<Self::Right>>;
+
+	fn common(&self) -> &Full2WayBridgeCommonParams<Self::Left, Self::Right>;
+
+	fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right>;
+
+	async fn start_on_demand_headers_relayers(
+		&mut self,
+	) -> anyhow::Result<(
+		Arc<dyn OnDemandRelay<BlockNumberOf<Self::Left>>>,
+		Arc<dyn OnDemandRelay<BlockNumberOf<Self::Right>>>,
+	)>;
+}
+
+#[async_trait]
+trait Full2WayBridge: Sized + Sync
+where
+	AccountIdOf<Self::Left>: From<<AccountKeyPairOf<Self::Left> as Pair>::Public>,
+	AccountIdOf<Self::Right>: From<<AccountKeyPairOf<Self::Right> as Pair>::Public>,
+	BalanceOf<Self::Left>: TryFrom<BalanceOf<Self::Right>> + Into<u128>,
+	BalanceOf<Self::Right>: TryFrom<BalanceOf<Self::Left>> + Into<u128>,
+{
+	type Base: Full2WayBridgeBase<Left = Self::Left, Right = Self::Right>;
+
+	/// The left relay chain.
+	type Left: Chain
+		+ ChainWithBalances
+		+ TransactionSignScheme<Chain = Self::Left>
+		+ CliChain<KeyPair = AccountKeyPairOf<Self::Left>>;
+	/// The right relay chain.
+	type Right: Chain
+		+ ChainWithBalances
+		+ TransactionSignScheme<Chain = Self::Right>
+		+ CliChain<KeyPair = AccountKeyPairOf<Self::Right>>;
+
+	// Left to Right bridge
+	type L2R: MessagesCliBridge<Source = Self::Left, Target = Self::Right>;
+	// Right to Left bridge
+	type R2L: MessagesCliBridge<Source = Self::Right, Target = Self::Left>;
+
+	fn new(params: <Self::Base as Full2WayBridgeBase>::Params) -> anyhow::Result<Self>;
+
+	fn base(&self) -> &Self::Base;
+
+	fn mut_base(&mut self) -> &mut Self::Base;
+
+	async fn run(&mut self) -> anyhow::Result<()> {
+		let left_client = self.base().common().left.clone();
+		let left_transactions_mortality = self.base().common().left_transactions_mortality;
+		let left_sign = self.base().common().left_sign.clone();
+		let left_messages_pallet_owner = self.base().common().left_messages_pallet_owner.clone();
+		let right_client = self.base().common().right.clone();
+		let right_transactions_mortality = self.base().common().right_transactions_mortality;
+		let right_sign = self.base().common().right_sign.clone();
+		let right_messages_pallet_owner = self.base().common().right_messages_pallet_owner.clone();
+
+		let lanes = self.base().common().shared.lane.clone();
+		let relayer_mode = self.base().common().shared.relayer_mode.into();
+		let relay_strategy = MixStrategy::new(relayer_mode);
+
+		// create metrics registry and register standalone metrics
+		let metrics_params: MetricsParams =
+			self.base().common().shared.prometheus_params.clone().into();
+		let metrics_params = relay_utils::relay_metrics(metrics_params).into_params();
+		let left_to_right_metrics = substrate_relay_helper::messages_metrics::standalone_metrics::<
+			<Self::L2R as MessagesCliBridge>::MessagesLane,
+		>(left_client.clone(), right_client.clone())?;
+		let right_to_left_metrics = left_to_right_metrics.clone().reverse();
+		self.mut_base().mut_common().at_left_accounts.push(TaggedAccount::Messages {
+			id: left_sign.public().into(),
+			bridged_chain: Self::Right::NAME.to_string(),
+		});
+		self.mut_base().mut_common().at_right_accounts.push(TaggedAccount::Messages {
+			id: right_sign.public().into(),
+			bridged_chain: Self::Left::NAME.to_string(),
+		});
+
+		// start conversion rate update loops for left/right chains
+		if let Some(left_messages_pallet_owner) = left_messages_pallet_owner.clone() {
+			let left_client = left_client.clone();
+			let format_err = || {
+				anyhow::format_err!(
+					"Cannon run conversion rate updater: {} -> {}",
+					Self::Right::NAME,
+					Self::Left::NAME
+				)
+			};
+			substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::<
+				<Self::L2R as MessagesCliBridge>::MessagesLane,
+				Self::Left,
+			>(
+				left_client,
+				TransactionParams {
+					signer: left_messages_pallet_owner.clone(),
+					mortality: left_transactions_mortality,
+				},
+				left_to_right_metrics
+					.target_to_source_conversion_rate
+					.as_ref()
+					.ok_or_else(format_err)?
+					.shared_value_ref(),
+				left_to_right_metrics
+					.target_to_base_conversion_rate
+					.as_ref()
+					.ok_or_else(format_err)?
+					.shared_value_ref(),
+				left_to_right_metrics
+					.source_to_base_conversion_rate
+					.as_ref()
+					.ok_or_else(format_err)?
+					.shared_value_ref(),
+				CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
+			);
+			self.mut_base().mut_common().at_left_accounts.push(
+				TaggedAccount::MessagesPalletOwner {
+					id: left_messages_pallet_owner.public().into(),
+					bridged_chain: Self::Right::NAME.to_string(),
+				},
+			);
+		}
+		if let Some(right_messages_pallet_owner) = right_messages_pallet_owner.clone() {
+			let right_client = right_client.clone();
+			let format_err = || {
+				anyhow::format_err!(
+					"Cannon run conversion rate updater: {} -> {}",
+					Self::Left::NAME,
+					Self::Right::NAME
+				)
+			};
+			substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::<
+				<Self::R2L as MessagesCliBridge>::MessagesLane,
+				Self::Right,
+			>(
+				right_client,
+				TransactionParams {
+					signer: right_messages_pallet_owner.clone(),
+					mortality: right_transactions_mortality,
+				},
+				right_to_left_metrics
+					.target_to_source_conversion_rate
+					.as_ref()
+					.ok_or_else(format_err)?
+					.shared_value_ref(),
+				right_to_left_metrics
+					.target_to_base_conversion_rate
+					.as_ref()
+					.ok_or_else(format_err)?
+					.shared_value_ref(),
+				right_to_left_metrics
+					.source_to_base_conversion_rate
+					.as_ref()
+					.ok_or_else(format_err)?
+					.shared_value_ref(),
+				CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
+			);
+			self.mut_base().mut_common().at_right_accounts.push(
+				TaggedAccount::MessagesPalletOwner {
+					id: right_messages_pallet_owner.public().into(),
+					bridged_chain: Self::Left::NAME.to_string(),
+				},
+			);
+		}
+
+		// start on-demand header relays
+		let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) =
+			self.mut_base().start_on_demand_headers_relayers().await?;
+
+		// add balance-related metrics
+		let metrics_params = substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
+			left_client.clone(),
+			metrics_params,
+			&self.base().common().at_left_accounts,
+		)
+		.await?;
+		let metrics_params = substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
+			right_client.clone(),
+			metrics_params,
+			&self.base().common().at_right_accounts,
+		)
+		.await?;
+
+		// Need 2x capacity since we consider both directions for each lane
+		let mut message_relays = Vec::with_capacity(lanes.len() * 2);
+		for lane in lanes {
+			let lane = lane.into();
+			let left_to_right_messages = substrate_relay_helper::messages_lane::run::<
+				<Self::L2R as MessagesCliBridge>::MessagesLane,
+			>(MessagesRelayParams {
+				source_client: left_client.clone(),
+				source_transaction_params: TransactionParams {
+					signer: left_sign.clone(),
+					mortality: left_transactions_mortality,
+				},
+				target_client: right_client.clone(),
+				target_transaction_params: TransactionParams {
+					signer: right_sign.clone(),
+					mortality: right_transactions_mortality,
+				},
+				source_to_target_headers_relay: Some(left_to_right_on_demand_headers.clone()),
+				target_to_source_headers_relay: Some(right_to_left_on_demand_headers.clone()),
+				lane_id: lane,
+				metrics_params: metrics_params.clone().disable(),
+				standalone_metrics: Some(left_to_right_metrics.clone()),
+				relay_strategy: relay_strategy.clone(),
+			})
+			.map_err(|e| anyhow::format_err!("{}", e))
+			.boxed();
+			let right_to_left_messages = substrate_relay_helper::messages_lane::run::<
+				<Self::R2L as MessagesCliBridge>::MessagesLane,
+			>(MessagesRelayParams {
+				source_client: right_client.clone(),
+				source_transaction_params: TransactionParams {
+					signer: right_sign.clone(),
+					mortality: right_transactions_mortality,
+				},
+				target_client: left_client.clone(),
+				target_transaction_params: TransactionParams {
+					signer: left_sign.clone(),
+					mortality: left_transactions_mortality,
+				},
+				source_to_target_headers_relay: Some(right_to_left_on_demand_headers.clone()),
+				target_to_source_headers_relay: Some(left_to_right_on_demand_headers.clone()),
+				lane_id: lane,
+				metrics_params: metrics_params.clone().disable(),
+				standalone_metrics: Some(right_to_left_metrics.clone()),
+				relay_strategy: relay_strategy.clone(),
+			})
+			.map_err(|e| anyhow::format_err!("{}", e))
+			.boxed();
+
+			message_relays.push(left_to_right_messages);
+			message_relays.push(right_to_left_messages);
+		}
+
+		relay_utils::relay_metrics(metrics_params)
+			.expose()
+			.await
+			.map_err(|e| anyhow::format_err!("{}", e))?;
+
+		futures::future::select_all(message_relays).await.0
+	}
+}
+
+pub struct MillauRialtoFull2WayBridge {
+	base: <Self as Full2WayBridge>::Base,
+}
+
+#[async_trait]
+impl Full2WayBridge for MillauRialtoFull2WayBridge {
+	type Base = RelayToRelayBridge<Self::L2R, Self::R2L>;
+	type Left = relay_millau_client::Millau;
+	type Right = relay_rialto_client::Rialto;
+	type L2R = MillauToRialtoCliBridge;
+	type R2L = RialtoToMillauCliBridge;
+
+	fn new(base: Self::Base) -> anyhow::Result<Self> {
+		Ok(Self { base })
+	}
+
+	fn base(&self) -> &Self::Base {
+		&self.base
+	}
+
+	fn mut_base(&mut self) -> &mut Self::Base {
+		&mut self.base
+	}
+}
+
+pub struct MillauRialtoParachainFull2WayBridge {
+	base: <Self as Full2WayBridge>::Base,
+}
+
+#[async_trait]
+impl Full2WayBridge for MillauRialtoParachainFull2WayBridge {
+	type Base = RelayToParachainBridge<Self::L2R, Self::R2L>;
+	type Left = relay_millau_client::Millau;
+	type Right = relay_rialto_parachain_client::RialtoParachain;
+	type L2R = MillauToRialtoParachainCliBridge;
+	type R2L = RialtoParachainToMillauCliBridge;
+
+	fn new(base: Self::Base) -> anyhow::Result<Self> {
+		Ok(Self { base })
+	}
+
+	fn base(&self) -> &Self::Base {
+		&self.base
+	}
+
+	fn mut_base(&mut self) -> &mut Self::Base {
+		&mut self.base
+	}
+}
+
+/// Start headers+messages relayer process.
+#[derive(Debug, PartialEq, StructOpt)]
+pub enum RelayHeadersAndMessages {
+	MillauRialto(MillauRialtoHeadersAndMessages),
+	MillauRialtoParachain(MillauRialtoParachainHeadersAndMessages),
+}
+
+impl RelayHeadersAndMessages {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			RelayHeadersAndMessages::MillauRialto(params) =>
+				MillauRialtoFull2WayBridge::new(params.into_bridge().await?)?.run().await,
+			RelayHeadersAndMessages::MillauRialtoParachain(params) =>
+				MillauRialtoParachainFull2WayBridge::new(params.into_bridge().await?)?
+					.run()
+					.await,
+		}
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn should_parse_relay_to_relay_options() {
+		// when
+		let res = RelayHeadersAndMessages::from_iter(vec![
+			"relay-headers-and-messages",
+			"millau-rialto",
+			"--millau-host",
+			"millau-node-alice",
+			"--millau-port",
+			"9944",
+			"--millau-signer",
+			"//Charlie",
+			"--millau-messages-pallet-owner",
+			"//RialtoMessagesOwner",
+			"--millau-transactions-mortality",
+			"64",
+			"--rialto-host",
+			"rialto-node-alice",
+			"--rialto-port",
+			"9944",
+			"--rialto-signer",
+			"//Charlie",
+			"--rialto-messages-pallet-owner",
+			"//MillauMessagesOwner",
+			"--rialto-transactions-mortality",
+			"64",
+			"--lane",
+			"00000000",
+			"--lane",
+			"73776170",
+			"--prometheus-host",
+			"0.0.0.0",
+		]);
+
+		// then
+		assert_eq!(
+			res,
+			RelayHeadersAndMessages::MillauRialto(MillauRialtoHeadersAndMessages {
+				shared: HeadersAndMessagesSharedParams {
+					lane: vec![
+						HexLaneId([0x00, 0x00, 0x00, 0x00]),
+						HexLaneId([0x73, 0x77, 0x61, 0x70])
+					],
+					relayer_mode: RelayerMode::Rational,
+					only_mandatory_headers: false,
+					prometheus_params: PrometheusParams {
+						no_prometheus: false,
+						prometheus_host: "0.0.0.0".into(),
+						prometheus_port: 9616,
+					},
+				},
+				left: MillauConnectionParams {
+					millau_host: "millau-node-alice".into(),
+					millau_port: 9944,
+					millau_secure: false,
+					millau_runtime_version: MillauRuntimeVersionParams {
+						millau_version_mode: RuntimeVersionType::Bundle,
+						millau_spec_version: None,
+						millau_transaction_version: None,
+					},
+				},
+				left_sign: MillauSigningParams {
+					millau_signer: Some("//Charlie".into()),
+					millau_signer_password: None,
+					millau_signer_file: None,
+					millau_signer_password_file: None,
+					millau_transactions_mortality: Some(64),
+				},
+				left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams {
+					millau_messages_pallet_owner: Some("//RialtoMessagesOwner".into()),
+					millau_messages_pallet_owner_password: None,
+				},
+				left_headers_to_right_sign_override: MillauHeadersToRialtoSigningParams {
+					millau_headers_to_rialto_signer: None,
+					millau_headers_to_rialto_signer_password: None,
+					millau_headers_to_rialto_signer_file: None,
+					millau_headers_to_rialto_signer_password_file: None,
+					millau_headers_to_rialto_transactions_mortality: None,
+				},
+				right: RialtoConnectionParams {
+					rialto_host: "rialto-node-alice".into(),
+					rialto_port: 9944,
+					rialto_secure: false,
+					rialto_runtime_version: RialtoRuntimeVersionParams {
+						rialto_version_mode: RuntimeVersionType::Bundle,
+						rialto_spec_version: None,
+						rialto_transaction_version: None,
+					},
+				},
+				right_sign: RialtoSigningParams {
+					rialto_signer: Some("//Charlie".into()),
+					rialto_signer_password: None,
+					rialto_signer_file: None,
+					rialto_signer_password_file: None,
+					rialto_transactions_mortality: Some(64),
+				},
+				right_messages_pallet_owner: RialtoMessagesPalletOwnerSigningParams {
+					rialto_messages_pallet_owner: Some("//MillauMessagesOwner".into()),
+					rialto_messages_pallet_owner_password: None,
+				},
+				right_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams {
+					rialto_headers_to_millau_signer: None,
+					rialto_headers_to_millau_signer_password: None,
+					rialto_headers_to_millau_signer_file: None,
+					rialto_headers_to_millau_signer_password_file: None,
+					rialto_headers_to_millau_transactions_mortality: None,
+				},
+			}),
+		);
+	}
+
+	#[test]
+	fn should_parse_relay_to_parachain_options() {
+		// when
+		let res = RelayHeadersAndMessages::from_iter(vec![
+			"relay-headers-and-messages",
+			"millau-rialto-parachain",
+			"--millau-host",
+			"millau-node-alice",
+			"--millau-port",
+			"9944",
+			"--millau-signer",
+			"//Iden",
+			"--rialto-headers-to-millau-signer",
+			"//Ken",
+			"--millau-messages-pallet-owner",
+			"//RialtoParachainMessagesOwner",
+			"--millau-transactions-mortality",
+			"64",
+			"--rialto-parachain-host",
+			"rialto-parachain-collator-charlie",
+			"--rialto-parachain-port",
+			"9944",
+			"--rialto-parachain-signer",
+			"//George",
+			"--rialto-parachain-messages-pallet-owner",
+			"//MillauMessagesOwner",
+			"--rialto-parachain-transactions-mortality",
+			"64",
+			"--rialto-host",
+			"rialto-node-alice",
+			"--rialto-port",
+			"9944",
+			"--lane",
+			"00000000",
+			"--prometheus-host",
+			"0.0.0.0",
+		]);
+
+		// then
+		assert_eq!(
+			res,
+			RelayHeadersAndMessages::MillauRialtoParachain(
+				MillauRialtoParachainHeadersAndMessages {
+					shared: HeadersAndMessagesSharedParams {
+						lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])],
+						relayer_mode: RelayerMode::Rational,
+						only_mandatory_headers: false,
+						prometheus_params: PrometheusParams {
+							no_prometheus: false,
+							prometheus_host: "0.0.0.0".into(),
+							prometheus_port: 9616,
+						},
+					},
+					left: MillauConnectionParams {
+						millau_host: "millau-node-alice".into(),
+						millau_port: 9944,
+						millau_secure: false,
+						millau_runtime_version: MillauRuntimeVersionParams {
+							millau_version_mode: RuntimeVersionType::Bundle,
+							millau_spec_version: None,
+							millau_transaction_version: None,
+						},
+					},
+					left_sign: MillauSigningParams {
+						millau_signer: Some("//Iden".into()),
+						millau_signer_password: None,
+						millau_signer_file: None,
+						millau_signer_password_file: None,
+						millau_transactions_mortality: Some(64),
+					},
+					left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams {
+						millau_messages_pallet_owner: Some("//RialtoParachainMessagesOwner".into()),
+						millau_messages_pallet_owner_password: None,
+					},
+					left_headers_to_right_sign_override:
+						MillauHeadersToRialtoParachainSigningParams {
+							millau_headers_to_rialto_parachain_signer: None,
+							millau_headers_to_rialto_parachain_signer_password: None,
+							millau_headers_to_rialto_parachain_signer_file: None,
+							millau_headers_to_rialto_parachain_signer_password_file: None,
+							millau_headers_to_rialto_parachain_transactions_mortality: None,
+						},
+					right: RialtoParachainConnectionParams {
+						rialto_parachain_host: "rialto-parachain-collator-charlie".into(),
+						rialto_parachain_port: 9944,
+						rialto_parachain_secure: false,
+						rialto_parachain_runtime_version: RialtoParachainRuntimeVersionParams {
+							rialto_parachain_version_mode: RuntimeVersionType::Bundle,
+							rialto_parachain_spec_version: None,
+							rialto_parachain_transaction_version: None,
+						},
+					},
+					right_sign: RialtoParachainSigningParams {
+						rialto_parachain_signer: Some("//George".into()),
+						rialto_parachain_signer_password: None,
+						rialto_parachain_signer_file: None,
+						rialto_parachain_signer_password_file: None,
+						rialto_parachain_transactions_mortality: Some(64),
+					},
+					right_messages_pallet_owner: RialtoParachainMessagesPalletOwnerSigningParams {
+						rialto_parachain_messages_pallet_owner: Some(
+							"//MillauMessagesOwner".into()
+						),
+						rialto_parachain_messages_pallet_owner_password: None,
+					},
+					right_relay_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams {
+						rialto_headers_to_millau_signer: Some("//Ken".into()),
+						rialto_headers_to_millau_signer_password: None,
+						rialto_headers_to_millau_signer_file: None,
+						rialto_headers_to_millau_signer_password_file: None,
+						rialto_headers_to_millau_transactions_mortality: None,
+					},
+					right_parachains_to_left_sign_override: RialtoParachainsToMillauSigningParams {
+						rialto_parachains_to_millau_signer: None,
+						rialto_parachains_to_millau_signer_password: None,
+						rialto_parachains_to_millau_signer_file: None,
+						rialto_parachains_to_millau_signer_password_file: None,
+						rialto_parachains_to_millau_transactions_mortality: None,
+					},
+					right_relay: RialtoConnectionParams {
+						rialto_host: "rialto-node-alice".into(),
+						rialto_port: 9944,
+						rialto_secure: false,
+						rialto_runtime_version: RialtoRuntimeVersionParams {
+							rialto_version_mode: RuntimeVersionType::Bundle,
+							rialto_spec_version: None,
+							rialto_transaction_version: None,
+						},
+					},
+				}
+			),
+		);
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0d847141d4a681d0b2317cff651b4de8509e2538
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs
@@ -0,0 +1,235 @@
+// Copyright 2019-2022 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+use async_trait::async_trait;
+use std::sync::Arc;
+
+use crate::cli::{
+	bridge::{
+		CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge,
+		RelayToRelayHeadersCliBridge,
+	},
+	relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams},
+	CliChain,
+};
+use bp_polkadot_core::parachains::ParaHash;
+use bp_runtime::BlockNumberOf;
+use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
+use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, Chain, Client, TransactionSignScheme};
+use sp_core::Pair;
+use substrate_relay_helper::{
+	finality::SubstrateFinalitySyncPipeline,
+	on_demand::{
+		headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay,
+	},
+	TaggedAccount, TransactionParams,
+};
+
+pub struct RelayToParachainBridge<
+	L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge,
+	R2L: MessagesCliBridge + ParachainToRelayHeadersCliBridge,
+> {
+	pub common:
+		Full2WayBridgeCommonParams<<R2L as CliBridgeBase>::Target, <L2R as CliBridgeBase>::Target>,
+	pub right_relay: Client<<R2L as ParachainToRelayHeadersCliBridge>::SourceRelay>,
+
+	// override for right_relay->left headers signer
+	pub right_headers_to_left_transaction_params:
+		TransactionParams<AccountKeyPairOf<<R2L as CliBridgeBase>::Target>>,
+	// override for right->left parachains signer
+	pub right_parachains_to_left_transaction_params:
+		TransactionParams<AccountKeyPairOf<<R2L as CliBridgeBase>::Target>>,
+	// override for left->right headers signer
+	pub left_headers_to_right_transaction_params:
+		TransactionParams<AccountKeyPairOf<<L2R as CliBridgeBase>::Target>>,
+}
+
+macro_rules! declare_relay_to_parachain_bridge_schema {
+	// chain, parachain, relay-chain-of-parachain
+	($left_chain:ident, $right_parachain:ident, $right_chain:ident) => {
+		paste::item! {
+			#[doc = $left_chain ", " $right_parachain " and " $right_chain " headers+parachains+messages relay params."]
+			#[derive(Debug, PartialEq, StructOpt)]
+			pub struct [<$left_chain $right_parachain HeadersAndMessages>] {
+				#[structopt(flatten)]
+				shared: HeadersAndMessagesSharedParams,
+				#[structopt(flatten)]
+				left: [<$left_chain ConnectionParams>],
+				// default signer, which is always used to sign messages relay transactions on the left chain
+				#[structopt(flatten)]
+				left_sign: [<$left_chain SigningParams>],
+				// override for right_relay->left headers signer
+				#[structopt(flatten)]
+				right_relay_headers_to_left_sign_override: [<$right_chain HeadersTo $left_chain SigningParams>],
+				// override for right->left parachains signer
+				#[structopt(flatten)]
+				right_parachains_to_left_sign_override: [<$right_chain ParachainsTo $left_chain SigningParams>],
+				#[structopt(flatten)]
+				left_messages_pallet_owner: [<$left_chain MessagesPalletOwnerSigningParams>],
+				#[structopt(flatten)]
+				right: [<$right_parachain ConnectionParams>],
+				// default signer, which is always used to sign messages relay transactions on the right chain
+				#[structopt(flatten)]
+				right_sign: [<$right_parachain SigningParams>],
+				// override for left->right headers signer
+				#[structopt(flatten)]
+				left_headers_to_right_sign_override: [<$left_chain HeadersTo $right_parachain SigningParams>],
+				#[structopt(flatten)]
+				right_messages_pallet_owner: [<$right_parachain MessagesPalletOwnerSigningParams>],
+				#[structopt(flatten)]
+				right_relay: [<$right_chain ConnectionParams>],
+			}
+
+			impl [<$left_chain $right_parachain HeadersAndMessages>] {
+				async fn into_bridge<
+					Left: TransactionSignScheme + CliChain<KeyPair = AccountKeyPairOf<Left>>,
+					Right: TransactionSignScheme + CliChain<KeyPair = AccountKeyPairOf<Right>>,
+					RightRelay: TransactionSignScheme + CliChain,
+					L2R: CliBridgeBase<Source = Left, Target = Right> + MessagesCliBridge + RelayToRelayHeadersCliBridge,
+					R2L: CliBridgeBase<Source = Right, Target = Left>
+						+ MessagesCliBridge
+						+ ParachainToRelayHeadersCliBridge<SourceRelay = RightRelay>,
+				>(
+					self,
+				) -> anyhow::Result<RelayToParachainBridge<L2R, R2L>> {
+					Ok(RelayToParachainBridge {
+						common: Full2WayBridgeCommonParams {
+							shared: self.shared,
+							left: self.left.into_client::<Left>().await?,
+							left_sign: self.left_sign.to_keypair::<Left>()?,
+							left_transactions_mortality: self.left_sign.transactions_mortality()?,
+							left_messages_pallet_owner: self.left_messages_pallet_owner.to_keypair::<Left>()?,
+							at_left_accounts: vec![],
+							right: self.right.into_client::<Right>().await?,
+							right_sign: self.right_sign.to_keypair::<Right>()?,
+							right_transactions_mortality: self.right_sign.transactions_mortality()?,
+							right_messages_pallet_owner: self.right_messages_pallet_owner.to_keypair::<Right>()?,
+							at_right_accounts: vec![],
+						},
+						right_relay: self.right_relay.into_client::<RightRelay>().await?,
+						right_headers_to_left_transaction_params: self
+							.right_relay_headers_to_left_sign_override
+							.transaction_params_or::<Left, _>(
+							&self.left_sign,
+						)?,
+						right_parachains_to_left_transaction_params: self
+							.right_parachains_to_left_sign_override
+							.transaction_params_or::<Left, _>(
+							&self.left_sign,
+						)?,
+						left_headers_to_right_transaction_params: self
+							.left_headers_to_right_sign_override
+							.transaction_params_or::<Right, _>(&self.right_sign)?,
+					})
+				}
+			}
+		}
+	};
+}
+
+#[async_trait]
+impl<
+		Left: Chain + TransactionSignScheme<Chain = Left> + CliChain<KeyPair = AccountKeyPairOf<Left>>,
+		Right: Chain<Hash = ParaHash>
+			+ TransactionSignScheme<Chain = Right>
+			+ CliChain<KeyPair = AccountKeyPairOf<Right>>,
+		RightRelay: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
+			+ TransactionSignScheme
+			+ CliChain,
+		L2R: CliBridgeBase<Source = Left, Target = Right>
+			+ MessagesCliBridge
+			+ RelayToRelayHeadersCliBridge,
+		R2L: CliBridgeBase<Source = Right, Target = Left>
+			+ MessagesCliBridge
+			+ ParachainToRelayHeadersCliBridge<SourceRelay = RightRelay>,
+	> Full2WayBridgeBase for RelayToParachainBridge<L2R, R2L>
+where
+	AccountIdOf<Left>: From<<AccountKeyPairOf<Left> as Pair>::Public>,
+	AccountIdOf<Right>: From<<AccountKeyPairOf<Right> as Pair>::Public>,
+{
+	type Params = RelayToParachainBridge<L2R, R2L>;
+	type Left = Left;
+	type Right = Right;
+
+	fn common(&self) -> &Full2WayBridgeCommonParams<Left, Right> {
+		&self.common
+	}
+
+	fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right> {
+		&mut self.common
+	}
+
+	async fn start_on_demand_headers_relayers(
+		&mut self,
+	) -> anyhow::Result<(
+		Arc<dyn OnDemandRelay<BlockNumberOf<Self::Left>>>,
+		Arc<dyn OnDemandRelay<BlockNumberOf<Self::Right>>>,
+	)> {
+		self.common.at_left_accounts.push(TaggedAccount::Headers {
+			id: self.right_headers_to_left_transaction_params.signer.public().into(),
+			bridged_chain: RightRelay::NAME.to_string(),
+		});
+		self.common.at_left_accounts.push(TaggedAccount::Parachains {
+			id: self.right_parachains_to_left_transaction_params.signer.public().into(),
+			bridged_chain: RightRelay::NAME.to_string(),
+		});
+		self.common.at_right_accounts.push(TaggedAccount::Headers {
+			id: self.left_headers_to_right_transaction_params.signer.public().into(),
+			bridged_chain: Left::NAME.to_string(),
+		});
+
+		<L2R as RelayToRelayHeadersCliBridge>::Finality::start_relay_guards(
+			&self.common.right,
+			&self.left_headers_to_right_transaction_params,
+			self.common.right.can_start_version_guard(),
+		)
+		.await?;
+		<R2L as ParachainToRelayHeadersCliBridge>::RelayFinality::start_relay_guards(
+			&self.common.left,
+			&self.right_headers_to_left_transaction_params,
+			self.common.left.can_start_version_guard(),
+		)
+		.await?;
+
+		let left_to_right_on_demand_headers =
+			OnDemandHeadersRelay::new::<<L2R as RelayToRelayHeadersCliBridge>::Finality>(
+				self.common.left.clone(),
+				self.common.right.clone(),
+				self.left_headers_to_right_transaction_params.clone(),
+				self.common.shared.only_mandatory_headers,
+			);
+		let right_relay_to_left_on_demand_headers =
+			OnDemandHeadersRelay::new::<<R2L as ParachainToRelayHeadersCliBridge>::RelayFinality>(
+				self.right_relay.clone(),
+				self.common.left.clone(),
+				self.right_headers_to_left_transaction_params.clone(),
+				self.common.shared.only_mandatory_headers,
+			);
+		let right_to_left_on_demand_parachains = OnDemandParachainsRelay::new::<
+			<R2L as ParachainToRelayHeadersCliBridge>::ParachainFinality,
+		>(
+			self.right_relay.clone(),
+			self.common.left.clone(),
+			self.right_parachains_to_left_transaction_params.clone(),
+			Arc::new(right_relay_to_left_on_demand_headers),
+		);
+
+		Ok((
+			Arc::new(left_to_right_on_demand_headers),
+			Arc::new(right_to_left_on_demand_parachains),
+		))
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4e9ecd2b581221168922d69850f1c78e4e11f824
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs
@@ -0,0 +1,187 @@
+// Copyright 2019-2022 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+use async_trait::async_trait;
+use std::sync::Arc;
+
+use crate::cli::{
+	bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge},
+	relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams},
+	CliChain,
+};
+use bp_runtime::BlockNumberOf;
+use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, Chain, TransactionSignScheme};
+use sp_core::Pair;
+use substrate_relay_helper::{
+	finality::SubstrateFinalitySyncPipeline,
+	on_demand::{headers::OnDemandHeadersRelay, OnDemandRelay},
+	TaggedAccount, TransactionParams,
+};
+
+pub struct RelayToRelayBridge<
+	L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge,
+	R2L: MessagesCliBridge + RelayToRelayHeadersCliBridge,
+> {
+	pub common:
+		Full2WayBridgeCommonParams<<R2L as CliBridgeBase>::Target, <L2R as CliBridgeBase>::Target>,
+	// override for right->left headers signer
+	pub right_to_left_transaction_params:
+		TransactionParams<AccountKeyPairOf<<R2L as CliBridgeBase>::Target>>,
+	// override for left->right headers signer
+	pub left_to_right_transaction_params:
+		TransactionParams<AccountKeyPairOf<<L2R as CliBridgeBase>::Target>>,
+}
+
+macro_rules! declare_relay_to_relay_bridge_schema {
+	($left_chain:ident, $right_chain:ident) => {
+		paste::item! {
+			#[doc = $left_chain " and " $right_chain " headers+messages relay params."]
+			#[derive(Debug, PartialEq, StructOpt)]
+			pub struct [<$left_chain $right_chain HeadersAndMessages>] {
+				#[structopt(flatten)]
+				shared: HeadersAndMessagesSharedParams,
+				// default signer, which is always used to sign messages relay transactions on the left chain
+				#[structopt(flatten)]
+				left: [<$left_chain ConnectionParams>],
+				// override for right->left headers signer
+				#[structopt(flatten)]
+				right_headers_to_left_sign_override: [<$right_chain HeadersTo $left_chain SigningParams>],
+				#[structopt(flatten)]
+				left_sign: [<$left_chain SigningParams>],
+				#[structopt(flatten)]
+				left_messages_pallet_owner: [<$left_chain MessagesPalletOwnerSigningParams>],
+				// default signer, which is always used to sign messages relay transactions on the right chain
+				#[structopt(flatten)]
+				right: [<$right_chain ConnectionParams>],
+				// override for left->right headers signer
+				#[structopt(flatten)]
+				left_headers_to_right_sign_override: [<$left_chain HeadersTo $right_chain SigningParams>],
+				#[structopt(flatten)]
+				right_sign: [<$right_chain SigningParams>],
+				#[structopt(flatten)]
+				right_messages_pallet_owner: [<$right_chain MessagesPalletOwnerSigningParams>],
+			}
+
+			impl [<$left_chain $right_chain HeadersAndMessages>] {
+				async fn into_bridge<
+					Left: TransactionSignScheme + CliChain<KeyPair = AccountKeyPairOf<Left>>,
+					Right: TransactionSignScheme + CliChain<KeyPair = AccountKeyPairOf<Right>>,
+					L2R: CliBridgeBase<Source = Left, Target = Right> + MessagesCliBridge + RelayToRelayHeadersCliBridge,
+					R2L: CliBridgeBase<Source = Right, Target = Left> + MessagesCliBridge + RelayToRelayHeadersCliBridge,
+				>(
+					self,
+				) -> anyhow::Result<RelayToRelayBridge<L2R, R2L>> {
+					Ok(RelayToRelayBridge {
+						common: Full2WayBridgeCommonParams {
+							shared: self.shared,
+							left: self.left.into_client::<Left>().await?,
+							left_sign: self.left_sign.to_keypair::<Left>()?,
+							left_transactions_mortality: self.left_sign.transactions_mortality()?,
+							left_messages_pallet_owner: self.left_messages_pallet_owner.to_keypair::<Left>()?,
+							at_left_accounts: vec![],
+							right: self.right.into_client::<Right>().await?,
+							right_sign: self.right_sign.to_keypair::<Right>()?,
+							right_transactions_mortality: self.right_sign.transactions_mortality()?,
+							right_messages_pallet_owner: self.right_messages_pallet_owner.to_keypair::<Right>()?,
+							at_right_accounts: vec![],
+						},
+
+						right_to_left_transaction_params: self
+							.right_headers_to_left_sign_override
+							.transaction_params_or::<Left, _>(&self.left_sign)?,
+						left_to_right_transaction_params: self
+							.left_headers_to_right_sign_override
+							.transaction_params_or::<Right, _>(&self.right_sign)?,
+					})
+				}
+			}
+		}
+	};
+}
+
+#[async_trait]
+impl<
+		Left: Chain + TransactionSignScheme<Chain = Left> + CliChain<KeyPair = AccountKeyPairOf<Left>>,
+		Right: Chain + TransactionSignScheme<Chain = Right> + CliChain<KeyPair = AccountKeyPairOf<Right>>,
+		L2R: CliBridgeBase<Source = Left, Target = Right>
+			+ MessagesCliBridge
+			+ RelayToRelayHeadersCliBridge,
+		R2L: CliBridgeBase<Source = Right, Target = Left>
+			+ MessagesCliBridge
+			+ RelayToRelayHeadersCliBridge,
+	> Full2WayBridgeBase for RelayToRelayBridge<L2R, R2L>
+where
+	AccountIdOf<Left>: From<<AccountKeyPairOf<Left> as Pair>::Public>,
+	AccountIdOf<Right>: From<<AccountKeyPairOf<Right> as Pair>::Public>,
+{
+	type Params = RelayToRelayBridge<L2R, R2L>;
+	type Left = Left;
+	type Right = Right;
+
+	fn common(&self) -> &Full2WayBridgeCommonParams<Left, Right> {
+		&self.common
+	}
+
+	fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right> {
+		&mut self.common
+	}
+
+	async fn start_on_demand_headers_relayers(
+		&mut self,
+	) -> anyhow::Result<(
+		Arc<dyn OnDemandRelay<BlockNumberOf<Self::Left>>>,
+		Arc<dyn OnDemandRelay<BlockNumberOf<Self::Right>>>,
+	)> {
+		self.common.at_right_accounts.push(TaggedAccount::Headers {
+			id: self.left_to_right_transaction_params.signer.public().into(),
+			bridged_chain: Self::Left::NAME.to_string(),
+		});
+		self.common.at_left_accounts.push(TaggedAccount::Headers {
+			id: self.right_to_left_transaction_params.signer.public().into(),
+			bridged_chain: Self::Right::NAME.to_string(),
+		});
+
+		<L2R as RelayToRelayHeadersCliBridge>::Finality::start_relay_guards(
+			&self.common.right,
+			&self.left_to_right_transaction_params,
+			self.common.right.can_start_version_guard(),
+		)
+		.await?;
+		<R2L as RelayToRelayHeadersCliBridge>::Finality::start_relay_guards(
+			&self.common.left,
+			&self.right_to_left_transaction_params,
+			self.common.left.can_start_version_guard(),
+		)
+		.await?;
+
+		let left_to_right_on_demand_headers =
+			OnDemandHeadersRelay::new::<<L2R as RelayToRelayHeadersCliBridge>::Finality>(
+				self.common.left.clone(),
+				self.common.right.clone(),
+				self.left_to_right_transaction_params.clone(),
+				self.common.shared.only_mandatory_headers,
+			);
+		let right_to_left_on_demand_headers =
+			OnDemandHeadersRelay::new::<<R2L as RelayToRelayHeadersCliBridge>::Finality>(
+				self.common.right.clone(),
+				self.common.left.clone(),
+				self.right_to_left_transaction_params.clone(),
+				self.common.shared.only_mandatory_headers,
+			);
+
+		Ok((Arc::new(left_to_right_on_demand_headers), Arc::new(right_to_left_on_demand_headers)))
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/cli/relay_messages.rs b/bridges/relays/bin-substrate/src/cli/relay_messages.rs
index 7242d1318520368fe9df32cf9cf127edd6c7d241..df21abac991ffacbcfa1bf9d58c951d0ad89a9f1 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_messages.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_messages.rs
@@ -20,16 +20,10 @@ use structopt::StructOpt;
 use strum::{EnumString, EnumVariantNames, VariantNames};
 
 use messages_relay::relay_strategy::MixStrategy;
-use relay_substrate_client::{AccountKeyPairOf, ChainBase, TransactionSignScheme};
-use substrate_relay_helper::{
-	messages_lane::{MessagesRelayParams, SubstrateMessageLane},
-	TransactionParams,
-};
+use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, BalanceOf, TransactionSignScheme};
+use substrate_relay_helper::{messages_lane::MessagesRelayParams, TransactionParams};
 
-use crate::cli::{
-	bridge::*, CliChain, HexLaneId, PrometheusParams, SourceConnectionParams, SourceSigningParams,
-	TargetConnectionParams, TargetSigningParams,
-};
+use crate::cli::{bridge::*, chain_schema::*, CliChain, HexLaneId, PrometheusParams};
 
 /// Relayer operating mode.
 #[derive(Debug, EnumString, EnumVariantNames, Clone, Copy, PartialEq, Eq)]
@@ -79,16 +73,15 @@ trait MessagesRelayer: MessagesCliBridge
 where
 	Self::Source: TransactionSignScheme<Chain = Self::Source>
 		+ CliChain<KeyPair = AccountKeyPairOf<Self::Source>>,
-	<Self::Source as ChainBase>::AccountId: From<<AccountKeyPairOf<Self::Source> as Pair>::Public>,
-	<Self::Target as ChainBase>::AccountId: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
-	<Self::Source as ChainBase>::Balance: TryFrom<<Self::Target as ChainBase>::Balance>,
-	Self::MessagesLane: SubstrateMessageLane<RelayStrategy = MixStrategy>,
+	AccountIdOf<Self::Source>: From<<AccountKeyPairOf<Self::Source> as Pair>::Public>,
+	AccountIdOf<Self::Target>: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
+	BalanceOf<Self::Source>: TryFrom<BalanceOf<Self::Target>>,
 {
 	async fn relay_messages(data: RelayMessages) -> anyhow::Result<()> {
-		let source_client = data.source.to_client::<Self::Source>().await?;
+		let source_client = data.source.into_client::<Self::Source>().await?;
 		let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
 		let source_transactions_mortality = data.source_sign.transactions_mortality()?;
-		let target_client = data.target.to_client::<Self::Target>().await?;
+		let target_client = data.target.into_client::<Self::Target>().await?;
 		let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
 		let target_transactions_mortality = data.target_sign.transactions_mortality()?;
 		let relayer_mode = data.relayer_mode.into();
diff --git a/bridges/relays/bin-substrate/src/cli/relay_parachains.rs b/bridges/relays/bin-substrate/src/cli/relay_parachains.rs
index eb46909b3a1bed71b7f126704623d7c30125ba95..f2d56b7d6e249cefb4fcdb39740e6238418a611a 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_parachains.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_parachains.rs
@@ -16,27 +16,22 @@
 
 use async_trait::async_trait;
 use bp_polkadot_core::parachains::ParaId;
-use pallet_bridge_parachains::RelayBlockNumber;
-use parachains_relay::{
-	parachains_loop::{ParachainSyncParams, SourceClient, TargetClient},
-	ParachainsPipeline,
-};
-use relay_substrate_client::{Chain, RelayChain};
+use parachains_relay::parachains_loop::{ParachainSyncParams, SourceClient, TargetClient};
 use relay_utils::metrics::{GlobalMetrics, StandaloneMetric};
 use structopt::StructOpt;
 use strum::{EnumString, EnumVariantNames, VariantNames};
 use substrate_relay_helper::{
-	parachains::{
-		source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter,
-		SubstrateParachainsPipeline,
-	},
+	parachains::{source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter},
 	TransactionParams,
 };
 
 use crate::cli::{
-	bridge::{CliBridgeBase, RialtoParachainToMillauCliBridge, WestmintToMillauCliBridge},
-	CliChain, PrometheusParams, SourceConnectionParams, TargetConnectionParams,
-	TargetSigningParams,
+	bridge::{
+		ParachainToRelayHeadersCliBridge, RialtoParachainToMillauCliBridge,
+		WestmintToMillauCliBridge,
+	},
+	chain_schema::*,
+	PrometheusParams,
 };
 
 /// Start parachain heads relayer process.
@@ -64,29 +59,23 @@ pub enum RelayParachainsBridge {
 }
 
 #[async_trait]
-trait ParachainsRelayer: CliBridgeBase
+trait ParachainsRelayer: ParachainToRelayHeadersCliBridge
 where
-	ParachainsSource<Self::Pipeline>: SourceClient<ParachainsPipelineAdapter<Self::Pipeline>>,
-	ParachainsTarget<Self::Pipeline>: TargetClient<ParachainsPipelineAdapter<Self::Pipeline>>,
+	ParachainsSource<Self::ParachainFinality>:
+		SourceClient<ParachainsPipelineAdapter<Self::ParachainFinality>>,
+	ParachainsTarget<Self::ParachainFinality>:
+		TargetClient<ParachainsPipelineAdapter<Self::ParachainFinality>>,
 {
-	type SourceRelay: Chain<BlockNumber = RelayBlockNumber> + CliChain + RelayChain;
-	type Pipeline: SubstrateParachainsPipeline<
-			SourceParachain = Self::Source,
-			TargetChain = Self::Target,
-			SourceRelayChain = Self::SourceRelay,
-			TransactionSignScheme = Self::Target,
-		> + ParachainsPipeline<SourceChain = Self::SourceRelay, TargetChain = Self::Target>;
-
 	async fn relay_headers(data: RelayParachains) -> anyhow::Result<()> {
-		let source_client = data.source.to_client::<Self::SourceRelay>().await?;
-		let source_client = ParachainsSource::<Self::Pipeline>::new(source_client, None);
+		let source_client = data.source.into_client::<Self::SourceRelay>().await?;
+		let source_client = ParachainsSource::<Self::ParachainFinality>::new(source_client, None);
 
 		let target_transaction_params = TransactionParams {
 			signer: data.target_sign.to_keypair::<Self::Target>()?,
 			mortality: data.target_sign.target_transactions_mortality,
 		};
-		let target_client = data.target.to_client::<Self::Target>().await?;
-		let target_client = ParachainsTarget::<Self::Pipeline>::new(
+		let target_client = data.target.into_client::<Self::Target>().await?;
+		let target_client = ParachainsTarget::<Self::ParachainFinality>::new(
 			target_client.clone(),
 			target_transaction_params,
 		);
@@ -110,15 +99,9 @@ where
 	}
 }
 
-impl ParachainsRelayer for RialtoParachainToMillauCliBridge {
-	type SourceRelay = relay_rialto_client::Rialto;
-	type Pipeline = crate::chains::rialto_parachains_to_millau::RialtoParachainsToMillau;
-}
+impl ParachainsRelayer for RialtoParachainToMillauCliBridge {}
 
-impl ParachainsRelayer for WestmintToMillauCliBridge {
-	type SourceRelay = relay_westend_client::Westend;
-	type Pipeline = crate::chains::westend_parachains_to_millau::WestendParachainsToMillau;
-}
+impl ParachainsRelayer for WestmintToMillauCliBridge {}
 
 impl RelayParachains {
 	/// Run the command.
diff --git a/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs b/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs
index 62af6afe937209a37595cda842ab0007dcaec626..a4cec796552e12185cfcbc83ec09fa8c13428ac2 100644
--- a/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs
+++ b/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
-use crate::cli::{Balance, TargetConnectionParams, TargetSigningParams};
+use crate::cli::{chain_schema::*, Balance};
 
 use codec::{Decode, Encode};
 use num_traits::{One, Zero};
@@ -102,7 +102,7 @@ impl ResubmitTransactions {
 	pub async fn run(self) -> anyhow::Result<()> {
 		select_bridge!(self.chain, {
 			let relay_loop_name = format!("ResubmitTransactions{}", Target::NAME);
-			let client = self.target.to_client::<Target>().await?;
+			let client = self.target.into_client::<Target>().await?;
 			let transaction_params = TransactionParams {
 				signer: self.target_sign.to_keypair::<Target>()?,
 				mortality: self.target_sign.target_transactions_mortality,
diff --git a/bridges/relays/bin-substrate/src/cli/send_message.rs b/bridges/relays/bin-substrate/src/cli/send_message.rs
index ee17fb3f31fea676e8b70d6e4b0bd8d36197bd4f..da844172c984eb084dda12dda1ae6ad2641d420c 100644
--- a/bridges/relays/bin-substrate/src/cli/send_message.rs
+++ b/bridges/relays/bin-substrate/src/cli/send_message.rs
@@ -16,15 +16,16 @@
 
 use crate::cli::{
 	bridge::{FullBridge, MessagesCliBridge, *},
+	chain_schema::*,
 	encode_message::{self, CliEncodeMessage},
 	estimate_fee::{estimate_message_delivery_and_dispatch_fee, ConversionRateOverride},
-	Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams, SourceSigningParams,
+	Balance, CliChain, HexBytes, HexLaneId,
 };
 use async_trait::async_trait;
-use bp_runtime::AccountIdOf;
 use codec::Encode;
 use relay_substrate_client::{
-	AccountKeyPairOf, Chain, ChainBase, SignParam, TransactionSignScheme, UnsignedTransaction,
+	AccountIdOf, AccountKeyPairOf, Chain, ChainBase, SignParam, TransactionSignScheme,
+	UnsignedTransaction,
 };
 use sp_core::{Bytes, Pair};
 use sp_runtime::AccountId32;
@@ -95,7 +96,7 @@ where
 	async fn send_message(data: SendMessage) -> anyhow::Result<()> {
 		let payload = encode_message::encode_message::<Self::Source, Self::Target>(&data.message)?;
 
-		let source_client = data.source.to_client::<Self::Source>().await?;
+		let source_client = data.source.into_client::<Self::Source>().await?;
 		let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
 
 		let lane = data.lane.clone().into();
diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs
index 448c33c5ba3979d69cbec5ef5fca55633b72d1bc..5619645d0a485ed5f9b8edc133c893e765cdeca2 100644
--- a/bridges/relays/client-substrate/src/client.rs
+++ b/bridges/relays/client-substrate/src/client.rs
@@ -756,6 +756,15 @@ impl<C: Chain> Client<C> {
 		let client = self.client.clone();
 		self.tokio.spawn(async move { make_jsonrpsee_future(client).await }).await?
 	}
+
+	/// Returns `true` if version guard can be started.
+	///
+	/// There's no reason to run version guard when version mode is set to `Auto`. It can
+	/// lead to relay shutdown when chain is upgraded, even though we have explicitly
+	/// said that we don't want to shutdown.
+	pub fn can_start_version_guard(&self) -> bool {
+		!matches!(self.chain_runtime_version, ChainRuntimeVersion::Auto)
+	}
 }
 
 impl<T: DeserializeOwned> Subscription<T> {
diff --git a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs
index 7c621b4c39a51741d312398af13efcd0b7795cbb..4210c7d981bd580000c2fae55039fe1ce711a295 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs
@@ -274,7 +274,7 @@ pub fn standalone_metrics<P: SubstrateMessageLane>(
 pub async fn add_relay_balances_metrics<C: ChainWithBalances>(
 	client: Client<C>,
 	metrics: MetricsParams,
-	relay_accounts: Vec<TaggedAccount<AccountIdOf<C>>>,
+	relay_accounts: &Vec<TaggedAccount<AccountIdOf<C>>>,
 ) -> anyhow::Result<MetricsParams>
 where
 	BalanceOf<C>: Into<u128> + std::fmt::Debug,